11# StringTable
22
3- ## String的基本特性
3+ ## String 的基本特性
44
55- String:字符串,使用一对 "" 引起来表示
66 - String s1 = "mogublog" ; // 字面量的定义方式
1111
1212### 为什么 JDK 9 改变了结构
1313
14- String 类的当前实现将字符存储在 char 数组中,每个字符使用两个字节(16位 )。从许多不同的应用程序收集的数据表明,字符串是堆使用的主要组成部分,而且,大多数字符串对象只包含拉丁字符。这些字符只需要一个字节的存储空间,因此这些字符串对象的内部 char 数组中有一半的空间将不会使用。
14+ String 类的当前实现将字符存储在 char 数组中,每个字符使用两个字节(16 位 )。从许多不同的应用程序收集的数据表明,字符串是堆使用的主要组成部分,而且,大多数字符串对象只包含拉丁字符。这些字符只需要一个字节的存储空间,因此这些字符串对象的内部 char 数组中有一半的空间将不会使用。
1515
1616> Motivation
1717>
@@ -32,19 +32,19 @@ private final char value[];
3232private final byte [] value
3333```
3434
35- 同时基于 String 的数据结构,例如 StringBuffer 和 StringBuilder 也同样做了修改
35+ 同时基于 String 的数据结构,例如 StringBuffer 和 StringBuilder 也同样做了修改。
3636
37- ### String的不可变性
37+ ### String 的不可变性
3838
3939String:代表不可变的字符序列。简称:不可变性。
4040
4141- 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的 value 进行赋值。
4242- 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的 value 进行赋值。
4343- 当调用 String 的 replace() 方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的 value 进行赋值。
4444
45- 通过字面量的方式(区别于new )给一个字符串赋值,此时的字符串值声明在字符串常量池中。
45+ 通过字面量的方式(区别于 new )给一个字符串赋值,此时的字符串值声明在字符串常量池中。
4646
47- 代码
47+ 代码:
4848
4949``` java
5050public class StringTest1 {
@@ -86,7 +86,7 @@ public class StringTest1 {
8686}
8787```
8888
89- 运行结果
89+ 运行结果:
9090
9191```
9292true
@@ -131,37 +131,37 @@ best
131131
132132### 注意
133133
134- ** 字符串常量池是不会存储相同内容的字符串的**
134+ ** 字符串常量池是不会存储相同内容的字符串的。 **
135135
136- String 的 String Pool 是一个固定大小的 Hashtable ,默认值大小长度是1009 。如果放进 String Pool 的 String 非常多,就会造成 Hash 冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用 String.intern 时性能会大幅下降。
136+ String 的 String Pool 是一个固定大小的 Hashtable ,默认值大小长度是 1009 。如果放进 String Pool 的 String 非常多,就会造成 Hash 冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用 String.intern 时性能会大幅下降。
137137
138- 使用 -XX: StringTablesize 可设置 StringTable 的长度
138+ 使用 -XX: StringTablesize 可设置 StringTable 的长度。
139139
140- 在 JDK 6 中 StringTable 是固定的,就是 1009 的长度,所以如果常量池中的字符串过多就会导致效率下降很快。StringTablesize 设置没有要求
140+ 在 JDK 6 中 StringTable 是固定的,就是 1009 的长度,所以如果常量池中的字符串过多就会导致效率下降很快。StringTablesize 设置没有要求。
141141
142- 在 JDK 7 中,StringTable 的长度默认值是 60013 ,StringTablesize 设置没有要求
142+ 在 JDK 7 中,StringTable 的长度默认值是 60013 ,StringTablesize 设置没有要求。
143143
144- 在 JDK 8 中,StringTable 可以设置的最小值为 1009
144+ 在 JDK 8 中,StringTable 可以设置的最小值为 1009。
145145
146- ## String的内存分配
146+ ## String 的内存分配
147147
148- 在 Java 语言中有8种基本数据类型和一种比较特殊的类型 String 。这些类型为了使它们在运行过程中速度更快、更节省内存,都提供了一种常量池的概念。
148+ 在 Java 语言中有 8 种基本数据类型和一种比较特殊的类型 String 。这些类型为了使它们在运行过程中速度更快、更节省内存,都提供了一种常量池的概念。
149149
150- 常量池就类似一个 Java 系统级别提供的缓存。8种基本数据类型的常量池都是系统协调的 ,** String 类型的常量池比较特殊。它的主要使用方法有两种。**
150+ 常量池就类似一个 Java 系统级别提供的缓存。8 种基本数据类型的常量池都是系统协调的 ,** String 类型的常量池比较特殊。它的主要使用方法有两种。**
151151
152152- 直接使用双引号声明出来的 String 对象会直接存储在常量池中。
153153 - 比如:String info = "atguigu.com";
154154
155155- 如果不是用双引号声明的 String 对象,** 可以使用 String 提供的 intern() 方法** 。
156156
157- Java 6 及以前,字符串常量池存放在永久代
157+ Java 6 及以前,字符串常量池存放在永久代。
158158
159- Java 7 中 Oracle 的工程师对字符串池的逻辑做了很大的改变,即将** 字符串常量池的位置调整到 Java 堆内**
159+ Java 7 中 Oracle 的工程师对字符串池的逻辑做了很大的改变,即将** 字符串常量池的位置调整到 Java 堆内** 。
160160
161161- 所有的字符串都保存在堆(Heap)中,和其他普通对象一样,这样可以让你在进行调优应用时仅需要调整堆大小就可以了。
162162- 字符串常量池概念原本使用得比较多,但是这个改动使得我们有足够的理由让我们重新考虑在 Java 7 中使用 String.intern()。
163163
164- Java 8 元空间,字符串常量在堆
164+ Java 8 元空间,字符串常量在堆。
165165
166166![ image-20200711093546398] ( https://gitee.com/xlshi/blog_img/raw/master/img/20201012122412.png )
167167
@@ -264,7 +264,7 @@ s1 + s2 的执行细节
264264| | 线程安全 | 线程不安全 |
265265| | 多线程操作字符串 | 单线程操作字符串 |
266266
267- 注意,我们左右两边如果是变量的话,就是需要 new StringBuilder 进行拼接,但是如果使用的是 final 修饰,则是从常量池中获取。所以说拼接符号左右两边都是字符串常量或常量引用 则仍然使用编译器优化。也就是说被 final 修饰的变量,将会变成常量,类和方法将不能被继承
267+ 注意,我们左右两边如果是变量的话,就是需要 new StringBuilder 进行拼接,但是如果使用的是 final 修饰,则是从常量池中获取。所以说拼接符号左右两边都是字符串常量或常量引用 则仍然使用编译器优化。也就是说被 final 修饰的变量,将会变成常量,类和方法将不能被继承。
268268
269269- 在开发中,能够使用 final 的时候,建议使用上
270270
@@ -278,7 +278,7 @@ public static void test4() {
278278}
279279```
280280
281- 运行结果
281+ 运行结果:
282282
283283```
284284true
@@ -302,26 +302,26 @@ public static void method2(int highLevel) {
302302}
303303```
304304
305- 方法1耗费的时间 :4005ms,方法2消耗时间 :7ms
305+ 方法 1 耗费的时间 :4005ms,方法 2 消耗时间 :7ms
306306
307307结论:
308308
309309- 通过 StringBuilder 的 append() 方式添加字符串的效率,要远远高于 String 的字符串拼接方法
310310
311- 好处
311+ 好处:
312312
313313- StringBuilder 的 append 的方式,自始至终只创建一个 StringBuilder 的对象
314- - 对于字符串拼接的方式,还需要创建很多 StringBuilder 对象和调用 toString 时候创建的String 对象
314+ - 对于字符串拼接的方式,还需要创建很多 StringBuilder 对象和调用 toString 时候创建的 String 对象
315315- 内存中由于创建了较多的 StringBuilder 和 String 对象,内存占用过大,如果进行 GC 那么将会耗费更多的时间
316316
317- 改进的空间
317+ 改进的空间:
318318
319- - 我们使用的是 StringBuilder 的空参构造器,默认的字符串容量是16 ,然后将原来的字符串拷贝到新的字符串中, 我们也可以默认初始化更大的长度,减少扩容的次数
319+ - 我们使用的是 StringBuilder 的空参构造器,默认的字符串容量是 16 ,然后将原来的字符串拷贝到新的字符串中, 我们也可以默认初始化更大的长度,减少扩容的次数
320320- 因此在实际开发中,我们能够确定,前前后后需要添加的字符串不高于某个限定值,那么建议使用构造器创建一个阈值的长度
321321
322322## intern() 的使用
323323
324- intern() 是一个 native 方法,调用的是底层 C 的方法
324+ intern() 是一个 native 方法,调用的是底层 C 的方法。
325325
326326字符串池最初是空的,由 String 类私有地维护。在调用 intern() 方法时,如果池中已经包含了由 equals(object) 方法确定的与该字符串对象相等的字符串,则返回池中的字符串。否则,该字符串对象将被添加到池中,并返回对该字符串对象的引用。
327327
@@ -333,13 +333,13 @@ intern() 是一个 native 方法,调用的是底层 C 的方法
333333String myInfo = new string("I love atguigu").intern();
334334```
335335
336- 也就是说,如果在任意字符串上调用 String.intern() 方法,那么其返回结果所指向的那个类实例,必须和直接以常量形式出现的字符串实例完全相同。因此,下列表达式的值必定是 true
336+ 也就是说,如果在任意字符串上调用 String.intern() 方法,那么其返回结果所指向的那个类实例,必须和直接以常量形式出现的字符串实例完全相同。因此,下列表达式的值必定是 true。
337337
338338``` java
339339(" a" + " b" + " c" ). intern()== " abc"
340340```
341341
342- 通俗点讲,Interned String 就是确保字符串在内存里只有一份拷贝,这样可以节约内存空间,加快字符串操作任务的执行速度。注意,这个值会被存放在字符串内部池(String Intern Pool)
342+ 通俗点讲,Interned String 就是确保字符串在内存里只有一份拷贝,这样可以节约内存空间,加快字符串操作任务的执行速度。注意,这个值会被存放在字符串内部池。 (String Intern Pool)
343343
344344``` java
345345/**
@@ -373,7 +373,7 @@ public class StringIntern {
373373
374374### intern() 的空间效率测试
375375
376- 我们通过测试一下,使用了 intern 和不使用的时候,其实相差还挺多的
376+ 我们通过测试一下,使用了 intern 和不使用的时候,其实相差还挺多的。
377377
378378``` java
379379public class StringIntern2 {
@@ -517,7 +517,7 @@ String s2 = "1";
517517System . out. println(s == s2); // true
518518```
519519
520- 而对于下面的来说,因为 s3 变量记录的地址是 new String("11"),然后这段代码执行完以后,常量池中不存在 "11",这是 JDK 6 的关系,然后执行 s3.intern() 后,就会在常量池中生成 "11",最后 s4 用的就是 s3 的地址
520+ 而对于下面的来说,因为 s3 变量记录的地址是 new String("11"),然后这段代码执行完以后,常量池中不存在 "11",这是 JDK 6 的关系,然后执行 s3.intern() 后,就会在常量池中生成 "11",最后 s4 用的就是 s3 的地址。
521521
522522> 为什么最后输出的 s3 == s4 会为 false 呢?
523523>
@@ -539,8 +539,6 @@ String s4 = "11";
539539System . out. println(s3 == s4); // true
540540```
541541
542- ![ image-20200711145925091] ( images/image-20200711145925091.png )
543-
544542### 扩展
545543
546544``` java
@@ -550,7 +548,7 @@ s3.intern(); // 然后s3就会从常量池中找,发现有了,就什么事
550548System . out. println(s3 == s4);
551549```
552550
553- 我们将 s4 的位置向上移动一行,发现变化就会很大,最后得到的是 false
551+ 我们将 s4 的位置向上移动一行,发现变化就会很大,最后得到的是 false。
554552
555553### 总结
556554
@@ -582,14 +580,14 @@ true
582580false
583581```
584582
585- 在 JDK 8 中是
583+ 在 JDK 8 中是:
586584
587585```
588586true
589587true
590588```
591589
592- 针对下面这题,在 JDK 6 和 JDK 8 中表现的是一样的
590+ 针对下面这题,在 JDK 6 和 JDK 8 中表现的是一样的:
593591
594592![ image-20200711151433277] ( https://gitee.com/xlshi/blog_img/raw/master/img/20201012142321.png )
595593
@@ -613,24 +611,25 @@ public class StringGCTest {
613611
614612## G1 中的 String 去重操作
615613
616- 注意这里说的重复,指的是在堆中的数据,而不是常量池中的,因为常量池中的本身就不会重复
614+ 注意这里说的重复,指的是在堆中的数据,而不是常量池中的,因为常量池中的本身就不会重复。
617615
618616### 描述
619617
620618背景:对许多 Java 应用(有大的也有小的)做的测试得出以下结果:
621- - 堆存活数据集合里面 String 对象占了25 %
619+ - 堆存活数据集合里面 String 对象占了 25 %
622620- 堆存活数据集合里面重复的 String 对象有13.5%
623621
624- - String 对象的平均长度是45
622+ - String 对象的平均长度是 45
623+
624+ 许多大规模的 Java 应用的瓶颈在于内存,测试表明,在这些类型的应用里面,** Java 堆中存活的数据集合差不多 25% 是 String 对象** 。更进一步,这里面差不多一半 String 对象是重复的,重复的意思是说:
625625
626- 许多大规模的 Java 应用的瓶颈在于内存,测试表明,在这些类型的应用里面,** Java 堆中存活的数据集合差不多25%是 String 对象** 。更进一步,这里面差不多一半 String 对象是重复的,重复的意思是说:
627626string1.equals(string2) = true。堆上存在重复的 String 对象必然是一种内存的浪费。这个项目将在 G1 垃圾收集器中实现自动持续对重复的 String 对象进行去重,这样就能避免浪费内存。
628627
629628### 实现
630629
631630- 当垃圾收集器工作的时候,会访问堆上存活的对象。** 对每一个访问的对象都会检查是否是候选的要去重的 String 对象。**
632631- 如果是,把这个对象的一个引用插入到队列中等待后续的处理。一个去重的线程在后台运行,处理这个队列。处理队列的一个元素意味着从队列删除这个元素,然后尝试去重它引用的 String 对象。
633- - 使用一个 Hashtable 来记录所有的被 String 对象使用的不重复的 char 数组。当去重的时候,会查这个 Hashtable ,来看堆上是否已经存在一个一模一样的 char 数组。
632+ - 使用一个 Hashtable 来记录所有的被 String 对象使用的不重复的 char 数组。当去重的时候,会查这个 Hashtable,来看堆上是否已经存在一个一模一样的 char 数组。
634633- 如果存在,String 对象会被调整引用那个数组,释放对原来的数组的引用,最终会被垃圾收集器回收掉。
635634- 如果查找失败,char 数组会被插入到 Hashtable,这样以后的时候就可以共享这个数组了。
636635
0 commit comments