解读String字符串拼接的原理
前言
明白什么是引用,什么是该引用指向的真正对象。
==对于基本数据类型比较的是值,对于引用数据类型比较的是指向的对象的地址,即两者指向的是否是同一个对象。
String s = "gzc";
上述代码中s为变量引用,它存在于栈中,而“gzc”则是该变量引用所指向的真正数据,它存在于字符串常量池中。
言归正传
字符串拼接主要有2种情况:
1、常量与常量拼接
String s1 = "g"+"zc";//常量“g”与常量“zc”拼接
常量与常量拼接的原理:
字符串常量与常量之间的拼接操作其实在未加载到JVM内存之前就已经完成了,即在编译期间就会对字符串常量之间的拼接操作进行优化如下图,
进行反编译后,我们不难发现在编译完之后,s4已经被直接拼接好了。而且此时s3和s4指向的是字符串常量池中的同一个对象,即两者存储的对象地址是相同的。
所以s3==s4其结果为true。
2、涉及到变量的字符串拼接
2.1 变量与常量拼接
String s1 = "g"; String s2 = s1+"zc";//变量s1与常量“zc”拼接
2.2 变量与变量拼接
String s1 = "g"; String s2 = "zc"; String s3 = s1+s2;//变量s1与变量s2拼接
涉及到变量的字符串拼接原理:
只要字符串拼接其中涉及到变量,不管是几个变量,那么其拼接原理都如下:
当涉及到变量时,字符串用+进行字符串拼接的本质,其实就是利用StringBuilder类里的append()方法,将每一个字符串都一一添加进去,然后返回一个StringBuilder对象,所以可以不用新创建一个对象去接收返回值,直接链式编程得到最终添加的结果,最后再调用toString()方法将其转换为我们想要的字符串String类型。
如下图:
特别注意:
StringBuilder的toString()方法调用的是String重载的构造器方法,是以字符数组为字符串实际内容进行创建的,并未直接以字面量方式创建String对象,即:
所以如果我们上述代码没有定义s3和s4两个变量,只定义了String s5 = s1+s2;
的话,那么其实字符串常量池中是不存在“gzc”这个字符串的,而是只有“g”和“zc”。
因为只有通过字面量定义一个字符串以及调用String的intern()方法,这两种方式才会在字符串常量池中生成对应的对象。
而StringBuilder调用toString()方法创建的String对象则会直接在堆中为其分配内存,常量池中不会存在对应的对象。
所以如果判断s3==s5
,则结果为false
,因为s3指向的是字符串常量池中的“gzc”,而s5指向的是堆中的“gzc”对象,二者指向的对象地址不同,则比较结果自然为false。
特殊情况:
若变量被声明为final类型,即为常量,则就遵循字符串常量拼接的规则了。
如下图:
jdk 1.8 对String字符串拼接并没有优化
String s = new String("1") + new String("1"); String s2 = s + "1" + "1" + "1"; //String s = "1" + "1"; String s1 = "11"; System.out.println(s.intern() == s1);
public static void main(java.lang.String[] args); 0 new java.lang.StringBuilder [16] 3 dup 4 new java.lang.String [18] 7 dup 8 ldc <String "1"> [20] 10 invokespecial java.lang.String(java.lang.String) [22] 13 invokestatic java.lang.String.valueOf(java.lang.Object) : java.lang.String [25] 16 invokespecial java.lang.StringBuilder(java.lang.String) [29] 19 new java.lang.String [18] 22 dup 23 ldc <String "1"> [20] 25 invokespecial java.lang.String(java.lang.String) [22] 28 invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [30] 31 invokevirtual java.lang.StringBuilder.toString() : java.lang.String [34] 34 astore_1 [s] 35 new java.lang.StringBuilder [16] 38 dup 39 aload_1 [s] 40 invokestatic java.lang.String.valueOf(java.lang.Object) : java.lang.String [25] 43 invokespecial java.lang.StringBuilder(java.lang.String) [29] 46 ldc <String "1"> [20] 48 invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [30] 51 ldc <String "1"> [20] 53 invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [30] 56 ldc <String "1"> [20] 58 invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [30] 61 invokevirtual java.lang.StringBuilder.toString() : java.lang.String [34] 64 astore_2 [s2] 65 ldc <String "11"> [38] 67 astore_3 [s1] 68 getstatic java.lang.System.out : java.io.PrintStream [40] 71 aload_1 [s] 72 invokevirtual java.lang.String.intern() : java.lang.String [46] 75 aload_3 [s1] 76 if_acmpne 83 79 iconst_1 80 goto 84 83 iconst_0 84 invokevirtual java.io.PrintStream.println(boolean) : void [49] 87 return Line numbers: [pc: 0, line: 18] [pc: 35, line: 19] [pc: 65, line: 21] [pc: 68, line: 22] [pc: 87, line: 23] Local variable table: [pc: 0, pc: 88] local: args index: 0 type: java.lang.String[] [pc: 35, pc: 88] local: s index: 1 type: java.lang.String [pc: 65, pc: 88] local: s2 index: 2 type: java.lang.String [pc: 68, pc: 88] local: s1 index: 3 type: java.lang.String Stack map table: number of frames 2 [pc: 83, full, stack: {java.io.PrintStream}, locals: {java.lang.String[], java.lang.String, java.lang.String, java.lang.String}] [pc: 84, full, stack: {java.io.PrintStream, int}, locals: {java.lang.String[], java.lang.String, java.lang.String, java.lang.String}] }
从class文件中可以看出,依然new了两个stringBuilder对象
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
最新评论