Skip to content

Commit

Permalink
计算机操作系统、计算机网络、Java 核心知识点
Browse files Browse the repository at this point in the history
  • Loading branch information
itwanger committed Mar 21, 2022
1 parent afedfbd commit bdc379f
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 2 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@
- [Java到底是值传递还是引用传递?](docs/basic-extra-meal/pass-by-value.md)
- [Java不能实现真正泛型的原因是什么?](docs/basic-extra-meal/true-generic.md)
- [详解Java中Comparable和Comparator的区别](docs/basic-extra-meal/comparable-omparator.md)
- [jdk9为何要将String的底层实现由char[]改成了byte[]?](docs/basic-extra-meal/jdk9-char-byte-string.md)
- [为什么JDK源码中,无限循环大多使用for(;;)而不是while(true)?](docs/basic-extra-meal/jdk-while-for-wuxian-xunhuan.md)


## Java并发编程
Expand Down Expand Up @@ -261,6 +263,9 @@
> - **计算机基础包括操作系统、计算机网络、计算机组成原理、数据结构与算法等**。对于任何一名想要走得更远的 Java 后端开发来说,都是必须要花时间和精力去夯实的。
> - 万丈高露平地起,勿在浮沙筑高台。
- [计算机操作系统知识点大梳理](https://mp.weixin.qq.com/s/G9ZqwEMxjrG5LbgYwM5ACQ)
- [计算机网络核心知识点大梳理](https://mp.weixin.qq.com/s/7EddtzpwIRvYfw34QE4zvw)


# 求职面试

Expand Down
101 changes: 101 additions & 0 deletions docs/basic-extra-meal/jdk-while-for-wuxian-xunhuan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
---
category:
- Java核心
tag:
- Java
---

# 为什么JDK源码中,无限循环大多使用for(;;)而不是while(true)?

在知乎上看到 R 大的这篇回答,着实感觉需要分享给在座的各位 javaer 们,真心透彻。

>https://www.zhihu.com/question/52311366/answer/130090347
-----

首先是先问是不是再问为什么系列。

在JDK8u的jdk项目下做个很粗略的搜索:

```
mymbp:/Users/me/workspace/jdk8u/jdk/src
$ egrep -nr "for \\(\\s?;\\s?;" . | wc -l
369
mymbp:/Users/me/workspace/jdk8u/jdk/src
$ egrep -nr "while \\(true" . | wc -l
323
```

并没有差多少。

其次,for (;;) 在Java中的来源。个人看法是喜欢用这种写法的人,追根溯源是受到C语言里的写法的影响。这些人不一定是自己以前写C习惯了这样写,而可能是间接受以前写C的老师、前辈的影响而习惯这样写的。

在C语言里,如果不include某些头文件或者自己声明的话,是没有内建的_Bool / bool类型,也没有TRUE / FALSE / true / false这些_Bool / bool类型值的字面量的。

所以,假定没有include那些头文件或者自己define出上述字面量,一个不把循环条件写在while (...)括号里的while语句,最常见的是这样:
```
while (1) {
/* ... */
}
```

…但不是所有人都喜欢看到那个魔数“1”的。

而用for (;;)来表达不写循环条件(也就是循环体内不用break或goto就会是无限循环)则非常直观——这就是for语句本身的功能,而且不需要写任何魔数。所以这个写法就流传下来了。

顺带一提,在Java里我是倾向于写while (true)的,不过我也不介意别人在他们自己的项目里写for (;;)。

=====================================

至于Java里while (true)与for (;;)哪个“效率更高”。这种规范没有规定的问题,答案都是“看实现”,毕竟实现只要保证语义符合规范就行了,而效率并不在规范管得着的范畴内。

以Oracle/Sun JDK8u / OpenJDK8u的实现来看,首先看javac对下面俩语句的编译结果:

```java
public void foo() {
int i = 0;
while (true) { i++; }
}

/*
public void foo();
Code:
stack=1, locals=2, args_size=1
0: iconst_0
1: istore_1
2: iinc 1, 1
5: goto 2
*/
```



```java
public void bar() {
int i = 0;
for (;;) { i++; }
}```

/*
public void bar();
Code:
stack=1, locals=2, args_size=1
0: iconst_0
1: istore_1
2: iinc 1, 1
5: goto 2
*/
```

连javac这种几乎什么优化都不做(只做了Java语言规范规定一定要做的常量折叠,和非常少量别的优化)的编译器,对上面俩版本的代码都生成了一样的字节码。后面到解释执行、JIT编译之类的就不用说了,输入都一样,输出也不会不同。

-----

分享的最后,二哥简单说几句。

可能在我们普通人眼中,这种问题完全没有求真的必要性,但 R大认真去研究了,并且得出了非常令人信服的答案。

所以,牛逼之人必有三连之处啊。

以后就可以放心大胆在代码里写 `for(;;) while(true)` 这样的死循环了。
109 changes: 109 additions & 0 deletions docs/basic-extra-meal/jdk9-char-byte-string.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
---
category:
- Java核心
tag:
- Java
---

# jdk9为何要将String的底层实现由char[]改成了byte[]?

大家好,我是二哥呀!如果你不是 Java8 的钉子户,你应该早就发现了:String 类的源码已经由 `char[]` 优化为了 `byte[]` 来存储字符串内容,为什么要这样做呢?

开门见山地说,从 `char[]``byte[]`,最主要的目的是**为了节省字符串占用的内存**。内存占用减少带来的另外一个好处,就是 GC 次数也会减少。

### 一、为什么要优化 String 节省内存空间

我们使用 `jmap -histo:live pid | head -n 10` 命令就可以查看到堆内对象示例的统计信息、查看 ClassLoader 的信息以及 finalizer 队列。

以我正在运行着的编程喵喵项目实例(基于 Java 8)来说,结果是这样的。

![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/basic-extra-meal/jdk9-char-byte-string-d826ce88-bbbe-47a3-a1a9-4dd86dd3632f.png)

其中 String 对象有 17638 个,占用了 423312 个字节的内存,排在第三位。

由于 Java 8 的 String 内部实现仍然是 `char[]`,所以我们可以看到内存占用排在第 1 位的就是 char 数组。

`char[]` 对象有 17673 个,占用了 1621352 个字节的内存,排在第一位。

那也就是说优化 String 节省内存空间是非常有必要的,如果是去优化一个使用频率没有 String 这么高的类库,就显得非常的鸡肋。

### 二、`byte[]` 为什么就能节省内存空间呢?

众所周知,char 类型的数据在 JVM 中是占用两个字节的,并且使用的是 UTF-8 编码,其值范围在 '\u0000'(0)和 '\uffff'(65,535)(包含)之间。



也就是说,使用 `char[]` 来表示 String 就导致了即使 String 中的字符只用一个字节就能表示,也得占用两个字节。

而实际开发中,单字节的字符使用频率仍然要高于双字节的。

当然了,仅仅将 `char[]` 优化为 `byte[]` 是不够的,还要配合 Latin-1 的编码方式,该编码方式是用单个字节来表示字符的,这样就比 UTF-8 编码节省了更多的空间。

换句话说,对于:

```java
String name = "jack";
```

这样的,使用 Latin-1 编码,占用 4 个字节就够了。

但对于:

```java
String name = "小二";
```

这种,木的办法,只能使用 UTF16 来编码。

针对 JDK 9 的 String 源码里,为了区别编码方式,追加了一个 coder 字段来区分。

```java
/**
* The identifier of the encoding used to encode the bytes in
* {@code value}. The supported values in this implementation are
*
* LATIN1
* UTF16
*
* @implNote This field is trusted by the VM, and is a subject to
* constant folding if String instance is constant. Overwriting this
* field after construction will cause problems.
*/
private final byte coder;
```

Java 会根据字符串的内容自动设置为相应的编码,要么 Latin-1 要么 UTF16。

也就是说,从 `char[]``byte[]`**中文是两个字节,纯英文是一个字节,在此之前呢,中文是两个字节,应为也是两个字节**

### 三、为什么用UTF-16而不用UTF-8呢?

在 UTF-8 中,0-127 号的字符用 1 个字节来表示,使用和 ASCII 相同的编码。只有 128 号及以上的字符才用 2 个、3 个或者 4 个字节来表示。

- 如果只有一个字节,那么最高的比特位为 0;
- 如果有多个字节,那么第一个字节从最高位开始,连续有几个比特位的值为 1,就使用几个字节编码,剩下的字节均以 10 开头。

具体的表现形式为:

- 0xxxxxxx:一个字节;
- 110xxxxx 10xxxxxx:两个字节编码形式(开始两个 1); - 1110xxxx 10xxxxxx 10xxxxxx:三字节编码形式(开始三个 1);
- 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx:四字节编码形式(开始四个 1)。

关于字符编码,我在《Java 程序员进阶之路》里曾讲到过,想要深入了解的小伙伴查看下面的链接🔗:

>https://tobebetterjavaer.com/basic-extra-meal/java-unicode.html
也就是说,UTF-8 是变长的,那对于 String 这种有随机访问方法的类来说,就很不方便。所谓的随机访问,就是charAt、subString这种方法,随便指定一个数字,String要能给出结果。如果字符串中的每个字符占用的内存是不定长的,那么进行随机访问的时候,就需要从头开始数每个字符的长度,才能找到你想要的字符。

那有小伙伴可能会问,UTF-16也是变长的呢?一个字符还可能占用 4 个字节呢?

的确,UTF-16 使用 2 个或者 4 个字节来存储字符。

- 对于 Unicode 编号范围在 0 ~ FFFF 之间的字符,UTF-16 使用两个字节存储。
- 对于 Unicode 编号范围在 10000 ~ 10FFFF 之间的字符,UTF-16 使用四个字节存储,具体来说就是:将字符编号的所有比特位分成两部分,较高的一些比特位用一个值介于 D800~DBFF 之间的双字节存储,较低的一些比特位(剩下的比特位)用一个值介于 DC00~DFFF 之间的双字节存储。

但是在 Java 中,一个字符(char)就是 2 个字节,占 4 个字节的字符,在 Java 里也是用两个 char 来存储的,而String的各种操作,都是以Java的字符(char)为单位的,charAt是取得第几个char,subString取的也是第几个到第几个char组成的子串,甚至length返回的都是char的个数。

所以UTF-16在Java的世界里,就可以视为一个定长的编码。

>参考链接:https://www.zhihu.com/question/447224628
6 changes: 4 additions & 2 deletions docs/home.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ title: Java程序员进阶之路
- [Java到底是值传递还是引用传递?](basic-extra-meal/pass-by-value.md)
- [Java不能实现真正泛型的原因是什么?](basic-extra-meal/true-generic.md)
- [详解Java中Comparable和Comparator的区别](basic-extra-meal/comparable-omparator.md)
- [jdk9为何要将String的底层实现由char[]改成了byte[]?](basic-extra-meal/jdk9-char-byte-string.md)
- [为什么JDK源码中,无限循环大多使用for(;;)而不是while(true)?](basic-extra-meal/jdk-while-for-wuxian-xunhuan.md)


## Java并发编程
Expand Down Expand Up @@ -269,11 +271,11 @@ title: Java程序员进阶之路

# 计算机基础


- **计算机基础包括操作系统、计算机网络、计算机组成原理、数据结构与算法等**。对于任何一名想要走得更远的 Java 后端开发来说,都是必须要花时间和精力去夯实的。
- 万丈高露平地起,勿在浮沙筑高台。


- [计算机操作系统知识点大梳理](https://mp.weixin.qq.com/s/G9ZqwEMxjrG5LbgYwM5ACQ)
- [计算机网络核心知识点大梳理](https://mp.weixin.qq.com/s/7EddtzpwIRvYfw34QE4zvw)

# 求职面试

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit bdc379f

Please sign in to comment.