步子百科步子百科

字符串是什么意思(结合JVM深入理解Java字符串)

既然题目里就提到了JVM,字符a字那么首先必然要奉上两张图。意思

image

image

HotSpot JVM内存模型已经是结合解老生常谈的知识了,所以这里也就不再赘述。入理直接说String。符串在String类的字符a字JavaDoc开头,就有这样一句话:

Strings are constant; their values cannot be changed after they are created. String buffers support mutable strings. Because String objects are immutable they can be shared.

也就是意思说,String是结合解不可变类。与它类似,入理基本类型的符串包装类也都是不可变类。字符串是字符a字常量,一旦初始化之后就不能更改。意思如果需要可变的结合解字符串,就要借助StringBuilder和StringBuffer了。入理为什么说String是符串不可变的?因为在它的内部是用一个final char数组来存储的。

private final char value[];仍然举一个例子 String s1 = "LittleMagic"; String s2 = "LittleMagic"; String s3 = new String("LittleMagic"); String s4 = s3.intern(); String s5 = "Little" + "Magic"; String s6 = "LittleMagic2"; String s7 = s2 + 2; System.out.println(s1 == s2); // true System.out.println(s2 == s3); // false System.out.println(s2 == s4); // true System.out.println(s2 == s5); // true System.out.println(s6 == s7); // false

这段代码的字节码如下。

0: ldc #2 // String LittleMagic 2: astore_1 3: ldc #2 // String LittleMagic 5: astore_2 6: new #3 // class java/lang/String 9: dup 10: ldc #2 // String LittleMagic 12: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V 15: astore_3 16: aload_3 17: invokevirtual #5 // Method java/lang/String.intern:()Ljava/lang/String; 20: astore 4 22: ldc #2 // String LittleMagic 24: astore 5 26: ldc #6 // String LittleMagic2 28: astore 6 30: new #7 // class java/lang/StringBuilder 33: dup 34: invokespecial #8 // Method java/lang/StringBuilder."<init>":()V 37: aload_2 38: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 41: iconst_2 42: invokevirtual #10 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 45: invokevirtual #11 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 48: astore 7 50: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream; 53: aload_1 54: aload_2.................

下面来逐个分析。

s1 == s2类似"LittleMagic"这样的字符串,我们叫它字符串字面量(String literal),后面简称字面量。字面量对象是存储在字符串常量池中的。采用字面量的方式创建字符串,JVM首先会在字符串常量池中寻找字面量为“LittleMagic”的字符串对象,如果不存在,就创建这个对象,并且返回它的引用;如果存在的话,就会直接返回它的引用。所以s1与s2的地址是相同的。s2 != s3s3是new出来的字符串对象,它会在堆内存中分配一个新的地址。它的地址与字符串常量池中s2的引用地址自然是不同的。还有一个问题就是,s3这条语句一共创建了几个对象?答案是2个,即堆上的对象,以及JVM栈中对它的reference。但是,如果前面没有创建过相同的字面量的话,那么还得加上字面量本身,也就是3个。s2 == s4这里就涉及到String.intern()方法的含义。在JDK1.8源码中,该方法的注释如下:

A pool of strings, initially empty, is maintained privately by theclass { @code String}.When the intern method is invoked, if the pool already contains a string equal to this { @code String} object as determined by the { @link #equals(Object)} method, then the string from the pool is returned. Otherwise, this { @code String} object is added to the pool and a reference to this { @code String} object is returned.

意思就是,如果字符串常量池中已经存在一个字面量上相等(用String.equals()方法判定)的字符串,就返回常量池中的字符串。否则,就将这个字符串加入常量池,并返回它的引用。这样理解,s2与s4相等就是自然的了。

s2 == s5从上面的字节码中可以看出,s5由两个字面量相连接,在字节码中体现出来的是连接后的结果,即“LittleMagic”。也就是说,字面量做“+”运算能够在编译期就确定值,最终还是归于字符串常量池中对象的比较。s6 != s7仍然从字节码中可以看出,s7 = s2 + 2这条语句,实际上是new出了一个StringBuilder,然后调用其append()方法来做连接。亦即与上面的情况相反,如果“+”运算中存在字符串引用的话,就会创建新的对象了,因为引用对应的值在编译期是无法确定的。由此也可以得知,不要在循环中使用类似s7 = s2 + 2这种调用方法,因为每次循环都要创建StringBuilder对象,拖累运行效率。TBD字符串常量池随JDK版本的变化,位置有哪些变迁?是如何实现的?字符串常量池里存放的到底是什么?对象,引用,还是兼而有之?其他两种JVM管理的常量池(运行时常量池,class常量池)又是怎么回事?