java-string-深入研究1

本文详细解析了Java中字符串比较的原理,特别是使用字符串常量、StringBuilder及intern方法时的比较行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先看一下面试题

代码1

public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "a" + "b";
        String s4 = s1 + s2;
        String s5 = "ab";
        String s6 = s4.intern();

        System.out.println(s3 == s4);
        System.out.println(s3 == s5);
        System.out.println(s3 == s6);
    }

如果想回答这个问题,可以先分析如下代码的字节码指令

代码2

  1 package com.example.demo;                                                   
  2  
  3 /**
  4  * @author micro.cloud.fly
  5  * @date 2022/10/12 10:40 AM
  6  * @desc
  7  */
  8 public class Stringtest01 {
  9     public static void main(String[] args) {
 10         String s1 = "a";
 11         String s2 = "b";
 12         String s3 = "ab";
 13     }
 14 }

通过javap命令,可以得到如下的字节码

javap -v Stringtest01.class

字节码如下:

Classfile /Users/java0904/IdeaProjects/jvm-study/target/classes/com/example/demo/Stringtest01.class
  Last modified 2022年10月12日; size 552 bytes
  MD5 checksum 56f169e3e3e64a5a7443e762cbef5534
  Compiled from "Stringtest01.java"
public class com.example.demo.Stringtest01
  minor version: 0
  major version: 55
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #5                          // com/example/demo/Stringtest01
  super_class: #6                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #6.#25         // java/lang/Object."<init>":()V
   #2 = String             #26            // a
   #3 = String             #27            // b
   #4 = String             #28            // ab
   #5 = Class              #29            // com/example/demo/Stringtest01
   #6 = Class              #30            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/example/demo/Stringtest01;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               s1
  #19 = Utf8               Ljava/lang/String;
  #20 = Utf8               s2
  #21 = Utf8               s3
  #22 = Utf8               MethodParameters
  #23 = Utf8               SourceFile
  #24 = Utf8               Stringtest01.java
  #25 = NameAndType        #7:#8          // "<init>":()V
  #26 = Utf8               a
  #27 = Utf8               b
  #28 = Utf8               ab
  #29 = Utf8               com/example/demo/Stringtest01
  #30 = Utf8               java/lang/Object
{
  public com.example.demo.Stringtest01();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 8: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/example/demo/Stringtest01;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=4, args_size=1
         0: ldc           #2                  // String a
         2: astore_1
         3: ldc           #3                  // String b
         5: astore_2
         6: ldc           #4                  // String ab
         8: astore_3
         9: return
      LineNumberTable:
        line 10: 0
        line 11: 3
        line 12: 6
        line 13: 9
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  args   [Ljava/lang/String;
            3       7     1    s1   Ljava/lang/String;
            6       4     2    s2   Ljava/lang/String;
            9       1     3    s3   Ljava/lang/String;
    MethodParameters:
      Name                           Flags
      args
}
SourceFile: "Stringtest01.java"

分析如下:

  • Constant pool 是指常量池,常量池中的每一个**#num**代表一个常量,程序在运行的时候,这些数字会被翻译成真正的内存地址(逻辑地址或者物理地址)
  • Code重点翻译一下:
    stack=1, locals=4, args_size=1
    0: ldc #2 // String a
    2: astore_1
    3: ldc #3 // String b
    5: astore_2
    6: ldc #4 // String ab
    8: astore_3
    9: return
  • ldc:常量池中的常量值(intfloatstring reference,object reference)入栈
  • astore_1 将栈顶引用类型值保存到局部变量1中
  • 局部变量在这个表中间,可以看到局部变量123都是string类型,也就是对应着s1,s2,s3
    LocalVariableTable:
    Start Length Slot Name Signature
    0 10 0 args [Ljava/lang/String;
    3 7 1 s1 Ljava/lang/String;
    6 4 2 s2 Ljava/lang/String;
    9 1 3 s3 Ljava/lang/String;

翻译:
LineNumberTable:
line 10: 0//文件的第10行,也就是第0步,常量池中的#2位置的常量值(a)(#号位置又引用了#26位置的常量a)入栈,
line 11: 3 同上
line 12: 6 同上
line 13: 9// 返回

常量池中的常量,都会被加载到运行时到常量池中,但是常量池中的a、b、ab只是符号,只有代码运行到10-12行的时候,这些符号,才会变为字符串对象。

分析代码1

通过反编译技术可以查看字节码文件如下:

Classfile /C:/Users/Administrator/IdeaProjects/vin/target/classes/com/example/vin/Stringtest02.class
  Last modified 2022-10-12; size 1169 bytes                               
  MD5 checksum 2ff3b898ef3cd1de122e3b1c66799088                           
  Compiled from "Stringtest02.java"                                       
public class com.example.vin.Stringtest02                                 
  minor version: 0                                                        
  major version: 52                                                       
  flags: ACC_PUBLIC, ACC_SUPER                                            
Constant pool:                                                            
   #1 = Methodref          #13.#39        // java/lang/Object."<init>":()V
   #2 = String             #40            // a
   #3 = String             #41            // b
   #4 = String             #42            // ab
   #5 = Class              #43            // java/lang/StringBuilder
   #6 = Methodref          #5.#39         // java/lang/StringBuilder."<init>":()V
   #7 = Methodref          #5.#44         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #8 = Methodref          #5.#45         // java/lang/StringBuilder.toString:()Ljava/lang/String;
   #9 = Methodref          #46.#47        // java/lang/String.intern:()Ljava/lang/String;
  #10 = Fieldref           #48.#49        // java/lang/System.out:Ljava/io/PrintStream;
  #11 = Methodref          #50.#51        // java/io/PrintStream.println:(Z)V
  #12 = Class              #52            // com/example/vin/Stringtest02
  #13 = Class              #53            // java/lang/Object
  #14 = Utf8               <init>
  #15 = Utf8               ()V
  #16 = Utf8               Code
  #17 = Utf8               LineNumberTable
  #18 = Utf8               LocalVariableTable
  #19 = Utf8               this
  #20 = Utf8               Lcom/example/vin/Stringtest02;
  #21 = Utf8               main
  #22 = Utf8               ([Ljava/lang/String;)V
  #23 = Utf8               args
  #24 = Utf8               [Ljava/lang/String;
  #25 = Utf8               s1
  #26 = Utf8               Ljava/lang/String;
  #27 = Utf8               s2
  #28 = Utf8               s3
  #29 = Utf8               s4
  #30 = Utf8               s5
  #31 = Utf8               s6
  #32 = Utf8               StackMapTable
  #33 = Class              #24            // "[Ljava/lang/String;"
  #34 = Class              #54            // java/lang/String
  #35 = Class              #55            // java/io/PrintStream
  #36 = Utf8               MethodParameters
  #37 = Utf8               SourceFile
  #38 = Utf8               Stringtest02.java
  #39 = NameAndType        #14:#15        // "<init>":()V
  #40 = Utf8               a
  #41 = Utf8               b
  #42 = Utf8               ab
  #43 = Utf8               java/lang/StringBuilder
  #44 = NameAndType        #56:#57        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #45 = NameAndType        #58:#59        // toString:()Ljava/lang/String;
  #46 = Class              #54            // java/lang/String
  #47 = NameAndType        #60:#59        // intern:()Ljava/lang/String;
  #48 = Class              #61            // java/lang/System
  #49 = NameAndType        #62:#63        // out:Ljava/io/PrintStream;
  #50 = Class              #55            // java/io/PrintStream
  #51 = NameAndType        #64:#65        // println:(Z)V
  #52 = Utf8               com/example/vin/Stringtest02
  #53 = Utf8               java/lang/Object
  #54 = Utf8               java/lang/String
  #55 = Utf8               java/io/PrintStream
  #56 = Utf8               append
  #57 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #58 = Utf8               toString
  #59 = Utf8               ()Ljava/lang/String;
  #60 = Utf8               intern
  #61 = Utf8               java/lang/System
  #62 = Utf8               out
  #63 = Utf8               Ljava/io/PrintStream;
  #64 = Utf8               println
  #65 = Utf8               (Z)V
{
  public com.example.vin.Stringtest02();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 8: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/example/vin/Stringtest02;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=7, args_size=1
         0: ldc           #2                  // String a
         2: astore_1
         3: ldc           #3                  // String b
         5: astore_2
         6: ldc           #4                  // String ab
         8: astore_3
         9: new           #5                  // class java/lang/StringBuilder
        12: dup
        13: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
        16: aload_1
        17: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        20: aload_2
        21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        24: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        27: astore        4
        29: ldc           #4                  // String ab
        31: astore        5
        33: aload         4
        35: invokevirtual #9                  // Method java/lang/String.intern:()Ljava/lang/String;
        38: astore        6
        40: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
        43: aload_3
        44: aload         4
        46: if_acmpne     53
        49: iconst_1
        50: goto          54
        53: iconst_0
        54: invokevirtual #11                 // Method java/io/PrintStream.println:(Z)V
        57: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
        60: aload_3
        61: aload         5
        63: if_acmpne     70
        66: iconst_1
        67: goto          71
        70: iconst_0
        71: invokevirtual #11                 // Method java/io/PrintStream.println:(Z)V
        74: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
        77: aload_3
        78: aload         6
        80: if_acmpne     87
        83: iconst_1
        84: goto          88
        87: iconst_0
        88: invokevirtual #11                 // Method java/io/PrintStream.println:(Z)V
        91: return
      LineNumberTable:
        line 10: 0
        line 11: 3
        line 12: 6
        line 13: 9
        line 14: 29
        line 15: 33
        line 17: 40
        line 18: 57
        line 19: 74
        line 20: 91
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      92     0  args   [Ljava/lang/String;
            3      89     1    s1   Ljava/lang/String;
            6      86     2    s2   Ljava/lang/String;
            9      83     3    s3   Ljava/lang/String;
           29      63     4    s4   Ljava/lang/String;
           33      59     5    s5   Ljava/lang/String;
           40      52     6    s6   Ljava/lang/String;
      StackMapTable: number_of_entries = 6
        frame_type = 255 /* full_frame */
          offset_delta = 53
          locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, cl
ass java/lang/String ]
        frame_type = 255 /* full_frame */
          offset_delta = 0
          locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, cl
ass java/lang/String ]
          stack = [ class java/io/PrintStream, int ]
    MethodParameters:
      Name                           Flags
      args
}
SourceFile: "Stringtest02.java"

有一点需要说明,常量池中的这些符号,并不是对象,而是只有在运行时,常量池的这些符号会被加载到内存,运行到对应的代码后(如 string s1=“a”),再把这些符号变为对象,变为对象的时候,jvm会首先分配一个空间,叫做stringTable,用来存储这些字符串,如果stringTable中已经有这个字符串对象了,则不会另外再生成这个字符串对象。

s3==s4

通过 9: new #5 // class java/lang/StringBuilder 可以看到,在进行s4=s1+s2的时候,底层实际上是进行了调用stringbuilder的构造方法,那么很明显,会重新生成一个对象,因此s3==s4为false;

s3==s5

那么s3==s5呢,可以看到LineNumberTable中的第14行,对应的是Code的29,即
29: ldc #4 // String ab

可以看到直接加载了#4这个常量池的字符串常量,没有生成新的字符串对象,所以s3==s5为true。如果在String s5="ab"的下面加上一行

        String s7 = "a"+"b";

此时,s3==s7的结果是什么呢,答案也为true,生成的字节码同上,原因是jvm在编译器进行了优化,直接认为s7的结果为确定的“ab”,因此,可以直接在字符串池中找到。

s3==s6

再看s3==s6呢,s6==s4.intern()方法,是尝试把s4的字符串对象放入到字符串常量池stringTable中,如果字符串常量池中已经有了,则不放入,如果没有,则放入,并且s4也会指向常量池中的字符串对象,(也有人说是字符串常量池中的对象是堆中的对象的引用,此处我没有深入研究)但是不论有没有,都会返回常量池中的字符串对象给s6,即s6来自于字符串常量池。所以s3 == s6 为true。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

micro_cloud_fly

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值