转载请注明链接: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包含哪些方面的知识?
- 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[]数组。是典型的immutable 类(final的class、final的字段)。 | 安全 | 性能低,内不会产生新的String对象。 |
StringBuffer | 用于解决字符串拼接产生的中间对象的问题。继承自AbstractStringBuilder 内部是char[]数组 | 安全 | 性能稍低,采用synchronized进行加锁。 |
StringBuilder | 继承自AbstractStringBuilder 内部是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数组
赋初值的过程。String
的其他API本质都是对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是两个
bytes
大小,拉丁语系语言的字符不需要这么宽的char。- char的无区别实现,会导致浪费。
- Java9中采用byte[]数组来实现。
常量池
9、Java为了避免在系统中产生大量的String对象,引入了字符串常量池。
- 创建字符串时,会到常量池中检查是否有相同的字符串对象。
- 如果有,就直接返回其引用。
- 如果没有,会创建字符串对象,将其放入常量池,并且返回引用。
10、字符串常量池有什么用?
Java为了避免在系统中产生大量的String对象
11、所有的String都是字符串常量池?
错误!
12、直接赋值的String对象才会放入字符串常量池:String str = “bitch”;
13、new方式创建的String对象不妨放入常量池:String str = new String("Hello");
关键字new
创建String对象,不会去检查常量池- 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);
false
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);
true
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()
- 一种显式的排重机制。
string.intern();
能提示JVM把相应的字符串缓存起来,以备重复使用。- 调用该方法时,如果常量池中有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 == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
7、为什么StringBuilder、StringBuffer要给定初始值?
- 如果采用默认容量16,在字符串很长的情况下,会导致多次扩容。
- 扩容时的创建新数组,arrayCopy的复制都会影响性能。
- 建议在使用时,预估会用到的字符串长度,合理的设定容量。
8、String采用的不可变模式的优点?
- 不可变模式是一种优质的设计模式。
- 能提高多线程程序性能。
- 能降低多线程程序的复杂度。
9、String常量池的优化
- 当两个String对象拥有相同内容是,只会引用常量池中同一个内容。
10、如何打印对象的地址?
1-直接打印对象: 会显示其地址(在@后面),十六进制。如果对象重写了toString()就不会打印出内存地址。
System.out.println(object); // java.lang.Object@4554617c
2-对象的toString(): 会显示其地址(在@后面),十六进制。如果对象重写了toString()就不会打印出内存地址。
System.out.println(object.toString()); //java.lang.Object@4554617c
2-对象的hashCode(): 等于内存地址(十进制)。如果对象重写了hashCode(),会导致数值和内存地址不相关的了。
System.out.println(object.hashCode()); //1163157884
11、如何打印出String对象的地址?
1-使用
System.identityHashCode(s1)
可以计算出任何对象的hashCode(根据内存地址得到),就算重写过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 1163157884
s2 1163157884
s3 1956725890
s4 356573597
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类
的功能。