深入分析String字符串

深入理解String字符串

相信大家在工作中,面试中,聊天中一直都会遇到一个很争论的问题,就是String的一堆知识,这里我就通过一些例子去全面的解析一遍,让自己也让同行更全面的去知其所以然.如有错误,欢迎指正!

举个栗子

java源代码

public class Demo{
    public static void main(String[]  args){
        String a = "1";
        String b = "11";
        String c = "111";
        String d = a+b;
        System.out.println(c == d);

        String e = new String("111");
        System.out.println(c == e);

        String f = new String("111").intern();
        System.out.println(f == c);

    }
}

输出结果

false
false
true

继续往下看,别问为什么!
执行javac Demo.java去编译源代码生成Demo.class
重点来了,把编译后的Demo.class拿过来用javap -v -c -s -l Demo.class执行一下,你可以看到这个东西:

Classfile /E:	/java/src/Demo.class
  Last modified 2019-4-3; size 978 bytes
  MD5 checksum 83f619899d9e19c6b48661d72c802f5d
  Compiled from "Demo.java"
public class Demo
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #15.#28        // java/lang/Object."<init>":()V
   #2 = String             #29            // 1
   #3 = String             #30            // 11
   #4 = String             #31            // 111
   #5 = Class              #32            // java/lang/StringBuilder
   #6 = Methodref          #5.#28         // java/lang/StringBuilder."<init>":()V
   #7 = Methodref          #5.#33         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #8 = Methodref          #5.#34         // java/lang/StringBuilder.toString:()Ljava/lang/String;
   #9 = Fieldref           #35.#36        // java/lang/System.out:Ljava/io/PrintStream;
  #10 = Methodref          #37.#38        // java/io/PrintStream.println:(Z)V
  #11 = Class              #39            // java/lang/String
  #12 = Methodref          #11.#40        // java/lang/String."<init>":(Ljava/lang/String;)V
  #13 = Methodref          #11.#41        // java/lang/String.intern:()Ljava/lang/String;
  #14 = Class              #42            // Demo
  #15 = Class              #43            // java/lang/Object
  #16 = Utf8               <init>
  #17 = Utf8               ()V
  #18 = Utf8               Code
  #19 = Utf8               LineNumberTable
  #20 = Utf8               main
  #21 = Utf8               ([Ljava/lang/String;)V
  #22 = Utf8               StackMapTable
  #23 = Class              #44            // "[Ljava/lang/String;"
  #24 = Class              #39            // java/lang/String
  #25 = Class              #45            // java/io/PrintStream
  #26 = Utf8               SourceFile
  #27 = Utf8               Demo.java
  #28 = NameAndType        #16:#17        // "<init>":()V
  #29 = Utf8               1
  #30 = Utf8               11
  #31 = Utf8               111
  #32 = Utf8               java/lang/StringBuilder
  #33 = NameAndType        #46:#47        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #34 = NameAndType        #48:#49        // toString:()Ljava/lang/String;
  #35 = Class              #50            // java/lang/System
  #36 = NameAndType        #51:#52        // out:Ljava/io/PrintStream;
  #37 = Class              #45            // java/io/PrintStream
  #38 = NameAndType        #53:#54        // println:(Z)V
  #39 = Utf8               java/lang/String
  #40 = NameAndType        #16:#55        // "<init>":(Ljava/lang/String;)V
  #41 = NameAndType        #56:#49        // intern:()Ljava/lang/String;
  #42 = Utf8               Demo
  #43 = Utf8               java/lang/Object
  #44 = Utf8               [Ljava/lang/String;
  #45 = Utf8               java/io/PrintStream
  #46 = Utf8               append
  #47 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #48 = Utf8               toString
  #49 = Utf8               ()Ljava/lang/String;
  #50 = Utf8               java/lang/System
  #51 = Utf8               out
  #52 = Utf8               Ljava/io/PrintStream;
  #53 = Utf8               println
  #54 = Utf8               (Z)V
  #55 = Utf8               (Ljava/lang/String;)V
  #56 = Utf8               intern
{
  public Demo();
    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 1: 0

  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 1
         2: astore_1
         3: ldc           #3                  // String 11
         5: astore_2
         6: ldc           #4                  // String 111
         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: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
        32: aload_3
        33: aload         4
        35: if_acmpne     42
        38: iconst_1
        39: goto          43
        42: iconst_0
        43: invokevirtual #10                 // Method java/io/PrintStream.println:(Z)V
        46: new           #11                 // class java/lang/String
        49: dup
        50: ldc           #4                  // String 111
        52: invokespecial #12                 // Method java/lang/String."<init>":(Ljava/lang/String;)V
        55: astore        5
        57: getstatic     #9                  // 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 #10                 // Method java/io/PrintStream.println:(Z)V
        74: new           #11                 // class java/lang/String
        77: dup
        78: ldc           #4                  // String 111
        80: invokespecial #12                 // Method java/lang/String."<init>":(Ljava/lang/String;)V
        83: invokevirtual #13                 // Method java/lang/String.intern:()Ljava/lang/String;
        86: astore        6
        88: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
        91: aload         6
        93: aload_3
        94: if_acmpne     101
        97: iconst_1
        98: goto          102
       101: iconst_0
       102: invokevirtual #10                 // Method java/io/PrintStream.println:(Z)V
       105: return
      LineNumberTable:
        line 3: 0
        line 4: 3
        line 5: 6
        line 6: 9
        line 7: 29
        line 9: 46
        line 10: 57
        line 12: 74
        line 13: 88
        line 15: 105
      StackMapTable: number_of_entries = 6
        frame_type = 255 /* full_frame */
          offset_delta = 42
          locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]
          stack = [ class java/io/PrintStream ]
        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 ]
          stack = [ class java/io/PrintStream, int ]
        frame_type = 255 /* full_frame */
          offset_delta = 26
          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 ]
          stack = [ class java/io/PrintStream ]
        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 ]
          stack = [ class java/io/PrintStream, int ]
        frame_type = 255 /* full_frame */
          offset_delta = 29
          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, class java/lang/String ]
          stack = [ class java/io/PrintStream ]
        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, class java/lang/String ]
          stack = [ class java/io/PrintStream, int ]
}
SourceFile: "Demo.java"

这个就是class文件的字节码,一直被我们认为很深奥的东西,其实也不是多么难理解,就是需要记住的东西有点多而已,看得多了其实也就掌握了(其实也是很难的).ok,栗子就到这了.

解析一下

栗子我们举完了,那我们就去分析一下,下边的内容涉及到了JVM,希望你有点JVM的基础再来看,大家都知道,JVM的体系结构其实是很复杂的,大体的结构描述的也很简单,比如堆栈方法区什么的这些大类别,但是方法区里边的内容,还有其他结构里边的内容可能就不太了解了.扯个蛋,我这里也不说这些玩意,我这里要说的是常量池
上边那个结果false,false,true到底是怎么来的?咱们徐徐渐进的来,结合字节码一步一步的分析,安全带请系好,我要开车了!
代码咱就不说了,既然要研究,那咱们就研究字节码,研究研究JVM到底是怎么去运算这些代码的.
1.创建一个类
从字节码可以看出来,前边的内容大致就是类的信息,比如:

Classfile /E:/java/src/Demo.class	//类的位置
  Last modified 2019-4-3; size 978 bytes	//类最后修改的时间和大小
  MD5 checksum 83f619899d9e19c6b48661d72c802f5d	//MD5效验码
  Compiled from "Demo.java"	//名称
  public class Demo		//类声明
  minor version: 0	//次版本号
  major version: 52	//主版本号
  flags: ACC_PUBLIC, ACC_SUPER	//类似于权限的东西,是否为Public类型,是否允许使用invokespecial字节码指令的新语义,JDK1.0.2之后编译出来的类的这个标志默认为真

2.常量池
从#1到#56都是,太多了就不一一解析了,给你们一个表格可以自己去一个一个分析,其实就是类常量池加载了那些东西而已.

常量池中数据项类型类型标志类型描述
Utf81UTF-8编码的Unicode字符串
Integer3int类型字面值
Float4float类型字面值
Long5long类型字面值
Double6double类型字面值
Class7对一个类或接口的符号引用
String8String类型字面值
Fieldref9对一个字段的符号引用
Methodref10对一个类中声明的方法的符号引用
InterfaceMethodref11对一个接口中声明的方法的符号引用
NameAndType12对一个字段或方法的部分符号引用

分析以后咱们就知道,在类被加载的时候其实a,b,c三个字符串已经被加载到常量池中了,这个常量池是类常量池,而不是运行时常量池,不理解没事,下面咱慢慢来.

3.类文件执行过程
具体的执行之类这里不多说,内容太多,可以参考我另一篇class指令的博文去对照着看一下.
咱们来分析.

System.out.println(c == d); //为什么是false?

字节码是

         0: ldc           #2                  
         //把常量池中的String 1压入栈
         2: astore_1						  
         //将引用类型或returnAddress类型值存入局部变量1
         3: ldc           #3                  
         // 把常量池中的String 11压入栈
         5: astore_2
         6: ldc           #4                  
         // String 111
         8: astore_3
         9: new           #5                  
         // class java/lang/StringBuilder	new一个Stringbuilder对象
        12: dup								  
        //复制栈顶部一个字长内容
        13: invokespecial #6                  
        // Method java/lang/StringBuilder."<init>":()V  根据编译时类型来调用实例方法
        16: aload_1							  
        //从局部变量1中装载引用类型值
        17: invokevirtual #7                  
        // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;  	调用append
        20: aload_2							  
        //从局部变量2中装载引用类型值
        21: invokevirtual #7                  
        // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;	调用append
        24: invokevirtual #8                  
        // Method java/lang/StringBuilder.toString:()Ljava/lang/String;	调用toString
        27: astore        4					  
        //将将引用类型或returnAddress类型值存入局部变量
        29: getstatic     #9                  
        // Field java/lang/System.out:Ljava/io/PrintStream;  从类中获取静态字段
        32: aload_3							  
        //从局部变量3中装载引用类型值
        33: aload         4					  
        //从局部变量中装载引用类型值(refernce)
        35: if_acmpne     42				  
        //有条件转移
        38: iconst_1						  
        //将int类型常量1压入栈
        39: goto          43				  
        //无条件跳转
        42: iconst_0						  
        //将int类型常量0压入栈
        43: invokevirtual #10                 
        // Method java/io/PrintStream.println:(Z)V 比较结果

从上边的方法表我们可以知道,在程序执行时,jvm会把类常量池中的字面量,常量等等加载到运行时常量池里去参与运算,然后再赋予引用,也就是说此时的a,b,c三个常量已经在运行时常量池中了,或许也可以理解为字符串常量池,这几个池子变来变去很是恶心,普及一下:
JDK1.7之前:运行时常量池逻辑包含字符串常量池存放在方法区,此时hotspot虚拟机对方法区的实现为永久代
JDK1.7的时候:字符串常量池被从方法区拿到了堆中,这里没有提到运行时常量池,也就是说字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区., 也就是hotspot中的永久代
JDK1.8的时候 hotspot移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(Metaspace)
而+这个运算符在java中底层其实就是append,通过字节码我们知道了,字符串的+运算其实就是创建了一个Stringbuildder对象,并调用它的append方法去计算结果的,最后调用toString方法得到结果,然后把引用再给局部变量c的.再看toString的源码:

public String toString() {
    // Create a copy, don't share the array
    return new String(value, 0, count);
}

Stringbuilder的toString方法其实就是new 了一个String的对象在堆空间中,它本身是一个String对象而不是常量,所以拿一个堆中的String对象引用和字符串常量池中的常量引用去做对比,结果肯定是false,如果想让他变成true,就必须让这个String对象入常量池,也就是调intern()方法.
现在再去看第二和第三个输出结果,自然也就明白了:

String e = new String("111");
System.out.println(c == e);

String f = new String("111").intern();
System.out.println(f == c);

e和c不相等是因为这个e的引用是一个新的String对象,通过new关键字创建的都是新对象,当然这句话也产生了2个对象,常见的面试题,就不多说了,一个是构造方法构建的,一个是new出来的,存放位置也不同,而f和c相等是因为f的引用进行了入池操作,而字符串常量池有点享元模式的意思,在类被加载的时候111这个字符串常量就已经被加载进去了,所以再入池的时候发现已经存在,就不会再创建新的了.好了,分析到此为止.

如果有错误, 欢迎指正! 共同进步!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在C#中处理字符串可以使用多种方法,以下是几种常见的方法: 方法一:使用字符串的内置方法 可以使用字符串的内置方法来处理字符串,例如使用Contains()方法来检查字符串是否包含某个指定的子字符串,使用Replace()方法来替换字符串中的指定字符或子字符串。可以使用Substring()方法来获取字符串的子串。还可以使用Split()方法将字符串拆分成数组,使用Join()方法将数组拼接成字符串。 方法二:使用正则表达式 正则表达式是一种强大的字符串处理工具。可以使用正则表达式来匹配、查找、替换字符串中的特定模式。C#中提供了Regex类来操作正则表达式。 方法三:使用StringBuilder类 如果需要对字符串做大量的修改操作,可以使用StringBuilder类。StringBuilder类提供了一组方法来对字符串进行添加、删除、替换等操作。与字符串不同,StringBuilder对象可以直接修改,而不会创建新的字符串对象。 方法四:使用字符串的格式化 在C#中,可以使用字符串的格式化来对字符串进行处理。可以使用String.Format()方法或者使用插值表达式($)来将变量或表达式插入到字符串中。 总结:C#中有多种方法可以处理字符串,可以根据具体需求选择合适的方法。可以使用字符串的内置方法、正则表达式、StringBuilder类或者字符串的格式化来对字符串进行处理。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [C#字符串(string)操作](https://blog.csdn.net/caoyanchao1/article/details/121547036)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [C# string字符串内存管理深入分析(全程干货)](https://blog.csdn.net/qq_52855744/article/details/126738039)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值