honzeland

记录点滴。。。

常用链接

统计

Famous Websites

Java

Linux

P2P

最新评论

String和StringBuffer之概览

String StringBuffer 之概览
  非可变对象一旦创建之后就不能再被改变,可变对象则可以在创建之后被改变。 String 对象是非可变对象, StringBuffer 对象则是可变对象。为获得更佳的性能你需要根据实际情况小心谨慎地选择到底使用这两者中的某一个。下面的话题会作详细的阐述。(注意:这个章节假设读者已经具备 Java String StringBuffer 的相关基础知识。)
 
创建字符串的较佳途径
你可以按照以下方式创建字符串对象:
1. String s1 = "hello"; 
    String s2 = "hello"; 
2. String s3 = new String("hello");
    String s4 = new String("hello");
 
上面哪种方式会带来更好的性能呢?下面的代码片断用来测量二者之间的区别。

StringTest1.java
package com.performance.string;
/** This class shows the time taken for creation of
 *  String literals and String objects.
 */
public class StringTest1 {
public static void main(String[] args){
    // create String literals
    long startTime = System.currentTimeMillis();
    for(int i=0;i<50000;i++){
    String s1 = "hello";
    String s2 = "hello";
    }
    long endTime = System.currentTimeMillis();
    System.out.println("Time taken for creation of String literals : "
                  + (endTime - startTime) + " milli seconds" );
    // create String objects using 'new' keyword       
    long startTime1 = System.currentTimeMillis();
    for(int i=0;i<50000;i++){
    String s3 = new String("hello");
    String s4 = new String("hello");
    }
    long endTime1 = System.currentTimeMillis();
    System.out.println("Time taken for creation of String objects : "
                  + (endTime1 - startTime1)+" milli seconds");
    }
}
这段代码的输出:
Time taken for creation of String literals : 0 milli seconds
Time taken for creation of String objects : 170 milli seconds
 
JVM
是怎样处理字符串的呢?
   Java 虚拟机会维护一个内部的滞留字符串对象的列表(唯一字符串的池)来避免在堆内存中产生重复的 String 对象。当 JVM class 文件里加载字符串字面量并执行的时候,它会先检查一下当前的字符串是否已经存在于滞留字符串列表,如果已经存在,那就不会再创建一个新的 String 对象而是将引用指向已经存在的 String 对象, JVM 会在内部为字符串字面量作这种检查,但并不会为通过 new 关键字创建的 String 对象作这种检查。当然你可以明确地使用 String.intern() 方法强制 JVM 为通过  new 关键字创建的 String 对象作这样的检查。这样可以强制 JVM 检查内部列表而使用已有的 String 对象。
  所以结论是, JVM 会内在地为字符串字面量维护一些唯一的 String 对象,程序员不需要为字符串字面量而发愁,但是可能会被一些通过  new 关键字创建的 String 对象而困扰,不过他们可以使用 intern() 方法来避免在堆内存上创建重复的 String 对象来改善 Java 的运行性能。下一小节会向大家展示更多的信息。
 
下图展示了未使用 intern() 方法来创建字符串的情况。
 
string_creating_without_intern() method
  你可以自己使用 == 操作符和 String.equals() 方法来编码测试上面提到的区别。 == 操作符会返回 true 如果一些引用指向一个相同的对象但不会判断 String 对象的内容是否相同; String.equals() 方法会返回 true 如果被操作的 String 对象的内容相同。对于上面的代码会有 s1==s2 ,因为 s1 s2 两个引用指向同一个对象,对于上面的代码, s3.equals(s4) 会返回 true 因为两个对象的内容都一样为 ”hello” 。你可以从上图看出这种机制。在这里有三个独立的包含了相同的内容( ”hello” )的对象,实际上我们不需要这么三个独立的对象 ——  因为要运行它们的话既浪费时间又浪费内存。
 
  那么怎样才能确保 String 对象不会重复呢?下一个话题会涵盖对于内建 String 机制的兴趣。
 
滞留字符串的优化作用
  同一个字符串对象被重复地创建是不必要的, String.intern () 方法可以避免这种情况。下图说明了 String.intern() 方法是如何工作的, String.intern() 方法检查字符串对象的存在性,如果需要的字符串对象已经存在,那么它会将引用指向已经存在的字符串对象而不是重新创建一个。下图描绘了使用了 intern() 方法的字符串字面量和字符串对象的创建情况。
 
string_creating_with_intern() method
下面的例程帮助大家了解 String.intern() 方法的重要性。
StringTest2.java
 
package com.performance.string;
// This class shows the use of intern() method to improve performance
public class StringTest2 {
public static void main(String[] args){
    // create String references like s1,s2,s3...so on..
    String variables[] = new String[50000];
    for( int i=0;i<variables.length;i++){
        variables[i] = "s"+i;
    }
    // create String literals
    long startTime0 = System.currentTimeMillis();
    for(int i=0;i<variables.length;i++){
        variables[i] = "hello";
    }
    long endTime0 = System.currentTimeMillis();
    System.out.println("Time taken for creation of String literals : "
                         + (endTime0 - startTime0) + " milli seconds" );
    // create String objects using 'new' keyword       
    long startTime1 = System.currentTimeMillis();
    for(int i=0;i<variables.length;i++){
        variables[i] = new String("hello");
    }
    long endTime1 = System.currentTimeMillis();
    System.out.println("Time taken for creation of String objects with 'new' key word : "
                        + (endTime1 - startTime1)+" milli seconds");
    // intern String objects with intern() method   
    long startTime2 = System.currentTimeMillis();
    for(int i=0;i<variables.length;i++){
        variables[i] = new String("hello");
        variables[i] = variables[i].intern();
    }
    long endTime2 = System.currentTimeMillis();
    System.out.println("Time taken for creation of String objects with intern(): "
                        + (endTime2 - startTime2)+" milli seconds");
    }
}
这是上面那段代码的输出结果:
Time taken for creation of String literals : 0 milli seconds
Time taken for creation of String objects with 'new' key word : 160 milli seconds
Time taken for creation of String objects with intern(): 60 milli seconds
 
连接字符串时候的优化技巧
  你可以使用 + 操作符或者 String.concat() 或者 StringBuffer.append() 等办法来连接多个字符串,那一种办法具有最佳的性能呢?
  如何作出选择取决于两种情景,第一种情景是需要连接的字符串是在编译期决定的还是在运行期决定的,第二种情景是你使用的是  StringBuffer 还是 String 。通常程序员会认为 StringBuffer.append() 方法会优于 + 操作符或  String.concat() 方法,但是在一些特定的情况下这个假想是不成立的。
 
1) 
第一种情景:编译期决定相对于运行期决定
请看下面的 StringTest3.java 代码和输出结果。

package com.performance.string;
/** This class shows the time taken by string concatenation at compile time and run time.*/
public class StringTest3 {
  public static void main(String[] args){
    //Test the String Concatination
    long startTime = System.currentTimeMillis();
    for(int i=0;i<5000;i++){
    String result = "This is"+ "testing the"+ "difference"+ "between"+
            "String"+ "and"+ "StringBuffer";
    }
    long endTime = System.currentTimeMillis();
    System.out.println("Time taken for string concatenation using + operator : "
         + (endTime - startTime)+ " milli seconds");
    //Test the StringBuffer Concatination
    long startTime1 = System.currentTimeMillis();
    for(int i=0;i<5000;i++){
    StringBuffer result = new StringBuffer();
         result.append("This is");
        result.append("testing the");
        result.append("difference");
        result.append("between");
       result.append("String");
       result.append("and");
       result.append("StringBuffer");
     }
    long endTime1 = System.currentTimeMillis();
    System.out.println("Time taken for String concatenation using StringBuffer : "
           + (endTime1 - startTime1)+ " milli seconds");
  }
}
这是上面的代码的输出结果:
Time taken for String concatenation using + operator : 0 milli seconds
Time taken for String concatenation using StringBuffer : 50 milli seconds
很有趣地, + 操作符居然比 StringBuffer.append() 方法要快,为什么呢?
 
  这里编译器的优化起了关键作用,编译器像下面举例的那样简单地在编译期连接多个字符串。它使用编译期决定取代运行期决定,在你使用 new 关键字来创建 String 对象的时候也是如此。
 
编译前:
String result = "This is"+"testing the"+"difference"+"between"+"String"+"and"+"StringBuffer";
编译后:
String result = "This is testing the difference between String and StringBuffer";

这里 String 对象在编译期就决定了而 StringBuffer 对象是在运行期决定的。运行期决定需要额外的开销当字符串的值无法预先知道的时候,编译期决定作用于字符串的值可以预先知道的时候,下面是一个例子。
 
编译前:
public String getString(String str1,String str2) {
    return str1+str2;
}
编译后:
return new StringBuffer().append(str1).append(str2).toString();
运行期决定需要更多的时间来运行。
 
2) 
第二种情景:使用 StringBuffer 取代 String
看看下面的代码你会发现与情景一相反的结果 —— 连接多个字符串的时候 StringBuffer 要比 String 快。
StringTest4.java
 
package com.performance.string;
/** This class shows the time taken by string concatenation
using + operator and StringBuffer  */
public class StringTest4 {
 public static void main(String[] args){
    //Test the String Concatenation using + operator
    long startTime = System.currentTimeMillis();
    String result = "hello";
    for(int i=0;i<1500;i++){
        result += "hello";
    }
    long endTime = System.currentTimeMillis();
    System.out.println("Time taken for string concatenation using + operator : "
                  + (endTime - startTime)+ " milli seconds");
    //Test the String Concatenation using StringBuffer
    long startTime1 = System.currentTimeMillis();
    StringBuffer result1 = new StringBuffer("hello");
    for(int i=0;i<1500;i++){
        result1.append("hello");
    }
    long endTime1 = System.currentTimeMillis();
    System.out.println("Time taken for string concatenation using StringBuffer :  "
                  + (endTime1 - startTime1)+ " milli seconds");
    }
}
这是上面的代码的输出结果:
Time taken for string concatenation using + operator : 280 milli seconds
Time taken for String concatenation using StringBuffer : 0 milli seconds
看得出 StringBuffer.append() 方法要比 + 操作符要快得多,为什么呢?

  原因是两者都是在运行期决定字符串对象,但是 + 操作符使用不同于 StringBuffer.append() 的规则通过 String StringBuffer 来完成字符串连接操作。(译注:什么样的规则呢?)
 
借助 StringBuffer 的初始化过程的优化技巧
  你可以通过 StringBuffer 的构造函数来设定它的初始化容量,这样可以明显地提升性能。这里提到的构造函数是 StringBuffer(int length) length 参数表示当前的 StringBuffer 能保持的字符数量。你也可以使用 ensureCapacity(int minimumcapacity) 方法在 StringBuffer 对象创建之后设置它的容量。首先我们看看 StringBuffer 的缺省行为,然后再找出一条更好的提升性能的途径。
 
StringBuffer
的缺省行为:
   StringBuffer 在内部维护一个字符数组,当你使用缺省的构造函数来创建 StringBuffer 对象的时候,因为没有设置初始化字符长度, StringBuffer 的容量被初始化为 16 个字符,也就是说缺省容量就是 16 个字符。当 StringBuffer 达到最大容量的时候,它会将自身容量增加到当前的 2 倍再加 2 ,也就是( 2* 旧值 +2 )。
  如果你使用缺省值,初始化之后接着往里面追加字符,在你追加到第 16 个字符的时候它会将容量增加到 34 2*16+2 ),当追加到 34 个字符的时候就会将容量增加到 70 2*34+2 )。无论何事只要 StringBuffer 到达它的最大容量它就不得不创建一个新的字符数组然后重新将旧字符和新字符都拷贝一遍 —— 这也太昂贵了点。所以总是给 StringBuffer 设置一个合理的初始化容量值是错不了的,这样会带来立竿见影的性能增益。
  我利用两个 StringBuffer 重新测试了上面的 StringTest4.java 代码,一个未使用初始化容量值而另一个使用了。这次我追加了 50000 ’hello’ 对象没有使用 + 操作符。区别是我使用 StringBuffer(250000) 的构造函数来初始化第二个  StringBuffer 了。
 
输出结果如下:
Time taken for String concatenation using StringBuffer with out setting size: 280 milli seconds
Time taken for String concatenation using StringBuffer with setting size: 0 milli seconds
StringBuffer
初始化过程的调整的作用由此可见一斑。所以,使用一个合适的容量值来初始化 StringBuffer 永远都是一个最佳的建议。
 
关键点
1. 
无论何时只要可能的话使用字符串字面量来常见字符串而不是使用 new 关键字来创建字符串。
2. 
无论何时当你要使用 new 关键字来创建很多内容重复的字符串的话,请使用 String.intern() 方法。
3. +
操作符会为字符串连接提供最佳的性能 —— 当字符串是在编译期决定的时候。
4. 
如果字符串在运行期决定,使用一个合适的初期容量值初始化的 StringBuffer 会为字符串连接提供最佳的性能。

 

posted on 2006-11-10 10:44 honzeland 阅读(191) 评论(0)  编辑  收藏 所属分类: JAVA


只有注册用户登录后才能发表评论。


网站导航: