`
xzuse
  • 浏览: 42712 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

(转)Java 中String 与StringBuffer之间的区别 (网络搜集)

    博客分类:
  • Java
阅读更多

 

String的长度是不可变的,StringBuffer的长度是可变的。如果你对字符串中的内容经常进行操作,特别是内容要修改时,那么使用StringBuffer,如果最后需要String,那么使用StringBuffer的toString()方法

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

// 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会为字符串连接提供最佳的性能。 

 

 

 

分享到:
评论

相关推荐

    JAVA上百实例源码以及开源项目源代码

     Tcp服务端与客户端的JAVA实例源代码,一个简单的Java TCP服务器端程序,别外还有一个客户端的程序,两者互相配合可以开发出超多的网络程序,这是最基础的部分。 递归遍历矩阵 1个目标文件,简单! 多人聊天室 3...

    JAVA上百实例源码以及开源项目

     Tcp服务端与客户端的JAVA实例源代码,一个简单的Java TCP服务器端程序,别外还有一个客户端的程序,两者互相配合可以开发出超多的网络程序,这是最基础的部分。 递归遍历矩阵 1个目标文件,简单! 多人聊天室 3...

    JAVA面试题最全集

    9.Java中访问数据库的步骤,Statement和PreparedStatement之间的区别。 10.找出下列代码可能存在的错误,并说明原因: 二、JSP&Servlet技术 1.描述JSP和Servlet的区别、共同点、各自应用的范围 2.在Web开发中...

    张孝祥Java就业培训教程.pdf

    在以后的章节中,用通俗易懂的手法,紧密联系实际应用的方式,深入浅出地讲解了多线程,常用Java类,Java中的I/O(输入输出)编程,GUI与Applet,网络编程等方面的知识。 本书许多内容都来源于程序员圈子里的非正式...

    java 面试题 总结

    重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。子类的对象使用这个方法时,...

    Thinking in Java简体中文(全)

    12.4.4 String和StringBuffer类 12.4.5 字串的特殊性 12.5 总结 12.6 练习 第13章 创建窗口和程序片 13.1 为何要用AWT? 13.2 基本程序片 13.2.1 程序片的测试 13.2.2 一个更图形化的例子 13.2.3 框架方法的演示 ...

    java初学者必看

    最近正在学习Java,也买了很多的有关Java方面的书籍,其中发现《跟我学Java》这本书,都的很不错啊,所以顺便拿电脑把这本书的目录敲了下来,与大家分享。尤其是那些和我一样初学Java的朋友们,看看哪一节对你有用,...

    java联想(中文)

    12.4.4 String和StringBuffer类 12.4.5 字串的特殊性 12.5 总结 12.6 练习 第13章 创建窗口和程序片 13.1 为何要用AWT? 13.2 基本程序片 13.2.1 程序片的测试 13.2.2 一个更图形化的例子 13.2.3 框架方法的演示 ...

    Thinking in Java 中文第四版+习题答案

    12.4.4 String和StringBuffer类 12.4.5 字串的特殊性 12.5 总结 12.6 练习 第13章 创建窗口和程序片 13.1 为何要用AWT? 13.2 基本程序片 13.2.1 程序片的测试 13.2.2 一个更图形化的例子 13.2.3 框架方法的演示 ...

    java面试题

    2. String 和StringBuffer的区别 8 3. 运行时异常与一般异常有何异同? 8 4. 说出ArrayList,Vector,LinkedList的存储性能和特性 8 5. EJB是基于哪些技术实现的?并说出SessionBean和EntityBean的区别,StatefulBean...

    涵盖了90%以上的面试题

    String,StringBuffer,StringBuilder有什么区别 java中的数组是不是对象 数组初始化的方式有哪几种 length属性和length()方法有什么区别 finally块中的代码什么时候被执行 Java中的异常处理机制的简单原理和应用。 ...

    java 编程入门思考

    12.4.4 String和StringBuffer类 12.4.5 字串的特殊性 12.5 总结 12.6 练习 第13章 创建窗口和程序片 13.1 为何要用AWT? 13.2 基本程序片 13.2.1 程序片的测试 13.2.2 一个更图形化的例子 13.2.3 框架方法的演示 ...

    Java经典常识,绝对经典的常识,有堆栈问题,String。。。类,接口 算法。。。。

    Java经典常识,绝对经典的常识,有堆栈问题,String。。。自己搜集的,都是别人的精华,是所有人都称赞的!!!!

    Java初学者入门教学

    12.4.4 String和StringBuffer类 12.4.5 字串的特殊性 12.5 总结 12.6 练习 第13章 创建窗口和程序片 13.1 为何要用AWT? 13.2 基本程序片 13.2.1 程序片的测试 13.2.2 一个更图形化的例子 13.2.3 框架方法的演示 ...

    基于javatcpsocket通信的拆包和装包源码-java-interview:java基础知识点

    StringBuffer的区别 Collection&Collections区别 hashSet如何保证不重复 什么是线程同步 进程 和 线程 Lock 和 Synchronized 的区别 常见的内存溢出 重载和重写的区别 构造器Constructor是否能被override public ...

    java面试题,180多页,绝对良心制作,欢迎点评,涵盖各种知识点,排版优美,阅读舒心

    【基础】java中String、StringBuffer、StringBuilder的区别 21 【基础】运行时异常和非运行时异常 参见 21 运行时异常 21 非运行时异常 22 【基础】java引用类型 23 强引用(StrongReference) 23 软引用...

    JAVA_Thinking in Java

    12.4.4 String和StringBuffer类 12.4.5 字串的特殊性 12.5 总结 12.6 练习 第13章 创建窗口和程序片 13.1 为何要用AWT? 13.2 基本程序片 13.2.1 程序片的测试 13.2.2 一个更图形化的例子 13.2.3 框架方法的演示 ...

    超级有影响力霸气的Java面试题大全文档

    重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。子类的对象使用这个方法时,...

Global site tag (gtag.js) - Google Analytics