前言研究表明Java堆中对象占最大比重的是字符串对象,因此了解字符串知识非常重要,本文主要针对字符串常量池。Java中的字符串常量池是Java堆中专门用来存放字符串的存储区。它的实现是为了提高字符串操作的性能和节省内存。它也被称为StringInternPool或StringConstantPool。所以让我看看发生了什么。理解字符串常量池当你从一个类中写一个字符串字面量时,JVM会首先检查该字符串是否已经存在于字符串常量池中,如果存在,JVM将返回对现有字符串对象的引用,而不是创建一个新对象。让我们通过一个例子更好地理解。例如下面的代码:Strings1="HarryPotter";Strings2="TheLordoftheRings";Strings3="HarryPotter";在这段代码中,JVM会创建一个值为“HarryPotter”的字符String对象,并将其存储在字符串常量池中。s1和s3都是对这个单一字符串对象的引用。如果s2的字符串内容“指环王”在池中不存在,则在字符串池中生成一个新的字符串对象。创建字符串的两种方法在Java编程语言中有两种创建字符串的方法。第一种方式是使用StringLiteral,另一种方式是使用new关键字。他们创建的字符串对象都是在常量池中吗?创建字符串文字Strings1="HarryPotter";Strings2="TheLordoftheRings";Strings3="HarryPotter";new关键字创建Strings4=newString("HarryPotter");Strings5=newString("指环王");让我们比较一下它们是否引用同一个对象:s1==s3//trues1==s4//falses2==s5//false使用==运算符比较两个对象时,它比较的是内存中的地址。正如您在上面的图片和示例中看到的,每当我们使用new运算符创建一个字符串时,它都会在Java堆中创建一个新的字符串对象,并且不会检查该对象是否在池中的字符串常量中。那么我现在有个疑问,如果是字符串拼接的情况呢?字符串拼接的方法上面已经说的很清楚了。通过直接使用字面量,即引号和使用new关键字创建字符串,将它们创建的字符串对象存放在堆的不同地方。那么现在让我们看看当您使用+运算符连接时会发生什么。例1publicstaticvoidtest1(){//都是常量,前端编译时会进行代码优化//通过idea直接查看对应的反编译class文件,会显示Strings1="abc";说明代码已经优化过了Strings1="a"+"b"+"c";字符串s2="abc";//没错,正如我们上面所知道的,s1和s2实际上指向了字符串常量池中的同一个值System.out.println(s1==s2);}常量和常量的拼接结果在常量池中,原理是编译期优化。示例2publicstaticvoidtest5(){Strings1="javaEE";字符串s2="hadoop";字符串s3="javaEEhadoop";字符串s4="javaEE"+"hadoop";字符串s5=s1+"hadoop";字符串s6="javaEE"+s2;字符串s7=s1+s2;System.out.println(s3==s4);//真正的编译时优化System.out.println(s3==s5);//falses1是变量,不能在编译时优化System.out.println(s3==s6);//falses2是一个变量,不能在编译时优化System.out.println(s3==s7);//falses1和s2是变量System.out.println(s5==s6);//falses5,s6不同的对象实例System.out.println(s5==s7);//falses5,s7不同的对象实例System.out.println(s6==s7);//falses6,s7是不同的对象实例}只要其中一个是变量,结果就在堆中。变量拼接的底层原理其实就是StringBuilder。例3:publicvoidtest6(){Strings0="beijing";Strings1="贝";Strings2="静";字符串s3=s1+s2;System.out.println(s0==s3);//falses3指向对象实例,s0指向字符串常量池中的“北京”Strings7="shanxi";finalStrings4="shan";最终字符串s5="xi";字符串s6=s4+s5;系统。out.println(s6==s7);//trues4和s5是final修饰的,s6的值可以在编译时确定}如果没有使用final修饰,就是一个变量。比如s3行的s1和s2会通过newStringBuilder拼接起来,并用final修饰,final是一个常量。代码优化将由编译器执行。String.intern()方法的妙用前面说过,通过new关键字和一些变量拼接创建的字符串对象不会在字符串常量池中,而是直接在堆中创建一个新对象。这样不太好,不能重复使用,也不能节省空间。那么最好的方法是什么?intern()派上用场了,这个很有用。intern()方法的作用可以理解为主动将一个还不在常量池中的字符串对象放入常量池中,并返回这个对象的地址。Strings6=newString("指环王").intern();s2==s6//trues2==s5//false字符串常量池有多大?字符串常量池有多大我不敢说,但是如果你解释一下它的底层数据结构,也许你就明白了。字符串常量池是一个固定大小的HashTable,哈希表,默认大小和长度为1009。如果放入StringPool的String过多,会出现严重的Hash冲突,导致链表非常长,而长链表的直接影响是调用String.intern时性能会大幅下降。使用-XX:StringTablesize来设置StringTable的长度。StringTable在jdk6中是固定的,长度是1009,所以如果常量池中的字符串过多,效率会下降的很快。不需要设置StringTable大小。在jdk7中,StringTable长度的默认值为60013,不需要设置StringTableSize。在jdk8中,如果设置StringTable的长度,1009是可以设置的最小值。字符串池的优缺点优点提高性能。使用字符串池时字符串操作更快,因为JVM可以返回对现有字符串对象的引用,而不是创建新对象。共享字符串可以节省内存。字符串池允许您在不同的变量和对象之间共享字符串,通过避免创建不必要的字符串对象来帮助节省内存。字符串池的缺点是可能会导致性能下降。从池中检索字符串需要搜索池中的所有字符串,这可能比简单地创建一个新的字符串对象要慢。如果程序创建和丢弃大量字符串,则尤其如此,因为每次使用字符串时都需要搜索字符串池。总结事实上,在Java7之前,JVM将JavaStringPool放在了PermGen空间中,它的大小是固定的——不能在运行时扩展,也不符合垃圾回收的条件。在PermGen(而不是堆)中驻留字符串的风险在于,如果我们驻留太多字符串,我们可能会从JVM中得到OutOfMemory错误。从Java7开始,JavaStringPool存放在Heap空间,由JVM进行垃圾回收。这种方法的优点是它降低了OutOfMemory错误的风险,因为未引用的字符串将从池中删除,从而释放内存。现在通过本文的学习,你应该知道如何更好地创建字符串对象了。
