您的位置:首页 > 编程语言 > Java开发

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)V
main函数:

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()方法示意图:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: