一、存储结构
jdk8及以前String底层使用char[],一个char是两个字节,jdk9开始改用byte[]加上编码标记节约空间。
jdk9官网提供的String修改说明:http://openjdk.java.net/jeps/254
修改动机:
二、不可变性
1、当对字符串重新赋值时,需要重写指定内存区域赋值
2、当对现有字符串进行连接操作时,也需要重新指定内存区域。
3、调用String的replace方法时修改指定字符串或者字符时,也需要重新指定内存区域赋值。
4、通过字面量方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。
三、字符串常量池是不会存储相同内容的字符串的。
1、String的String Pool是一个固定大小的HashTable,默认值大小长度是1009,如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是调用String.intern时性能会大幅下降。
2、-XX:StringTableSize可设置StringTable的长度。
3、在jdk6中StringTable是固定的,就是1009的长度,如果常量池中的字符串过多就会导致效率下降很快。
4、在jdk7中,StringTable的长度默认是60013,jdk8开始StringTable的1009是可设置的最小值,默认值是60013。
四、String的内存分配
jdk6及以前,字符串常量池存放在永久代。jdk7开始字符串常量池的位置调整到java堆中。
jdk8去除了永久代,开始使用元空间,字符串常量池存放于堆中。
五、字符串优化
1、常量和常量的拼接结果在常量池中,直接使用常量池的字面量,原理是编译器优化。
2、字符串拼接中,出现常量和字符串变量的拼接,那个字符串变量相当于在堆空间中new StringBuilder(),具体的内容为拼接的结果。
3、str.inter(),判断字符串常量池中是否存在str,如果存在,则返回常量池中str的地址,如果不存在则在常量池中加载一份str,同时返回str的地址。
例:观察s4=s1+s2
s4的编译执行过程
(1)生成StringBuilder()对象,在jdk5.0之前使用StringBuffer()对象
(2)调用StringBuilder对象的append方法加入常量池中"a"
(3)调用StringBuilder对象的append方法加入常量池中"b"
(4)调用StringBuilder对象的toString方法
注意:如果变量s1和s2前加入了final修饰符,此时编译器会在编译阶段做优化,不会生成StringBuilder对象,直接形成从常量池中找"ab"返回给s4,当然s3==s4也是true。
六、intern
str.inter(),判断字符串常量池中是否存在str,如果存在,则返回常量池中str的地址,如果不存在则在常量池中加载一份str,同时返回str的地址。
1、jdk1.6,将字符串str尝试放入常量池
(1)如果常量池中有,则并不会放入,返回已有常量池中的对象的地址
(2)如果没有,会把此对象复制一份,放入常量池,并返回常量池的对象地址。
2、jdk1.7及以后版本,将字符串str尝试放入常量池
(1)如果常量池中有,则不会放入,返回已有的常量池中的对象地址
(2)如果没有,则把对象的引用地址复制一份,放入常量池中,并返回常量池中引用的地址。
七、扩展知识
1、new String("ab");会产生几个对象
一共有2个对象,在第一个堆中new String生成,第二个是堆中常量池中的ab对象
2、new String("a")+new String("b");会产生多少个对象
一共会产生6个对象
(1)有拼接操作,会生成StringBuilder对象
(2)new String("a")对象
(3)常量池中的"a"对象
(4)new String("b")对象
(5)常量池中"b"对象
(6)拼接完后会调用StringBuilder对象的toString()方法,toString方法内会new String()对象
@Override public String toString() { // Create a copy, don't share the array return new String(value, 0, count); }
3、intern在jdk版本中的区别
public static void main(String[] args) { String s=new String("1"); s.intern();//调用intern()前,字符串常量池中已经存在"1"了。 String s2="1"; System.out.println(s==s2);//jdk6:false,jdk7/8:false String s3=new String("1")+new String("1"); s3.intern(); String s4="11"; System.out.println(s3==s4);//jdk6:false,jdk7/8:true }
jdk6:
jdk7/8:
注意:s4和s3.intern()互换
String s3=new String("1")+new String("1"); String s4="11"; s3.intern(); System.out.println(s3==s4);//jdk6:false,jdk7/8:false
八、G1对String去重(默认是不开启的)
java工程师测试了大部分java应用出现如下现象
1、堆中存活的String对象占25%
2、堆中存活的重复的String对象有13.5%
3、String对象的平均长度是45
堆上存在重复的String对象势必造成内存的浪费,G1垃圾回收器会在自动持续的去重String对象,避免堆内存浪费。
G1去重的实现步骤: