码迷,mamicode.com
首页 > 编程语言 > 详细

Java中的字符串

时间:2015-01-16 23:46:27      阅读:295      评论:0      收藏:0      [点我收藏+]

标签:

作者:禅楼望月(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.class
Compiled 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中的字符串

标签:

原文地址:http://www.cnblogs.com/yaoyinglong/p/Java字符串.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!