您的位置 首页 java

结合JVM深入理解Java字符串

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

结合JVM深入理解Java字符串

image

结合JVM深入理解Java字符串

image

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

也就是说,String是 不可变类 。与它类似,基本类型的包装类也都是不可变类。字符串是常量,一旦初始化之后就不能更改。如果需要可变的字符串,就要借助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 != s3
    s3是new出来的字符串对象,它会在堆内存中分配一个新的地址。它的地址与字符串常量池中s2的引用地址自然是不同的。
    还有一个问题就是,s3这条语句一共创建了几个对象?答案是2个,即堆上的对象,以及JVM栈中对它的reference。但是,如果前面没有创建过相同的字面量的话,那么还得加上字面量本身,也就是3个。
  • s2 == s4
    这里就涉及到String.intern()方法的含义。在JDK1.8源码中,该方法的注释如下:

A pool of strings, initially empty, is maintained privately by the
class {@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常量池)又是怎么回事?

文章来源:智云一二三科技

文章标题:结合JVM深入理解Java字符串

文章地址:https://www.zhihuclub.com/187703.shtml

关于作者: 智云科技

热门文章

网站地图