Java基础:字符串

前言

在开发过程中避免不了与字符串打交道,在使用字符串时,应该对字符串存放位置以及如果高效操作字符串有一个大体的认识。

正题

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


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值