以字节码来分析String
来源:互联网 发布:从程序员到架构师 pdf 编辑:程序博客网 时间:2024/06/06 13:23
Java String的比较是平常开发常常遇到的坑,也是各种面试经常考察的问题,本文从Java编译后字节码的出发,来深入理解这个问题的本质,以使得我们对String有更加深入的理解。
public class TestContantPool{private static String a = "1234";final String x = "34";final String y;public TestContantPool(){y = "34";}public void test(){String b = "1234";String c = "12" + "34";String d = new String("12345");final String e = "34";String f = "34";String g = "12" + e;String h = "12" + f;String i = "12" + x;String j = "12" + y;System.out.println(a == b);System.out.println(a == c);System.out.println(a != d);System.out.println(a == g);System.out.println(a != h && a == h.intern());System.out.println(a == i && a == i.intern());System.out.println(a != j && a == j.intern());}public static void main(String[] ar){new TestContantPool().test();}}/**truetruetruetruetruetruetrue*/
编译之后,我们来分析一下部分字节码,重点在于常量池(Constant Pool),静态块static{},构造方法和test()方法:
javap -verbose TestContantPool.class Constant pool: #5 = Utf8 a #7 = Utf8 x #9 = String #10 // 34 #10 = Utf8 34 #11 = Utf8 y #15 = String #16 // 1234 #16 = Utf8 1234 #31 = Class #32 // java/lang/String #32 = Utf8 java/lang/String #33 = String #34 // 12345 #34 = Utf8 12345 #39 = Utf8 java/lang/StringBuilder #40 = String #41 // 12 #41 = Utf8 12 #66 = Utf8 b #67 = Utf8 c #68 = Utf8 d #69 = Utf8 e #70 = Utf8 f #71 = Utf8 g #72 = Utf8 h #73 = Utf8 i #74 = Utf8 j final java.lang.String x; descriptor: Ljava/lang/String; flags: ACC_FINAL ConstantValue: String 34 final java.lang.String y; descriptor: Ljava/lang/String; flags: ACC_FINAL static {}; descriptor: ()V flags: ACC_STATIC Code: stack=1, locals=0, args_size=0 0: ldc #15 // String 1234 2: putstatic #17 // Field a:Ljava/lang/String; 5: return LineNumberTable: line 6: 0 LocalVariableTable: Start Length Slot Name Signature public bytecode.TestContantPool(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: aload_0 1: invokespecial #22 // Method java/lang/Object."<init>":()V 4: aload_0 5: ldc #9 // String 34 7: putfield #24 // Field x:Ljava/lang/String; 10: aload_0 11: ldc #9 // String 34 13: putfield #26 // Field y:Ljava/lang/String; 16: return LocalVariableTable: Start Length Slot Name Signature 0 17 0 this Lbytecode/TestContantPool; public void test(); descriptor: ()V flags: ACC_PUBLIC Code: stack=3, locals=10, args_size=1 0: ldc #15 // String 1234 2: astore_1 3: ldc #15 // String 1234 5: astore_2 6: new #31 // class java/lang/String 9: dup 10: ldc #33 // String 12345 12: invokespecial #35 // Method java/lang/String."<init>":(Ljava/lang/String;)V 15: astore_3 16: ldc #9 // String 34 18: astore 4 20: ldc #9 // String 34 22: astore 5 24: ldc #15 // String 1234 26: astore 6 28: new #38 // class java/lang/StringBuilder 31: dup 32: ldc #40 // String 12 34: invokespecial #42 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 37: aload 5 39: invokevirtual #43 // Method java/lang/StringBuilder.append:(Ljava/lang/String;) 42: invokevirtual #47 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 45: astore 7 47: ldc #15 // String 1234 49: astore 8 51: new #38 // class java/lang/StringBuilder 54: dup 55: ldc #40 // String 12 57: invokespecial #42 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 60: aload_0 61: getfield #26 // Field y:Ljava/lang/String; 64: invokevirtual #43 // Method java/lang/StringBuilder.append:(Ljava/lang/String;) 67: invokevirtual #47 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 70: astore 9 235: return
1, 首先分析ConstantPool,可以看到编译之后常量池中就有了String的字面量"12","34",1234"。下面代码中所有的其他String的创建都离不开常量池中的这些字面量。
2, 再看类变量a的初始化过程,根据虚拟机类加载机制可以知道,static变量和static{}代码块会在编译生成的<clinit>()方法中执行。<clinit>()是由编译器自动收集类中的所有 static变量的赋值动作和static{}代码块中的语句合并产生的,<clinit>()方法如果存在,会在实例构造函数<init>()执行。
下面两句代码是对static变量a的赋值语句,可以看出来a是从常量池中取值的:
0: ldc #15 // String 1234 (将int,float,String型常量值从常量池推送至栈顶)2: putstatic #17 // Field a:Ljava/lang/String; (为指定的类的静态域赋值)
3,最后分析test()方法。
b和c 的赋值"1234",都是从常量池中取值:
0: ldc #15 // String 1234 (将int,float,String型常量值从常量池推送至栈顶)2: astore_1 (将栈顶引用型数值存入第二个本地变量) 3: ldc #15 // String 1234 5: astore_2 (将栈顶引用型数值存入第三个本地变量)
d的赋值"1234",可以看到是重新调用了new了一个String对象:
6: new #31 // class java/lang/String (创建一个对象,并且将其引用压入栈顶)9: dup (赋值栈顶数值,并且将复制值压入栈顶)10: ldc #33 // String 12345 (将int,float,String型常量值从常量池推送至栈顶)12: invokespecial #35 (调用超类构造方法,实例初始化方法)15: astore_3 (将栈顶引用型数值存入第四个本地变量)
e,f,g的赋值,都是从常量池中取值:
16: ldc #9 // String 3418: astore 420: ldc #9 // String 3422: astore 524: ldc #15 // String 123426: astore 6
h的赋值,使用StringBuilder new一个String(从中可以看到,String 的"+"操作是用StringBuilder实现的):
28: new #38 // class java/lang/StringBuilder31: dup32: ldc #40 // String 1234: invokespecial #42 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V37: aload 539: invokevirtual #43 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)42: invokevirtual #47 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;45: astore 7
i的赋值,常量池去取:
47: ldc #15 // String 123449: astore 8
j的赋值,使用StringBuilder new一个String(从中可以看到,String 的"+"操作是用StringBuilder实现的):
51: new #38 // class java/lang/StringBuilder54: dup55: ldc #40 // String 1257: invokespecial #42 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V60: aload_061: getfield #26 // Field y:Ljava/lang/String;64: invokevirtual #43 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)67: invokevirtual #47 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;70: astore 9
根据以上分析,只要是从常量池中取值的,"=="比较都是true;其他的或者是new String()产生的,或者是new StringBuilder()产生的, 都是在Heap区新建对象,因此都不相等。按照这种逻辑,就能够理解String对象的比较问题了。
- 以字节码来分析String
- JVM-String比较-字节码分析
- IL字节码分析
- IL字节码分析
- IL字节码分析
- java中按字节数的长度来截取字符串,并以...来忽略
- 以字节码通信时的乱码
- 从jvm运行机制来分析String对象
- 从jvm运行机制来分析String对象
- 以live555为例来分析H264码流的打包发送
- Node.js通过write、read以字节为单位来写、读
- python分析字节码dis
- Java虚拟机字节码分析
- java String 之字节码解析
- String值的调用(字节码层面)
- 怎么以文本方式查看二进制的字节码文件
- String计算所占字节
- 字节转string
- class.newInstance()报NullPointException问题解决
- SecureCRT连接虚拟机中的Linux系统相关
- volley 兼容新版本sdk
- Codeforces 496D Tennis Game【连续二分+枚举】
- margin外边距
- 以字节码来分析String
- LinkedHashMap和HashMap的比较使用
- 谈对C语言中内存分配的理解
- 按键控制数码管0-99显示,十位不跳,用定时器T0中断
- 体系结构—批处理风格
- 货运公司的交货方式有哪些呢?
- 算法作业12
- 用原生js实现addClass,removeClass,hasClass方法
- Linux (1) : 基础介绍