Java基础:字符串
2017-08-11 10:32
225 查看
前言
在开发过程中避免不了与字符串打交道,在使用字符串时,应该对字符串存放位置以及如果高效操作字符串有一个大体的认识。正题
String对象是不可变的。查看JDK文档你就会发现,String类中每一个看起来会修改String值的方法,实际上都是创建一个全新的String对象,以包含修改后的字符串内容。而最初的String对象则丝毫未动。String对象是不可变的,你可以给一个String对象加以任意多的别。因为String对象具有只读特性,所以指向它的任何引用都不可能改变它的值,因此,也就不会对其他的引用有什么影响。
不可变性会带来一定的效率问题。对String对象重载的“+”操作符就是一个例子。重载的意思是,一个操作符在引用于特定类时,被赋予了特殊的意义(用于String的“+”与“+=”是Java中仅有的两个重载过的操作符,而Java并不允许程序员重载任何操作符)
操作符“+”可以连接String:
public class StringOperation { public static void main(String[] args) { String str1 = "hel"; String str2 = str1 + "lo"; System.out.println(str2); } }main对应的字节码:
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=3, args_size=1 0: ldc #2 // String hel 2: astore_1 3: new #3 // class java/lang/StringBuilder 6: dup 7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 10: aload_1 11: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 14: ldc #6 // String lo 16: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 22: astore_2 23: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream; 26: aload_2 27: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 30: return从以上代码可以看出:编译器自动引用了java.lang.StringBuillder类。虽然我们在源码中并没有使用StringBuilder类,但是编译器却自作主张的使用了它,因为它更高效。现在也许你会觉得可以随意使用String对象,反正编译器会为你自动地优化性能。下面看看另外两种方式生成一个String:
public String implicit(String[] fields){ String result = ""; for(String str : fields){ result += str; } return result; } public String explicit(String[] fields){ StringBuilder result = new StringBuilder(); for(String str : fields) { result.append(str); } return result.toString(); }implicit()方法对应的字节码:
public java.lang.String implicit(java.lang.String[]); descriptor: ([Ljava/lang/String;)Ljava/lang/String; flags: ACC_PUBLIC Code: stack=2, locals=7, args_size=2 0: ldc #2 // String 2: astore_2 3: aload_1 4: astore_3 5: aload_3 6: arraylength 7: istore 4 9: iconst_0 10: istore 5 12: iload 5 14: iload 4 16: if_icmpge 51 19: aload_3 20: iload 5 22: aaload 23: astore 6 25: new #3 // class java/lang/StringBuilder 28: dup 29: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 32: aload_2 33: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 36: aload 6 38: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 41: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 44: astore_2 45: iinc 5, 1 48: goto 12 51: aload_2 52: areturn从字节码可以看出每一次循环都会创建一个新的StringBuilder对象。
explicit()方法对应的字节码:
public java.lang.String explicit(java.lang.String[]); descriptor: ([Ljava/lang/String;)Ljava/lang/String; flags: ACC_PUBLIC Code: stack=2, locals=7, args_size=2 0: new #3 // class java/lang/StringBuilder 3: dup 4: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 7: astore_2 8: aload_1 9: astore_3 10: aload_3 11: arraylength 12: istore 4 14: iconst_0 15: istore 5 17: iload 5 19: iload 4 21: if_icmpge 43 24: aload_3 25: iload 5 27: aaload 28: astore 6 30: aload_2 31: aload 6 33: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 36: pop 37: iinc 5, 1 40: goto 17 43: aload_2 44: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 47: areturn可以看出不仅循环部分的代码更简短、更简单,而且它只生成了一个StringBuilder对象。因此,当你为一个类编写toString()方法时,如果字符串操作比较简单,那就可以信赖编译器,它会为你合理的构造最终的字符串结果。但是,如果你要在toString()方法中使用循环,那么最好自己创建一个StringBuilder对象,用它来构造最终的结果。
接下来看看有关String操作的例子:
代码一
public class StringType { public static void main(String[] args) { String str1 = "string"; String str2 = new String("string"); String str3 = str2.intern(); System.out.println(str1 == str2); System.out.println(str1 == str3); } }
对应的class字节码:
Constant pool: #1 = Methodref #9.#22 // java/lang/Object."<init>":()V #2 = String #23 // string #3 = Class #24 // java/lang/String #4 = Methodref #3.#25 // java/lang/String."<init>":(Ljava/lang/String;)V #5 = Methodref #3.#26 // java/lang/String.intern:()Ljava/lang/String; #6 = Fieldref #27.#28 // java/lang/System.out:Ljava/io/PrintStream; #7 = Methodref #29.#30 // java/io/PrintStream.println:(Z)V #8 = Class #31 // com/java/container/StringType #9 = Class #32 // java/lang/Object #10 = Utf8 <init> #11 = Utf8 ()V #12 = Utf8 Code #13 = Utf8 LineNumberTable #14 = Utf8 main #15 = Utf8 ([Ljava/lang/String;)V #16 = Utf8 StackMapTable #17 = Class #33 // "[Ljava/lang/String;" #18 = Class #24 // java/lang/String #19 = Class #34 // java/io/PrintStream #20 = Utf8 SourceFile #21 = Utf8 StringType.java #22 = NameAndType #10:#11 // "<init>":()V #23 = Utf8 string #24 = Utf8 java/lang/String #25 = NameAndType #10:#35 // "<init>":(Ljava/lang/String;)V #26 = NameAndType #36:#37 // intern:()Ljava/lang/String; #27 = Class #38 // java/lang/System #28 = NameAndType #39:#40 // out:Ljava/io/PrintStream; #29 = Class #34 // java/io/PrintStream #30 = NameAndType #41:#42 // println:(Z)V #31 = Utf8 4000 com/java/container/StringType #32 = Utf8 java/lang/Object #33 = Utf8 [Ljava/lang/String; #34 = Utf8 java/io/PrintStream #35 = Utf8 (Ljava/lang/String;)V #36 = Utf8 intern #37 = Utf8 ()Ljava/lang/String; #38 = Utf8 java/lang/System #39 = Utf8 out #40 = Utf8 Ljava/io/PrintStream; #41 = Utf8 println #42 = Utf8 (Z)V { public com.java.container.StringType(); 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 7: 0 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=4, args_size=1 0: ldc #2 // String string 2: astore_1 3: new #3 // class java/lang/String 6: dup 7: ldc #2 // String string 9: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V 12: astore_2 13: aload_2 14: invokevirtual #5 // Method java/lang/String.intern:()Ljava/lang/String; 17: astore_3 18: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream; 21: aload_1 22: aload_2 23: if_acmpne 30 26: iconst_1 27: goto 31 30: iconst_0 31: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V 34: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream; 37: aload_1 38: aload_3 39: if_acmpne 46 42: iconst_1 43: goto 47 46: iconst_0 47: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V 50: return }现在开始看看main方法的执行过程:
0:ldc #2:加载常量池中的第二项(“String”)到栈中
2:astore_1:将栈顶引用型数值存入第二个本地变量
结果#1:str1指向的是常量池中的常量
3:new #3:创建一个String对象,并将其值压入栈顶
6:dup:复制栈顶数值并将复制值压入栈顶
7:ldc #2:加载常量池中的第二项(“String”)到栈中
9:invokevirtual #4:调用实例执行常量池第四项方法
12:astore_2:将栈顶引用型数值存入第三个本地变量
结果#2:str2指向堆中的对象
13:aload_2:将第三个引用类型本地变量推送至栈顶
14:invokevirtual #5:调用实例执行常量池第五项方法
17:astore_3:将栈顶引用型数值存入第四个本地变量
结果#3:str调用intern方法,会将str2中的("string")值复制到常量池中,但如果常量池已经存在该字符串,所以直接返回该字符串的引用,因此str1 = str3
18:getstatic #6:获取指定类的静态域,并将其值压入栈顶
21:aload_1:将第二个引用类型本地变量推送至栈顶
22:aload_2:将第三个引用类型本地变量推送至栈顶
23:if_acmpne 30:比较栈顶两引用型数值,当结果不相等时跳转
26:iconst_1:将int型1推送至栈顶
27:goto 31:无条件跳转
30:iconst_0:将int型0推送至栈顶
31:invokevirtual #7:调用实例执行常量池第七项方法
34:getstatic #6:获取指定类的静态域,并将其值压入栈顶
37:aload_1:将第二个引用类型本地变量推送至栈顶
38:aload_3:将第四个引用类型本地变量推送至栈顶
39:if_acmpne 46:比较栈顶两引用型数值,当结果不相等时跳转
42:iconst_1:将int型1推送至栈顶
43:goto 47:无条件跳转
46:iconst_0:将int型0推送至栈顶
47:invokevirtual #7:调用实例执行常量池第七项方法
50:return:从当前方法返回void
输出结果:false、true
代码二
public class StringType { public static void main(String[] args) { String baseStr = "baseStr"; final String baseFinalStr = "baseStr"; String str1 = "baseStr01"; String str2 = "baseStr"+"01"; String str3 = baseStr + "01"; String str4 = baseFinalStr+"01"; String str5 = new String("baseStr01").intern(); System.out.println(str1 == str2);//#3 System.out.println(str1 == str3);//#4 System.out.println(str1 == str4);//#5 System.out.println(str1 == str5);//#6 } }常量池:
Constant pool: #1 = Methodref #15.#28 // java/lang/Object."<init>":()V #2 = String #29 // baseStr #3 = String #30 // baseStr01 #4 = Class #31 // java/lang/StringBuilder #5 = Methodref #4.#28 // java/lang/StringBuilder."<init>":()V #6 = Methodref #4.#32 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #7 = String #33 // 01 #8 = Methodref #4.#34 // java/lang/StringBuilder.toString:()Ljava/lang/String; #9 = Class #35 // java/lang/String #10 = Methodref #9.#36 // java/lang/String."<init>":(Ljava/lang/String;)V #11 = Methodref #9.#37 // java/lang/String.intern:()Ljava/lang/String; #12 = Fieldref #38.#39 // java/lang/System.out:Ljava/io/PrintStream; #13 = Methodref #40.#41 // java/io/PrintStream.println:(Z)V #14 = Class #42 // com/java/container/StringType #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 #35 // java/lang/String #25 = Class #45 // java/io/PrintStream #26 = Utf8 SourceFile #27 = Utf8 StringType.java #28 = NameAndType #16:#17 // "<init>":()V #29 = Utf8 baseStr #30 = Utf8 baseStr01 #31 = Utf8 java/lang/StringBuilder #32 = NameAndType #46:#47 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #33 = Utf8 01 #34 = NameAndType #48:#49 // toString:()Ljava/lang/String; #35 = Utf8 java/lang/String #36 = NameAndType #16:#50 // "<init>":(Ljava/lang/String;)V #37 = NameAndType #51:#49 // intern:()Ljava/lang/String; #38 = Class #52 // java/lang/System #39 = NameAndType #53:#54 // out:Ljava/io/PrintStream; #40 = Class #45 // java/io/PrintStream #41 = NameAndType #55:#56 // println:(Z)V #42 = Utf8 com/java/container/StringType #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 (Ljava/lang/String;)V #51 = Utf8 intern #52 = Utf8 java/lang/System #53 = Utf8 out #54 = Utf8 Ljava/io/PrintStream; #55 = Utf8 println #56 = Utf8 (Z)Vmain函数:
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=8, args_size=1 0: ldc #2 // String baseStr 2: astore_1 3: ldc #3 // String baseStr01 5: astore_3 6: ldc #3 // String baseStr01 8: astore 4 10: new #4 // class java/lang/StringBuilder 13: dup 14: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V 17: aload_1 18: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 21: ldc #7 // String 01 23: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 26: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 29: astore 5 31: ldc #3 // String baseStr01 33: astore 6 35: new #9 // class java/lang/String 38: dup 39: ldc #3 // String baseStr01 41: invokespecial #10 // Method java/lang/String."<init>":(Ljava/lang/String;)V 44: invokevirtual #11 // Method java/lang/String.intern:()Ljava/lang/String; 47: astore 7 49: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream; 52: aload_3 53: aload 4 55: if_acmpne 62 58: iconst_1 59: goto 63 62: iconst_0 63: invokevirtual #13 // Method java/io/PrintStream.println:(Z)V 66: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream; 69: aload_3 70: aload 5 72: if_acmpne 79 75: iconst_1 76: goto 80 79: iconst_0 80: invokevirtual #13 // Method java/io/PrintStream.println:(Z)V 83: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream; 86: aload_3 87: aload 6 89: if_acmpne 96 92: iconst_1 93: goto 97 96: iconst_0 97: invokevirtual #13 // Method java/io/PrintStream.println:(Z)V 100: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream; 103: aload_3 104: aload 7 106: if_acmpne 113 109: iconst_1 110: goto 114 113: iconst_0 114: invokevirtual #13 // Method java/io/PrintStream.println:(Z)V 117: return
0:ldc #2:加载常量池第二项(“baseStr”)到栈中
2:astroe_1:将栈顶引用型数值存入第二个本地变量,即String baseStr = “baseStr”;
3:ldc #3:加载常量池第三项(“baseStr01”)到栈中
5:astore_3:将栈顶引用型数值存入到第四个本地变量,即String str1 = "baseStr01";
结果#1:str1指向常量池(“baseStr01”)引用
6:ldc #3:加载常量池第三项(“baseStr01”)到栈中
8:astore #4:将栈顶引用型数值存入到第五个本地变量,即String str2= "baseStr01";
结果#2:str2指向常量池(“baseStr01”)引用
10:new #4:创建一个StringBuilder对象,并将其引用值压入栈顶
13:dup:复制栈顶数值并将数值压入栈顶
14:invokevirtual #5:调用实例执行常量池第五项方法,即StringBuilder.<init>方法
17:aload_1:将第二个引用类型本地变量推送至栈顶,即“baseStr”
18:invokevirtual #6:调用实例执行常量池第六项方法,即StringBuilder.append方法
21:ldc #7:加载常量池第七项(“01”)到栈中
23:invokevirtual #6:调用实例执行常量池第六项方法,即StringBuilder.append方法
26:invokevirtual #8:调用实例执行常量池第六项方法,即StringBuilder.toString方法
29:astore #5:将栈顶引用型数值存入到第六个本地变量,即String str3= "baseStr01";
结果#3:str3指向堆对象
31:ldc #3:加载常量池第三项(“baseStr01”)到栈中
33:astore 6:将栈顶引用型数值存入到第七个本地变量,即String str4= "baseStr01";
结果#4:可以看出,对于final字段,编译期直接进项了常量替换,而对于非final字段则在运行时进行赋值处理
35:new #9:创建一个String对象,并将其引用值压入栈顶
38:dup:复制栈顶数值并将数值压入栈顶
39:ldc #3:加载常量池第三项(“baseStr01”)到栈中
41:invokevirtual #10:调用实例执行常量池第十项方法,即String.<init>方法
44:invokevirtual #11:调用实例执行常量池第十一项方法,即String.intern方法
47:astore 7:将栈顶引用型数值存入到第八个本地变量,即String str5= "baseStr01";
结果#5:调用intern方法,会将“baseStr01”值复制到常量池中,但如果常量池已经存在该字符串,所以直接返回该字符串的引用
输出结果:true、false、true、true
代码三:
public static void main(String[] args) { String str1 = new StringBuilder("计算机").append("软件").toString(); System.out.println(str1.intern() == str1); String str2 = new StringBuilder("ja").append("va").toString(); System.out.println(str2.intern() == str2); }这段代码在JDK1.6中运行,会得到两个false,而在JDK1.7中运行,会得到一个true和一个false。产生差异的原因是:在JDK1.6中,intern()方法会把首次遇到的字符串实例复制到永久代中,返回的也是永久代中这个字符串实例引用,而由StringBuilder创建的字符串实例在Java堆上,所以必然不是同一个引用,将返回false。而JDK1.7(以及部分其他虚拟机,例如JRockit)的intern实例不会再复制实例,只是在常量池中记录首次出现的实例引用,因此intern()返回的引用和StringBuilder创建的那个字符串实例是同一个。对str2比较返回false是因为“java”这个字符串在执行StringBuilder.toString()之前已经出现过,字符串常量池中已经有它的引用了,不符合“首次出现”的原则,而“计算机软件”这个字符串则是首次出现的,因此放回true。
JDK1.6执行String.intern()方法示意图:
JDK1.7执行String.intern()方法示意图:
相关文章推荐
- Java-基础 查找字符串最后一次出现的位置
- [Java基础知识点]字符串和数字间的转换
- JAVA语言基础之字符串
- Java基础之字符串的编码(Encode)和解码(Decode)
- Java基础之字符串创建于存储的机制
- 字符串处理是许多程序中非常重要的一部分,它们可以用于文本显示,数据表示,查找键和很多目的.在Unix下,用户可以使用正则表达式的强健功能实现这些 目的,从Java1.4起,Java核心API就引入了java.util.regex程序包,它是一种有价值的基础
- Java基础知识强化46:StringBuffer类之判断一个字符串是否对称案例
- JAVA代码—算法基础:周期字符串
- java基础之增强for循环和jdk字符串
- java基础——JAVA中创建字符串的两种方式的区别
- java_基础知识_字符串练习题_计算两个字符串的最长公共字串长度
- Java之数据结构基础、线性表、栈和队列、数组和字符串,树—学习笔记
- java基础讲解06-----字符串
- Java学习笔记-《Java程序员面试宝典》-第四章基础知识-4.5字符串与数组(4.5.1-4.5.3)
- 黑马程序员_Java基础_字符串_13
- Java基础知识强化75:正则表达式之分割功能(字符串中的数字排序案例)
- java基础---------字符串常量池-创建了几个对象
- Java基础之写文件——将多个字符串写入到文件中(WriteProverbs)
- 求字符串空格、数字、字母个数--JAVA基础