作者回复: 你好 KL3,在Java6中substring方法会调用new string构造函数,此时会复用原来的char数组,而如果我们仅仅是用substring获取一小段字符,而原本string字符串非常大的情况下,substring的对象如果一直被引用,由于substring的里面的char数组仍然指向原字符串,此时string字符串也无法回收,从而导致内存泄露。
试想下,如果有大量这种通过substring获取超大字符串中一小段字符串的操作,会因为内存泄露而导致内存溢出。
作者回复: 答案非常正确,理解了这个题目基本理解了string的特性了。
作者回复: 非常感谢Geek的补充,我在这里也再补充一个小点,split有两种情况不会使用正则表达式:
第一种为传入的参数长度为1,且不包含“.$|()[{^?*+\\”regex元字符的情况下,不会使用正则表达式;
第二种为传入的参数长度为2,第一个字符是反斜杠,并且第二个字符不是ASCII数字或ASCII字母的情况下,不会使用正则表达式。
作者回复: 这里我纠正下,str3是intern返回的引用,intern而不是创建出来的。
你的答案是正确的!
作者回复: 如果看不太懂,建议先熟悉下JVM这块的知识点。我们知道,JVM从逻辑分区可以分为堆、JVM栈、本地方法栈、方法区、程序计数器,方法区中,在JDK1.8之后,包含了元空间、静态常量池、运行时常量池。
对于字符串常量,在类加载时,会将字符串放入方法区中的静态常量池,包括字符串的字面量和字符引用。而在初始化或运行时,会将字符引用转为直接引用,存放在运行时常量池。
如果是运行时动态生成的字符串对象调用intern方法,如果字符串的引用在运行时常量池不存在,则会在常量池中创建一个引用。
所以第一个通过加动态生成的“11”字符串由于在运行时常量中没有该字符串的引用,所以会在调用s1.intern时,在运行时常量池中生成一个s1的引用,当s2再次引用该字符串时,发现运行时常量池中存在相同值的字符串的引用,就直接返回s1的引用。所以s1==s2是返回的true。这也仅限于JDK1.7之后的版本。
而第二种,用于"11"在类加载时,已经存在静态常量池中,在new string(“11”)时,会在运行时常量池中创建一个“11”字符串的直接引用。而s1指向的并不是该引用,而是new string这个对象的引用。当s2=“11”时,返回的是运行时常量池中的引用。所以s1==s2返回false。
作者回复: 严格来说,是静态常量池和运行时常量池,静态常量池是存放字符串字面量、符号引用以及类和方法的信息,而运行时常量池存放的是运行时一些直接引用。
运行时常量池是在类加载完成之后,将静态常量池中的符号引用值转存到运行时常量池中,类在解析之后,将符号引用替换成直接引用。
这两个常量池在JDK1.7版本之后,就移到堆内存中了,这里指的是物理空间,而逻辑上还是属于方法区(方法区是逻辑分区)。
我文中说的是两个常量池,没有具体区分,在初次加载时,是字面量是加载到了静态常量池中,解析之后会将引用加载到运行时常量池。
intern方法生成的引用或对象是在运行时常量池中。
作者回复: 如果对空间要求高于时间要求,且存在大量重复字符串时,可以考虑使用常量池存储。
如果对查询速度要求很高,且存储字符串数量很大,重复率很低的情况下,不建议存储在常量池中。
具体可以通过模拟测试自己的场景,对比两种存储方式的性能,通过数据来给自己答案。
作者回复: 你好 teanmy。运行时创建的字符串对象只会在堆中创建一个对象。在这个前提下,如果有相同值的对象创建,使用intern可以减少重复字符串的创建。例如,有广东省/深圳市/南山区,如果有千万个人发布消息,创建了地址对象,这样导致千万个“广东省”对象在堆内存中创建,如果长时间引用,这些对象都没法释放,使用intern将“广东省”放到常量池中,其他对象引用常量池中的同一个“广东省”字符串,而堆中的千万个对象将被回收。
如果有疑问,请继续留言。
作者回复: 你好 Zend,具体的复制过程是先将常量池中的字符串压入栈中,在使用string的构造方法时,会拿到栈中的字符串作为构造方法的参数。这里我纠正一点,今天我查看了下这个构造函数,String的构造函数是一个char数组赋值过程,不是new char[]重新创建,所以是引用了常量池中的字符串对象,存在引用关系。
作者回复: 你理解的分歧点是对的,这个构造是在加载类时,就已经在常量池中构造好常量。
作者回复: 非常感谢only now的总结,这一讲中没有详细去区分常量池,而是在强调字符串的使用,后面我们在JVM中可以再一起研究下常量池。
JVM文献中提到方法区是存在垃圾回收。我们可以通过intern方法来验证这个gc问题,通过大量请求请求某个接口,传入参数创建字符串对象,之后通过intern方法在常量池中生成字符串对象,之后失去引用,观察gc情况。
作者回复: 我们可以看到0 new,即是生成了一个对象,这个对象是在堆内存用创建的,之后4 Idc则是将常量池中创建的字符串abc压入栈中,invokespecial调用构造方法复制abc字符串到对象中,invokevirtual调用intern本地方法,返回常量池中的对象引用给s1。
new String("abc")是会创建两个对象的,一个是堆对象,一个是常量池中的对象,intern会去判断常量池中是否有,这个时候是有的,所以不会创建,而是改变s1的引用。
不知道这样是否更好理解?
作者回复: String s1 = new String("1") + new String("1")会在堆中组合一个新的字符串对象"11",在s1.intern()之后,由于常量池中没有该字符串的引用(只有字符串常量"11"),所以常量池中生成一个堆中字符串"11"的引用,此时String s2= "11"返回的是堆字符串"11"的引用,所以s1==s2。
在JDK1.7版本以及之后的版本运行以下代码,你会发现结果为true,在JDK1.6版本运行的结果却为false:
String s1 = new String("1") + new String("1");
System.out.println( s1.intern()==s1);
而String s1 = new String("11")首先会在常量池中创建字符串"11"的引用,而s1则是返回的堆中的new String("11")对象的引用,此时s1.intern()返回的是常量池字符串常量"11"的引用,而非堆中的。而String s2="11"又是返回的常量池中常量"11"的引用。所以s1==s2为false。
总结:常量池中同时存在字符串常量和字符串引用,在JDK1.7版本之后的intern()方法只会尝试对象的引用放入常量池,而在之前的版本中,intern()方法会复制字符串常量到常量池中,并返回字符串引用。
作者回复: 是的,运行时动态创建是在堆内存中直接创建的,调用intern方法,会反倒常量池中。
作者回复: 你好,如果我们的数据对查询速度没有这么高要求,可以考虑使用。
作者回复: 你好 W.LI,刚我debug了下,a和b的value是同一个地址,因为a在常量池中创建了"abc",而new String("abc")时,发现常量池存在"abc"字符串对象,不会创建了。这时通过构造函数String(String original)将常量池中的"abc"复制给value,这里的复制是引用,不是创建新的char[]数组,所以是同一个value地址。
而c中的构造函数,是新开辟了一个char[]数组:
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
所以value的地址不一样。
可以再试试,有问题留言。
作者回复: 如果是需要按照创建顺序来讲,常量“abc”,则会在加载编译时构造常量池时在常量池中创建“abc”字符串对象,而new对象的构造函数是在运行时创建并复制常量池中的“abc”。还有一个运行时常量池,也就是说,在运行时创建的字符串对象,通过intern方法会在运行时常量池中创建字符串对象。
作者回复: 实际编码中,我们要结合实际场景来选择创建字符串的方式,例如,在创建局部变量以及常量时,我们一般使用A的这种方式;如果我们要区别一个字符串创建两个不同的对象来使用时,会选择B;intern一般使用的比较少,例如我们平时会创建很多一样的字符串的对象时,且对象会保存在内存中,我们可以考虑使用intern方法来减少过多重复对象占用内存空间。
作者回复: 假设有以下代码:
String a1 = "11";
String a2 = new String("11") ;
String a3 = a2.intern();
System.out.println(a1==a2);
System.out.println(a2==a3);
System.out.println(a3==a1);
以上代码,在JDK1.7以上,运行的结果为:
false
false
true
这是为什么呢?
这是因为String a1 = "11"中的 "11" 是一个常量,类加载时,该常量的字面量会保存在静态常量池中,也就是堆中,当类初始化时,该常量的引用会加载到运行常量池中,这就是“把首次遇到的字符串的引用添加到常量池中。
而 String a2 = new String("11") 则会在运行生成一个new String("11")对象,如果调用String a3 = a2.intern(),此时a2会判断运行常量池中已经存在字面量为"11"的引用了,则会直接返回"11"的引用,所以实际上a3的引用就是a1的引用。