文章目录
第 12 章 StringTable
1、String 的基本特性
1.1、String 概述
- 第 12 章 StringTable
- 1、String 的基本特性
- 1.1、String 概述
- 1.2、String 的基本特征
- 1.3、String 的底层结构
- 2、String 的内存分配
- 2.1、String 内存分配演进过程
- 2.2、为什么要调整 String 位置 (从永久代调到堆)
- 3、String 的基本操作
- 4、字符串拼接操作
- 4.1、符串拼接操作的结论
- 4.2、字符串拼接的底层细节
- 5、intern() 的使用
- 5.1、intern() 方法的说明
- 5.2、new String() 的说明
- 5.3、有点难的面试题
- 5.4、intern() 方法的总结
- 5.5、intern() 方法的练习
- 5.6、intern() 方法效率测试
- 6、StringTable 的垃圾回收
- 7、G1 中的 String 去重操作
String 的概述
- String:字符串,使用一对
“”
引起来表示String s1 = "guizy" ; // 字面量的定义方式 String s2 = new String("moxi"); // new 对象的方式
- String声明为
final
的,不可被继承 - String实现了Serializable接口:表示字符串是
支持序列化
的。实现了Comparable接口
:表示String可以比较大小 - String在jdk8及以前内部定义了
final char[] value
用于存储字符串数据。JDK9时改为byte[]
为什么 JDK9 改变了 String 的结构
官方文档
http://openjdk.java.net/jeps/254
为什么改为 byte[] 存储?
在许多不同的应用程序收集数据表明: 字符串是堆使用的主要组成部分
, 大多数字符串对象只包含拉丁字符
, 只需要用到一个字节的空间
, 之前用char[]
数组, 站2个字节的空间
, 实际用到只有一个字节。
- byte[] 数组 + 编码标志位的方式
- String类的当前实现将字符存储在
char数组
中,每个字符使用两个字节(16位)。 - 从许多不同的应用程序收集的数据表明,
字符串是堆使用的主要组成部分
,而且大多数字符串对象只包含拉丁字符
。这些字符只需要一个字节的存储空间,因此这些字符串对象的内部char数组中有一半的空间将不会使用。 之前 String 类使用 UTF-16 的 char[] 数组存储
,现在改为 byte[] 数组 外加一个编码标志位存储
,该编码标志将指定 String 类中 byte[] 数组的编码方式- 结论:String再也不用char[] 来存储了,改成了byte [] 加上编码标记,节约了一些空间
- 同时基于
String
的数据结构,例如StringBuffer
和StringBuilder
也同样做了修改
// 之前
private final char value[];
// 之后
private final byte[] value
1.2、String 的基本特征
String 的基本特征
String:代表不可变的字符序列
。简称:不可变性。
- 当
对字符串重新赋值
时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。 - 当
对现有的字符串进行连接操作
时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。 - 当
调用String的replace()方法修改指定字符或字符串
时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
- 通过
字面量的方式(区别于new)给一个字符串赋值
,此时的字符串值声明在字符串常量池中。
当对字符串重新赋值
时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
代码
@Test
public void test1() {
String s1 = "abc";//字面量定义的方式,"abc"存储在字符串常量池中
String s2 = "abc";
System.out.println(s1 == s2);// true, 表示指向字符串常量池中的同一地址
s1 = "hello";
System.out.println(s1 == s2);//判断地址:false, 表明s1从新开辟了一个存储空间
System.out.println(s1);//hello
System.out.println(s2);//abc
}
- 字节码指令
- 取字符串 “abc” 时,使用的是同一个符号引用:
#2
- 取字符串 “hello” 时,使用的是另一个符号引用:
#3
- 取字符串 “abc” 时,使用的是同一个符号引用:
0 ldc #2
2 astore_1
3 ldc #2
5 astore_2
6 ldc #3
8 astore_1
9 getstatic #4
12 aload_1
13 aload_2
14 if_acmpne 21 (+7)
17 iconst_1
18 goto 22 (+4)
21 iconst_0
22 invokevirtual #5
25 getstatic #4
28 aload_1
29 invokevirtual #6
32 getstatic #4
35 aload_2
36 invokevirtual #6
39 return
-
当对现有的字符串进行
连接操作
时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值 -
代码
@Test
public void test2() {
String s1 = "abc";
String s2 = "abc";
s2 += "def";
System.out.println(s2);//abcdef
System.out.println(s1);//abc
}
- 字节码指令:
拼接操作(+)
通过StringBuilder 的 append() 方法
完成
0 ldc #2
2 astore_1
3 ldc #2
5 astore_2
6 new #7
9 dup
10 invokespecial #8
13 aload_2
14 invokevirtual #9
17 ldc #10
19 invokevirtual #9
22 invokevirtual #11
25 astore_2
26 getstatic #4
29 aload_2
30 invokevirtual #6
33 getstatic #4
36 aload_1
37 invokevirtual #6
40 return
- 当
调用string的replace()方法修改指定字符或字符串
时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值
@Test
public void test3() {
String s1 = "abc";
String s2 = s1.replace('a', 'm');
System.out.println(s1);//abc
System.out.println(s2);//mbc
}
来看看 replace() 方法的源码
- new String(buf, true); 后,
返回新的 String 对象
public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = value.length;
int i = -1;
char[] val = value; /* avoid getfield opcode */
while (++i
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?