首页 > 代码库 > Java中的字符串

Java中的字符串

作者:禅楼望月(http://www.cnblogs.com/yaoyinglong/

1.字符串可以被GC回收了

我们之前在表达式的陷阱中就说到“对于Java程序中的字符直接量,JVM会使用一个字符串池来保护他们:当第一次使用某个字符串直接时,JVM会将它们放入字符串池进行缓存。”在jdk1.7之前HotSpot将该字符串常量池放在永久代中,所以当初我们还说“在一般情况下,字符串缓冲池中字符串对象不会被垃圾回收”,但是jdk1.7以后HotSpot就将字符串常量池从永久代中移出。因此我们看到如下程序,并不会导致内存溢出:

技术分享

public class StringTest {    public static void main(String[] args){        List<String> list=new ArrayList<String>();        int i=0;        while(true){            list.add(String.valueOf(i++).intern());        }    }}

这段代码在jdk1.7中会一直循环下去,但是在jdk1.6中会报“OutOfMemoryError:PermGen space”错误。这样java字符串常量池就回归到了堆内存中,接受垃圾回收器的垃圾回收。

2.String是不可变的

创建好一个String之后,它就不会再被改变了,查看JDK文档会发现,String类中看似修改了String值的方法,实际上都是创建了一个全新的String对象,以包含修改后的字符串内容,而最初的String对象纹丝未动。

3.String重载了“+”运算符

Java不允许Java程序员重载运算符。

public class StringTest {    public static void main(String[] args){        String mango="mango";        String s="abc"+mango+"def"+47;        System.out.println(s);    }}

通过javap来反编译以上代码:

E:\program\Thinking_in_Java\bin\string>javap -c StringTest.classCompiled from "StringTest.java"public class string.StringTest {  public string.StringTest();    Code:       0: aload_0       1: invokespecial #8                  // Method java/lang/Object."<init>":()V       4: return  public static void main(java.lang.String[]);    Code:       0: ldc           #16                 // String mango       2: astore_1       3: new           #18                 // class java/lang/StringBuilder       6: dup       7: ldc           #20                 // String abc       9: invokespecial #22                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V      12: aload_1      13: invokevirtual #25                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;      16: ldc           #29                 // String def      18: invokevirtual #25                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;      21: bipush        47      23: invokevirtual #31                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;      26: invokevirtual #34                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;      29: astore_2      30: getstatic     #38                 // Field java/lang/System.out:Ljava/io/PrintStream;      33: aload_2      34: invokevirtual #44                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V      37: return}

从上面的的反编译代码我们可以清楚的看到,编译器默认使用StringBuilder对我们的字符串做了优化。这样就避免了直接使用String和“+”运算符拼接所产生的中间垃圾。既然,JDK已经为我们做了字符串的优化,那我们是不是肆无忌怛的使用“+”了呢?那么我们再来看看编译器到底为我们优化到什么程度:

public class StringTest {    public String implicit(String[] fields){        String result="";        for(int i=0; i<fields.length; i++){            result+=fields[i];        }        return result;    }    public String explicit(String[] fields){        StringBuilder result=new StringBuilder();        for(int i=0; i<fields.length; i++){            result.append(fields[i]);        }        return result.toString();    }    public static void main(String[] args){    }}

首先我们查看反编译后的第一个函数:

public java.lang.String implicit(java.lang.String[]);  Code:     0: ldc           #16                 // String     2: astore_2     3: iconst_0     4: istore_3     5: goto          32     8: new           #18                 // class java/lang/StringBuilder    11: dup    12: aload_2    13: invokestatic  #20                 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;    16: invokespecial #26                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V    19: aload_1    20: iload_3    21: aaload    22: invokevirtual #29                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;    25: invokevirtual #33                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;    28: astore_2    29: iinc          3, 1    32: iload_3    33: aload_1    34: arraylength    35: if_icmplt     8    38: aload_2    39: areturn

从上面反编译的代码可以看出,从第4到第32行是for循环体,同时我们也看到,StringBuilder是在for循环中创建的,即每循环一次就创建一新的StringBuilder。再看看第二个函数:

public java.lang.String explicit(java.lang.String[]);  Code:     0: new           #18                 // class java/lang/StringBuilder     3: dup     4: invokespecial #45                 // Method java/lang/StringBuilder."<init>":()V     7: astore_2     8: iconst_0     9: istore_3    10: goto          24    13: aload_2    14: aload_1    15: iload_3    16: aaload    17: invokevirtual #29                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;    20: pop    21: iinc          3, 1    24: iload_3    25: aload_1    26: arraylength    27: if_icmplt     13    30: aload_2    31: invokevirtual #33                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;    34: areturn

不仅代码缩短了,而且整个方法中只在for循环的外面生成了一个StringBuilder对象。

总结:循环中处理字符串最好自己创建一个StringBuilder对象,并用它来构造最终的结果,循环之外则可以信赖编译器对String的优化。

 

4.一个陷阱:

public class StringTest {    public static void main(String[] args){        String  str1="abc";        StringBuilder sb=new StringBuilder();        sb.append("is true? ");        sb.append(str1+str1.length());        System.out.println(sb.toString());    }}

这段代码,编译器会怎么处理呢?

public static void main(java.lang.String[]);  Code:     0: ldc           #16                 // String abc     2: astore_1     3: new           #18                 // class java/lang/StringBuilder     6: dup     7: invokespecial #20                 // Method java/lang/StringBuilder."<init>":()V    10: astore_2    11: aload_2    12: ldc           #21                 // String is true?    14: invokevirtual #23                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;    17: pop    18: aload_2    19: new           #18                 // class java/lang/StringBuilder    22: dup    23: aload_1    24: invokestatic  #27                 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;    27: invokespecial #33                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V    30: aload_1    31: invokevirtual #36                 // Method java/lang/String.length:()I    34: invokevirtual #40                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;    37: invokevirtual #43                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;    40: invokevirtual #23                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;    43: pop    44: getstatic     #47                 // Field java/lang/System.out:Ljava/io/PrintStream;    47: aload_2    48: invokevirtual #43                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;    51: invokevirtual #53                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V    54: return

由上面代码我们可以看出,在sb.append(str1+str1.length()); 这句代码中,编译器还是采取了创建一个StringBuilder在拼接括号里面的字符串,然后将这个StringBuilder的toString传递给外围的StringBuilder。

结论:禁止在StringBuilder的append中使用“+”。如果StringBuilder处于循环中就更糟糕了。

Java中的字符串