一、String的基本特性
● 字符串常量池中是不会存储相同内容的字符串的。
➢String的String Pool是一个固定大小的Hashtable,默认值大小长度是1009。如果放进String Pool的String非常多, 就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String. intern时性能会大幅下降。
➢使用-xX:StringTableSize可设置StringTable的长度
➢在jdk6中StringTable是固定的,就是1009的长度,所以如果常量池中的字符串过多就会导致效率下降很快。StringTableSize设置没有要求
➢在jdk7中,StringTable的长度默认值是60013, StringTableSize设置没有要求
➢Jdk8开始,设 置StringTable的长度的话,1009是可设置的最小值。
二、String在不同的jdk版本中的区别
Java 6及以前,字符串常量池存放在永久代。
Java 7中Oracle的工程师对字符串池的逻辑做了很大的改变,即将字符串常量池的位置调整到Java堆内。
➢所有的字符串都保存在堆(Heap)中,和其他普通对象一样,这样可以让你在进行调优应用时仅需要调整堆大小就可以了。
➢字符串常量池概念原本使用得比较多,但是这个改动使得我们有足够的理由让我们重新考虑在Java 7中使用String. intern()。
Java8元空间,字符串常量在堆
三、字符串拼接操作
1.常量与常量的拼接结果在常量池,原理是编译期优化
2.常量池中不会存在相同内容的常量。
3.只要其中有一个是变量,结果就在堆中。变量拼接的原理是StringBuilder
4.如果拼接的结果调用intern()方法,则主动将常量池中还没有的字符串对象放入池中,并返回此对象地址。
Java语言规范里要求完全相同的字符串字面量,应该包含同样的Unicode。字符序列(包含同一份码点序列的常量),并且必须是指向同一个String类实例。
小总结:
1、字符串拼接操作不一定使用的是stringBuilder,如梦拼接符号左右两边都是字符串常量引用,刚仍然使用编译期优化,即非StringBuilder的方式。
2、针对final修改类、方法、基本数据类型、引用数据类型的量的结构时,能使用final时的时候尽量使用上。
3、通过StringBuilder的append()的方法添加字符串的效率要远高于使用String的字符串拼接方式。
详情:
①、StringBuilder的append()的方式,自始至终只创建了一个StringBUilder对象。使用String的字符串拼接方式,创建过多个StringBuilder和String对象。
②、使用String的字符串拼接方式,内存于由于创建了较多的StringBuilder和string对象,内存占用过大,如果进行GC,需要花费额外的时间。
改进的空间:
在实际开发中,如果基本确定要前后添加的字符串长度不高于某个限定值highlevel的情况下,建议使用构造函数:
StringBuilder s = new StringBuilder(highlevel);
四、intern()的使用
● 如果不是用双引号声明的String对象,可以使用String提供的intern方法: intern方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中。
● 比如: String myInfo = new String(“I love atguigu”) .intern() ;
也就是说,如果在任意字符串上调用String. intern方法,那么其返回结果所指向的。那个类实例,必须和直接以常量形式出现的字符串实例完全相同。因此,下列表达式的值必定是true:
(“a” + “b” + “c”) . intern() == “abc”
通俗点讲,Interned String就是确保字符串在内存里只有一-份拷贝,这样可以节约内存空间,加快字符串操作任务的执行速度。注意,这个值会被存放在字符串内部池(String Intern Pool)。
如何保证变量s指向的是字符串常量池中的数据呢?
有两种方式:
方式一: string s = “shkstart”;//字面量定义的方式
方式二: 调用intern()
string S = new String(“shkstart”).intern();
String s = new StringBuilder( “shkstart”). tostring(). intern();
五、两道面试题目
①、new String(“ab”) 会创建几个对象?看字节码,就知道是两个。
一个对象是: new关键字在堆空间创建的
另一个对象是:字符串常量池中的对象。字节码指令: ldc
②、new String(“a”) + new string(“b”)呢?
对象1: new StringBuilder()
对象2:new String(“a”)
对象3:常量池中的"a"
对象4: new String(“b”)
对象5:常量池 中的"b”
深入剖析: stringBuilder 的tostring():
对象6 : new string( “ab”)
强调一下,tostring()的调用,在字符串常量池中,没有生成"ab’
六、总结String的intern()的使用:
● jdk1.6中,将这个字符串对象尝试放入串池。
➢如果串池中有,则并不会放入。返回已有的串池中的对象的地址
➢如果没有,会把此对象复制一份,放入串池,并返回串池中的对象地址
● Jdk1.7起,将这个字符串对象尝试放入串池。
➢如果串池中有,则并不会放入。返回已有的串池中的对象的地址
➢如果没有,则会把对象的引用地址复制- -份,放入串池,并返回串池中的引用地址
七、G1的String去重操作
● 背景:对许多Java应用(有大的也有小的)做的测试得出以下结果:
➢堆存活数据集合里面string对象占了25%
➢堆存活数据集合里面重复的String对象有13.5%
➢String对象的平均长度是45%
● 许多大规模的Java应用的瓶颈在于内存,测试表明,在这些类型的应用里面,Java堆中存活的数据集合差不多25号是string对象。更进一步,这里面差不多一半string对象是重复的,重复的意思是说:string1.equals(string2)=true。堆上存在重复的String对象必然是一种内存的浪费。这个项目将在G1垃圾收集器中实现自动持续对重复的String对象进行去重,这样就能避免浪费内存。
这章到这里就结束了,如果觉得学的还不过瘾的话,请移步到:https://www.processon.com/view/6076a7ffe401fd2d66980f5b
在推荐一款全栈学习地十:https://www.processon.com/view/60504b5ff346fb348a93b4fa
本章内容总结来源于以下微信小程序: