转载请注明链接:https://blog.csdn.net/feather_wch/article/details/82389184
本文包括如下内容:
- String的特点
- StringBuider的特点
- StringBuffer的特点
- 什么是字符串缓存的intern机制
- 什么是字符串排重
- 什么是intrinsic机制
- Java 9中String的改进
String、SrtingBuilder、StringBuffer详解
版本号:2018/9/5-1(16:16)
- String、SrtingBuilder、StringBuffer详解
- 问题汇总
- String
- 常量池
- intern
- AbstractStringBuilder
- StringBuffer
- StringBuilder
- 字符串缓存
- 字符串排重
- Intrinsic机制
- Java9 Compact String
- 知识扩展
- 编译和反编译
- 知识储备
- 参考资料
问题汇总
- 【☆】String包含哪些方面的知识?
- String、StringBuilder、StringBuffer的特点和区别。
- 字符串缓存的intern机制
- 字符串排重(JVM)
- Intrinsic机制
- JAVA9的Compat Strings
- String、StringBuffer、StringBuilder的区别
- String的特点
- String的immutabale特性有哪些优点呢?
- String的内部原理
- String比较的equals和==的区别
- String API的分类(12)
- String拼接的场景中是否一定要使用StringBuilder或者StringBuffer?
- String底层实现采用char导致的问题?
- AbstractStringBuilder是什么(2)
- AbstractStringBuilder的API分类(7)
- StringBuffer和StringBuilder的扩容问题(默认容量和性能损耗)
- StringBuffer的特点(3)
- StringBuffer的适用场景?
- StringBuilder的特点(4)
- StringBuilder的适用场景?
- 字符串重复的开销问题
- Java 6开始提供的intern()的作用
- Java 6中intern的严重缺陷
- Java 6以后的字符串缓存的优化
- 字符串缓存大小?如何修改?
- Java6以后intern()的副作用
- Oracle JDK 8u20后,推出了字符串排重的新特性
- JDK 8 的字符串排重的功能如何开启?(GC1)
- JVM内部的Intrinsic机制是干什么的?
- 字符串如何利用Intrinsic机制优化的?
- Java9中StringBuffer和StringBuilder底层的char[]数组都变更为byte[]数组
- Java9中的字符串引入了Compact Strings进行了哪些方面的修改?
- String是典型的immutable类,final修饰的类是否就是immutable的类?
- final的作用?(类、变量、方法)
- 如何去实现一个immutable类?
- 【☆】Java中对String的缓存机制?
intern()、JDK8JVM层的字符串排重
- getBytes()和new String()采用的什么编码方式?
- JDK中String的hash值为什么没有采用final修饰,也没有考虑hashcode()在多线程中会重复计算的问题?
- Java为了避免在系统中产生大量的String对象,引入了字符串常量池。
- 字符串常量池有什么用?
- 所有的String都是字符串常量池?
- 【☆】创建字符串对象的两种方式?
- 直接赋值:
String str = “bitch”;
- new方式创建
- 直接赋值:
- new方式创建的String对象是否会采用字符串常量池?
- 为什么StringBuilder、StringBuffer要给定初始值?
- String采用的不可变模式的优点?
- String常量池的优化机制?
- String str = new String(“AB”)是否还会涉及到常量池?如何验证?
- String str = new String(“AB”)会创建几个对象?
- 【☆】String str = “AB”会创建几个对象?
只会创建一个对象。
- 如何打印对象的地址?
- 如何打印出String对象的地址?
- 如何通过string调用Object的toString
- intern在JDK1.6和JDK1.7的区别?
String
1、String、StringBuffer、StringBuilder的区别
String | 特点 | 线程安全 | 性能 |
---|---|---|---|
String | 提供字符串相关功能。内部是final char[]数组。是典型的 类(final的class、final的字段)。 | 安全 | 性能低,内不会产生新的String对象。 |
StringBuffer | 用于解决字符串拼接产生的中间对象的问题。继承自 内部是char[]数组 | 安全 | 性能稍低,采用synchronized进行加锁。 |
StringBuilder | 继承自 内部是char[]数组 | 线程不安全 | 性能高 |
2、String的特点(4)
- String是典型的
类(不可变的):
immutable
修改String不会在原有的内存地址修改,而是重新指向一个新对象
- String用final修饰,不可以
:String本质是
继承
,所以
final的char[]数组
数组的内存地址不会被修改,而且
char[]
没有对外暴露修改
String
的方法。
char[]数组
- String是线程安全的:因为其是
类,实现
immutable
。
字符串常量池
- 频繁的
不建议使用
增删操作
String
- 操作不当会导致大量临时String对象。
3、String的immutabale特性有哪些优点呢?
- 性能安全
- 拷贝:不需要额外复制数据。
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
4、String的内部原理
内部是一个
String
:名称为
final修饰 char[]数组
value
本质上就是对
String的构造
赋初值的过程。
value数组
的其他API本质都是对
String
操作的过程。如:
value数组
是通过
substring
完成的,最终会
数组的复制
新建的
返回
,不会影响到原有的数组。
String
5、String比较的equals和==的区别
- astr.equals(bstr) 比较对象是否相等
- == 仅仅是比较首地址
6、String API的分类(12)
- 构造方法
- 字符串长度:length
- 字符串某一位置:charAt
- 提取子串:substring
- 字符串比较:compareTo、compareToIgnore、equals、equalsIgnoreCase
- 字符串的连接:concat
- 字符串的查找:indexOf、lastIndexOf
- 字符串的替换:replace
- 前后空格的移除:trim
- 起始字符串/终止字符串是否与给定字符串相同:startsWith、endWith
- 是否包含字符串:contains
- 基本类型转换为字符串类型:valueOf
7、String拼接的场景中是否一定要使用StringBuilder或者StringBuffer?
不是!
1. String的可读性更好;StringBuilder的可读性差。
1. JDK 8开始,
会默认采用
“a”+“b”+“c”
StringBuilder
实现(反编译后可以看见)
1. JDK 9中,提供了更加统一的字符串操作优化,提供了
作为同一入口。
StringConcatFactory
8、String底层实现采用char导致的问题?
- char是两个
大小,拉丁语系语言的字符不需要这么宽的char。
bytes
- char的无区别实现,会导致浪费。
- Java9中采用byte[]数组来实现。
常量池
9、Java为了避免在系统中产生大量的String对象,引入了字符串常量池。
- 创建字符串时,会到常量池中检查是否有相同的字符串对象。
- 如果有,就直接返回其引用。
- 如果没有,会创建字符串对象,将其放入常量池,并且返回引用。
10、字符串常量池有什么用?
Java为了避免在系统中产生大量的String对象
11、所有的String都是字符串常量池?
错误!
12、直接赋值的String对象才会放入字符串常量池:
String str = “bitch”;
13、new方式创建的String对象不妨放入常量池:
String str = new String("Hello");
创建String对象,不会去检查常量池
关键字new
- new创建String会直接在
或者
堆区
创建一个新的对象,也不会放入池中。
栈区
14、
String str = new String("AB");
是否还会涉及到常量池?如何验证?
15、
String str = new String("AB");
会创建几个对象?
两个对象!
1. “AB”会先创建String对象,内容为”AB”。再用该string去创建str。
1. 建议使用
, 只会创建一个对象。
String str = "AB"
16、String的直接赋值得到的对象和new创建的对象是否相同?
String s1 = "hello world!";
String s2 = "hello world!";
String s3 = new String(s1);
String s4 = new String("hello world!");
System.out.println("s1 == s2 : " + (s1 == s2));
System.out.println("s1 == s3 : " + (s1 == s3));
System.out.println("s3 == s4 : " + (s3 == s4));
- s1和s2是同一个对象(常量池复用)。
- s3,s4都是创建的额外的对象。
intern
17、JVM常量池位置的变更
- JDK1.6,JVM运行时数据区的
中,有一个常量池。
方法区
- JDK1.6以后,常量池位于
堆空间
- 常量池的位置会影响intern的效果。
18、intern在JDK1.6和JDK1.7的区别
- JDK1.6: intern()方法会把首次遇到的字符串实例复制到永久代(可以就当是方法区)中,返回的也是永久代中这个字符串实例的引用。
- JDK1.7: intern()实现不会复制实例,只是在常量池中记录首次出现的实例的引用。
//JDK1.6
String s3 = new String("1") + new String("1");
// 1. 将s3(“11”)的复制品,放到永久带中。
s3.intern();
// 2. s4 = s3的复制品
String s4 = "11";
// 3. 一定不相等,结果为false
System.out.println(s3 == s4);
//JDK1.7
String s3 = new String("1") + new String("1");
// 1. 将s3(“11”)的引用记录在常量池中。
s3.intern();
// 2. s4 = s3的引用
String s4 = "11";
// 3. 一定相等,结果为true
System.out.println(s3 == s4);
19、String的intern方法的实例
1-如下情况有什么结果?(JDK7)
// 1. 生成常量池中的Hello、World!生成位于堆空间中“HelloWorld!”的对象
String str1 = new String("Hello")+ new String("World!");
// 2、 str1.intern(),在常量池中记录首次出现的实例的引用。也就是str1的引用,因此str1.intern() = str1,和str1相比,肯定一样。
System.out.println(str1.intern() == str1);
// 3、“HelloWorld!”会去常量池找,找到了str1的引用
System.out.println(str1 == "HelloWorld!");
true
true
2-如下情况有什么结果?(JDK7)
// 1. 现在常量池中生成"HelloWorld!"
String str2 = "HelloWorld!";
// 2. 生成常量池中的Hello、World!生成位于堆空间中“HelloWorld!”的对象,也就是str1
String str1 = new String("Hello")+ new String("World!");
// 3、 str1.intern(),在常量池中记录首次出现的实例的引用。也就是"HelloWorld!"的引用,所以str1.intern()=常量"HelloWorld!" 不等于 str1(堆空间对象)
System.out.println(str1.intern() == str1);
// 4、“HelloWorld!”会去常量池找,找到了,返回常量池的引用。因此和str1一定不相等
System.out.println(str1 == "HelloWorld!");
false
false
3-如下情况有什么结果?(JDK7)
// 1、常量"1"和堆空间对象“1”
String s = new String("1");
// 2、在常量池中记录首次出现的实例的引用。也就是常量“1”的引用
s.intern();
// 3、从常量池中获取到常量“1”的引用。
String s2 = "1";
// 4、s2=常量“1”, s=对象“1”,一定不相等
System.out.println(s == s2);
4-如下情况有什么结果?(JDK7)
// 1、生成常量“1”,s3=堆空间对象“11”
String s3 = new String("1") + new String("1");
// 2、在常量池中记录首次出现的实例的引用。也就是s3的引用。
s3.intern();
// 3、去常量池找“11”,获取到s3的引用
String s4 = "11";
// 4、s4和s3肯定相同 = true
System.out.println(s3 == s4);
AbstractStringBuilder
1、AbstractStringBuilder是什么(2)
和
StringBuider
都是继承自
StringBuffer
AbstractStringBuilder
,
内部是一个char[]数组
修饰符。
没有final
2、AbstractStringBuilder的API分类(7)
- 扩容:ensureCapacity、newCapacity、hugeCapacity—
扩容方式:以前大小 * 2 + 2
- 追加:append(容量不够就扩容,容量够就追加到
的最后)
value数组
- 插入字符串:insert
- 删除字符串:delete(删除[start, end]之间的字符,通过复制的方式实现)
- 提取子串:substring(new一个新String)
- 字符串的替换:replace
- 字符串某位置的字符:charAt
3、StringBuffer和StringBuilder的扩容问题
- 默认容量是
16
- 如果知道需要的容量需要
手动设置
- 频繁扩容会导致严重的性能损耗,会涉及到创建新数组和
的数据复制,会产生的性能问题。
arraycopy
StringBuffer
4、StringBuffer的特点(3)
- 继承自
AbstractStringBuilder
默认是创建
构造
的
容量为16
AbstractStringBuilder
- 线程安全:
的所有方法,都是直接使用父类的方法,并都是用
StringBuffer
进行加锁保护。
synchronized
- 性能比StringBuilder低。
5、StringBuffer的适用场景?
:适用于
线程安全
多线程
- Http参数拼接、xml解析
StringBuilder
6、StringBuilder的特点(4)
- 继承自
AbstractStringBuilder
- Java 1.5中新增
- 初始容量16.
:
非线程安全
的所有方法,都是直接使用父类的方法,但是没有使用
StringBuilder
进行加锁保护。
synchronized
- 性能最高,在单线程中推荐使用。
7、StringBuilder的适用场景?
:适用于
非线程安全
单线程
- SQL语句拼接
- JSON封装
- XML解析
字符串缓存
1、字符串重复的开销问题
- 经过分析,对象中平均25%都是字符串
- 字符串的50%都是重复的字符串。
- 如果进行优化,能有效降低内存消耗、对象创建开销。
2、Java 6开始提供的intern()
- 一种显式的排重机制。
能提示JVM把相应的字符串缓存起来,以备重复使用。
string.intern();
- 调用该方法时,如果常量池中有String和该String相等,则直接返回常量池中的字符串。(String的equals进行比较)
- 如果常量池中没有该字符串,则将String对象,添加到常量池中,并且返回引用。
3、Java 6中intern的严重缺陷
- Java 6中并不推荐使用
intern
- 将缓存的字符串存储到了
里,也就是臭名昭著的
PermGen
永久代
- FullGC以外的垃圾回收都不会涉及到
永久代
- 使用不当,会导致
问题
OOM
4、Java 6以后的字符串缓存的优化
- 后续采用了
来替代
堆
来存储
永久代
字符串
- Java 8中采用
MetaSpace(元数据区)
5、字符串缓存大小?
- 随着发展,已经从最初的
提升到了
1009
60013
可以查看
-XX:+PrintStringTableStatistics
具体数值
能手动修改,
-XX:StringTableSize=XXX
不建议修改
6、intern()的副作用
- 需要开发者显式调用,使用不方便
- 很难保证效率,因为开发者难以清楚
,最终可能导致代码的污染。
字符串的重复率
字符串排重
7、Oracle JDK 8u20后,推出了字符串排重的新特性
下的字符串排重
G1 GC
- 做法:将相同数据的字符串指向同一份数据
- 这种方法是JVM底层的改变,并不需要Java类库做什么改变。
8、JDK 8 的字符串排重的功能默认是关闭的
1-需要指定使用G1 GC
-XX:+UseG1GC
2-开启字符串排重功能
-XX:+UseStringDeduplication
Intrinsic机制
9、JVM内部的Intrinsic机制是干什么的?
- 是一种利用native方法,hard-coded(硬编码)的逻辑,
- 一种特殊的内联(intrinsic-内在的)。
- 很多优化还是需要直接使用特定的CPU指令。
10、字符串如何利用Intrinsic机制优化的?
- 字符串的特殊操作运行的都是特殊优化的
本地代码
- 而不是去运行
生成的
Java代码
字节码
Java9 Compact String
1、Java9中StringBuffer和StringBuilder底层的char[]数组都变更为byte[]数组
2、Java9中的字符串引入了Compact Strings的设计
- String不再使用
char数组
- String采用
实现,并且加上标识编码
byte数组
coder
- 将String相关的操作类都进行了修改。
- String相关的
类都进行了重写
intrinsic
知识扩展
1、String是典型的immutable类,final修饰的类是否就是immutable的类?
不是
2、final的作用?(类、变量、方法)
3、如何去实现一个immutable类?
4、Java中String的缓存机制。
5、getBytes()和new String()采用的什么编码方式?
- 会先从JVM参数中找有没有指定的file.encoding参数
- 没有找到,会采用操作系统环境的编码方式。
- 建议:getBytes/String相关业务需要指定
,否则因为其不确定性,可能会导致问题。
编码方式
6、JDK中String的hash值为什么没有采用final修饰,也没有考虑同步问题?
- String的hash值没有采用final修饰,其计算方式是在第一次调用
时生成。
hashcode()
- hashcode()方法没有加锁,没有采用
修饰。在多线程中可能会出现
valatile
多次计算。
hash值
- 虽然运算结果是一致的(同一个对象调用hashcode方法,结果肯定是一致的),为什么不去优化这种会多次计算的情况。
- 这种优化会导致在通用场景变成
,volatile有明显开销,但是冲突并不多见。因此不需要这种优化。
持续的成本
public int hashCode() {
int h = hash;
// 1. h == 0, 决定了在单线程中只会计算一次。
if (h == && value.length > ) {
char val[] = value;
for (int i = ; i < value.length; i++) {
h = * h + val[i];
}
hash = h;
}
return h;
}
7、为什么StringBuilder、StringBuffer要给定初始值?
- 如果采用默认容量16,在字符串很长的情况下,会导致多次扩容。
- 扩容时的创建新数组,arrayCopy的复制都会影响性能。
- 建议在使用时,预估会用到的字符串长度,合理的设定容量。
8、String采用的不可变模式的优点?
- 不可变模式是一种优质的设计模式。
- 能提高多线程程序性能。
- 能降低多线程程序的复杂度。
9、String常量池的优化
- 当两个String对象拥有相同内容是,只会引用常量池中同一个内容。
10、如何打印对象的地址?
1-直接打印对象: 会显示其地址(在@后面),十六进制。如果对象重写了toString()就不会打印出内存地址。
2-对象的toString(): 会显示其地址(在@后面),十六进制。如果对象重写了toString()就不会打印出内存地址。
2-对象的hashCode(): 等于内存地址(十进制)。如果对象重写了hashCode(),会导致数值和内存地址不相关的了。
11、如何打印出String对象的地址?
1-使用可以计算出任何对象的hashCode(根据内存地址得到),就算重写过
System.identityHashCode(s1)
也不会影响
hashCode()
String s1 = "Hello World for Feather!";
String s2 = "Hello World for Feather!";
String s3 = new String(s2);
String s4 = new String("Hello World for Feather!");
System.out.println(System.identityHashCode(s1));
System.out.println(System.identityHashCode(s2));
System.out.println(System.identityHashCode(s3));
System.out.println(System.identityHashCode(s4));
\\输出结果
s1
s2
s3
s4
2-用==来比较是否相等,但是无法比较。
12、如何通过string调用Object的toString?
不可以
编译和反编译
1、Java代码的编译和反编译
- javac: 编译
- javap: 反编译
javac Main.java
javap -v Main.class
知识储备
1、JEP 193: Variable Handles 是什么?
Variable Handles的API主要是用来取代以及
java.util.concurrent.atomic包
的功能。
sun.misc.Unsafe类
参考资料
- Java技术——你真的了解String类的intern()方法吗