forked from itwanger/toBeBetterJavaer
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
219 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)` 这样的死循环了。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file added
BIN
+342 KB
...basic-extra-meal/jdk9-char-byte-string-d826ce88-bbbe-47a3-a1a9-4dd86dd3632f.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.