深入解析java.lang.String

 

   java.lang.String作为一个最常用的类,出现在各种各样的编程环境中。而其实现方式的不同可能造成运行效率的大大不同。只有深入了解JVM对String的实现机制,才能更好的利用String。

  • String的不变性

    String是一个final类,因此它不能被继承,同时,用以存储字符串内容的char value[]数组是private、final的,因此同样不能改变它的内容。下面是String类的源码:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

...

    那么在JVM中是怎么对String进行更新处理的呢?答案是新建一个String类,将原引用指向这个新String类。

public class Main {

    public static String test(String s){
        return s.toUpperCase();
    }

    public static void main(String []args){
        String s1 = "asd";
        String s2 = test(s1);
        System.out.println("s1 is : " + s1 + " and s2 is : " + s2);
    }

}


/××
s1 is : asd and s2 is : ASD
×/

    s1作为一个拷贝传递给了test函数,返回了一个新的对象引用,而s1本身并没有改变。

    但是不变性造成的一个麻烦就是浪费空间,降低效率,如下代码:    

String s = "a" + "b" + "c";

    如果不考虑JVM所做的优化(直接生成一个字符串"abcd",而不是运行时合成;或优化使用 StringBuilder),那么这句语句在执行的时候就会先生成一个"a",然后"a"和"b"组合生成字符串"ab",然后生成"abc",其效率可想而知(据说java最先版本就是如此)。但是上面这个例子,由于都是不可变的字符串,因此JVM会一次性合成"abc",进行优化。那么合成串中包含变量呢?代码如下:

String a = "adsf";
String s = "a" +"b" +"c" + a;

    JVM在处理这段代码的时候就会用到StringBuiler类。

 

  • StringBuiler

    StringBuiler是一个可变的普通类。可以调用append()的方法直接在当前串后面加上新串,而不会生成新的StringBuilder,它是final的,但是,它所实现的AbstractStringBuiler抽象类中,储存值的char[] value不是final的,这点与String不同。

    使用类似上面String的代码加以说明,运行后s1和s2的值同时改变了,可见s.append()并不是返回一个新串,而是在引用指向的对象上动手脚。

public class Main {

    public static StringBuilder test(StringBuilder s){
        return s.append("d");
    }

    public static void main(String []args){
        StringBuilder s1 = new StringBuilder("abc");
        StringBuilder s2 = s1;
        test(s1);
        System.out.println("s1 is : " + s1 + " and s2 is : " + s2);
    }

}

/××
s1 is : abcd and s2 is : abcd
×/

    回到刚才声明String的代码,

String a = "adsf";
String s = "a" +"b" +"c" + a;

    让我们看一看JVM是怎么处理它的,通过javap -c Main命令查看Main的jvm汇编程序:

Compiled from "Main.java"
public class Main {
  public Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String adsf
       2: astore_1
       3: new           #3                  // class java/lang/StringBuilder
       6: dup
       7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      10: ldc           #5                  // String abc
      12: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      15: aload_1
      16: invokevirtual #6                  // 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: return
}

先说说几个指令的含义:

  • ldc:将int、float或String型常量值从常量池中推送至栈顶
  • astore_n:将栈顶数值存入当前frame的局部变量数组中指定下标(n)处的变量中,栈顶数值出栈。
  • new:创建一个对象,并且其引用进栈
  • dup:复制栈顶数值,并且复制值进栈
  • invoke...:调用方法
  • aload_n:当前frame的局部变量数组中下标为n的引用型局部变量进栈

    main函数中,首先直接合成字符串"adsf"放入栈顶,然后存入局部变量s。接下来创建一个StringBuiler类,初始化,接着调用append()方法将abc加入,再次调用append()加入adsf,然后调用toString()输出。

    可见在复杂的String增长中,JVM基本上都会对String进行优化,使用StringBuiler。但是这个优化并不是始终最好的,不能认为有JVM就高枕无忧:

public static void main(String []args){
        String add[] = "q w e r t y u i o p".split(" ");
        String str = "";
        for(int i = 0;i < add.length;i++){
            str += add[i];
        }
    }

    以上代码在一个循环中,每次为str加上一个String,看看它的实现:

public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String q w e r t y u i o p
       2: ldc           #3                  // String
       4: invokevirtual #4                  // Method java/lang/String.split:(Ljava/lang/String;)[Ljava/lang/String;
       7: astore_1
       8: ldc           #5                  // String
      10: astore_2
      11: iconst_0
      12: istore_3
      13: iload_3
      14: aload_1
      15: arraylength
      16: if_icmpge     46
      19: new           #6                  // class java/lang/StringBuilder
      22: dup
      23: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
      26: aload_2
      27: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      30: aload_1
      31: iload_3
      32: aaload
      33: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      36: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      39: astore_2
      40: iinc          3, 1
      43: goto          13
      46: return

    可以看到,在循环体中(13~43),每次循环,都会新建一个StringBuiler对象。显然JVM在此处犯蠢了,因此我们不应该寄希望于JVM进行优化,更好的方法是直接将str声明为StringBuiler:

public static void main(String []args){
        String add[] = "q w e r t y u i o p".split(" ");
        StringBuilder str = new StringBuilder();
        for(int i = 0;i < add.length;i++){
            str.append(add[i]);
        }
    }

    这样就只会有一个StringBuiler对象被创建:

public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String q w e r t y u i o p
       2: ldc           #3                  // String
       4: invokevirtual #4                  // Method java/lang/String.split:(Ljava/lang/String;)[Ljava/lang/String;
       7: astore_1
       8: new           #5                  // class java/lang/StringBuilder
      11: dup
      12: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
      15: astore_2
      16: iconst_0
      17: istore_3
      18: iload_3
      19: aload_1
      20: arraylength
      21: if_icmpge     38
      24: aload_2
      25: aload_1
      26: iload_3
      27: aaload
      28: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      31: pop
      32: iinc          3, 1
      35: goto          18
      38: return

    因此,了解String和StringBuiler的实现,对写出高效的程序很有必要。

  • StringBuffer

    有过了解的应该还知道有一个StringBuffer的类,它同样实现了AbstractStringBuiler接口,大部分函数和StringBuiler功能相同,但是区别就在于这些函数前面加了synchronized关键字,保证线程安全。因此其运行速度要比StringBuiler稍微慢一些,但是保证了多线程下数据的准确性。这里就不再多说了。

转载于:https://my.oschina.net/u/2248183/blog/909924

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值