首页 > 代码库 > String、StringBuffer和StringBuilder
String、StringBuffer和StringBuilder
一、String和StringBuffer
String类型和StringBuffer类型的主要性能区别其实在于String是不可变的对象,因此在每次对String类型进行改变的时候其实都等同于生成了一个新的String对象,然后将指针指向新的String对象,所以经常改变内容的字符串最好不要用String,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象对了以后,JVM的GC就会开始工作,那速度是一定会相当慢的。
具体说原理的话,两个字符串相加,相当于执行了如下操作:
Str1+str2执行了下面的过程:
StringBuffer sb1 = new StringBuffer(str1);
sb1.append(str2);
String result1 = sb1.toString();
执行到最后,我们所需要的内容只有result1这一个对象,中间出现的sb1就成为了垃圾回收的目标。
此时,如果我们再加一个字符串的话……
str1+str2+str3相当于在上面的基础上又执行了
StringBuffersb2 = new StringBuffer(result1);
sb2.append(str3);
String result2 = sb2.toString();
这时,对我们有用的对象只有result2一个中间生成的对象都成为了垃圾回收的目标。如果继续追加下去,又会产生若干个StringBuffer的垃圾对象和String的垃圾对象。
而如果是使用Stringbuffer类则结果就不一样了,每次结果都会对StringBuffer对象本身进行操作,而不是生成新的对象,再改变对象引用。
StringBuffersb = new StringBuffer();
sb.append(str1);
sb.append(str2);
……
sb.append(strN);
String result = sb.toString();
除了中间的一个StringBuffer对象最后会被弃掉,其他的都是有效对象,效率自然会高。
package SE.AboutString; import java.util.Calendar; /** * * <p> * Description: 测试String和StringBuffer的效率 * </p> * @author zhangjunshuai * @version 1.0 * Create Date: 2014-7-7 下午5:25:57 * Project Name: UseTest * * <pre> * Modification History: * Date Author Version Description * ----------------------------------------------------------------------------------------------------------- * LastChange: $Date:: $ $Author: $ $Rev: $ * </pre> * */ public class StringMerger { public static String merge(String[] strings, String separator, boolean isSepAtTail) { if (strings == null) { return null; } if (separator == null) { separator = ""; } String mergedString = new String(); for (int i = 0; i < strings.length - 1; i++) { mergedString += (strings[i] + separator); } mergedString += strings[strings.length - 1]; if (isSepAtTail) { mergedString += separator; } return mergedString; } public static String bufferMerge(String[] strings, String separator, boolean isSepAtTail) { if (strings == null) { return null; } if (separator == null) { separator = ""; } StringBuffer mergeSb = new StringBuffer(); for (int i = 0; i < strings.length - 1; i++) { mergeSb.append(strings[i]).append(separator); } mergeSb.append(strings[strings.length - 1]); if (isSepAtTail) { mergeSb.append(separator); } return mergeSb.toString(); } public static void main(String[] args) { // call the two methods to initialize the class and methods. System.out.println(StringMerger.merge(new String[] {"a", "kk", "ef"}, " ^_^ ", false)); System.out.println(StringMerger.bufferMerge(new String[] {"a", "kk", "ef"}, " ^_^ ", false)); final int n = 10000; final String sep = System.getProperty("line.separator"); // create an array of String for merging. String[] forMerge = new String[n]; for (int i = 0; i < n; i++) { forMerge[i] = Integer.toBinaryString(i); } // declare two variables to store start time and end time. long startTime = 0; long endTime = 0; // run the test code 5 times. for (int i = 0; i < 5; i++) { System.out.println("==="); // get current time as start time. startTime = Calendar.getInstance().getTimeInMillis(); // merge string by using String. StringMerger.bufferMerge(forMerge, sep, false); // get current time as end time. endTime = Calendar.getInstance().getTimeInMillis(); // print out the result. System.out.println("merge by StringBuffer start: " + startTime); System.out.println("merge time: " + (endTime - startTime)); System.out.println("merge by StringBuffer end: " + endTime); // get current time as start time. startTime = Calendar.getInstance().getTimeInMillis(); // merge string by using StringBuffer. StringMerger.merge(forMerge, sep, false); // get current time as end time. endTime = Calendar.getInstance().getTimeInMillis(); // print out the result. System.out.println("merge by String start: " + startTime); System.out.println("merge time: " + (endTime - startTime)); System.out.println("merge by String end: " + endTime); } } }
二、关于线程安全
线程安全就是多个线程修改同一个对象时可能产生的冲突问题。比如有一个StringBuilder对象,变量名为stringBuffer,在一个线程里执行stringBuffer.append("0")的同时,另外一个线程也执行同样的代码,就有可能出现无法预料的问题。出现问题的原因是在StringBuilder的append方法中,不是只有一条语句,而是由若干语句。当进程A进入append()函数时,另一个线程B可能在其中任意一条语句之后就进入这个函数,从而再次执行函数中第一条语句,而接下来执行线程A中即将继续执行的语句还是执行线程B中即将执行的第二句谁也说不清。
实际剖析一下,可以看到StringBuilder里面实际执行的语句如下(实际是执行父类的内容)
if(str == null) str = "null";
intlen = str.length();
ensureCapacityInternal(count+ len);
str.getChars(0,len, value, count);
count+= len;
returnthis;
假设StringBuilder对象中的字符串长度为10,也就是count为10,我们追加的字符串为“0”,也就是长度为1.如果当线程A执行到count+=len时候,恰好线程B的代码进入函数,并且取得运行权一直到结束,此时在线程B中的count因为加上了“0”的长度,为11.现在线程A再次开始执行,因为count的定义没有volatile关键字,所以很有可能线程A中的count还是之前的10,所以再次执行语句时候,让count变成11.结果命名之行了两次append()函数,count却只增加了1.显然与期望逻辑不符。
由此可知StringBuffer是线程安全的可变字符序列。可将字符串缓冲区安全的用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。StringBuffer上的主要操作时append和insert方法,可重载这些方法,以接受任意的类型的数据。每个方法都能效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append方法始终将这些字符添加到缓冲区的末端;而inert方法则在指定的点添加字符。
StringBuffer是一个可变的字符序列是5.0新增的,此类提供一个与StringBuffer兼容的API,但不保证同步。该类被设计用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候。如果可能,简易优先采用该类,因为在大多数实现中,它比StringBuffer要快。两者的方法基本相同。
package SE.AboutString; import java.util.Random; public class StringBufferVsStringBuilder { public static int demo(final Object stringJoiner, final int testCount) throws InterruptedException { ThreadGroup group = new ThreadGroup(stringJoiner.getClass().getName() + "@" + stringJoiner.hashCode()); final Random rand = new Random(); Runnable listAppender = new Runnable() { public void run() { try { Thread.sleep(rand.nextInt(2)); } catch (InterruptedException e) { return; } if (stringJoiner instanceof StringBuffer) { ((StringBuffer)stringJoiner).append("0"); } else if (stringJoiner instanceof StringBuilder) { ((StringBuilder)stringJoiner).append("0"); } } }; for (int i = 0; i < testCount; i++) { new Thread(group, listAppender, "InsertList-" + i).start(); } while (group.activeCount() > 0) { Thread.sleep(10); } return stringJoiner.toString().length(); } public static void main(String[] args) throws InterruptedException { StringBuilder stringBuilder = new StringBuilder(); StringBuffer stringBuffer = new StringBuffer(); final int N = 10000; for (int i = 0; i < 10; i++) { stringBuilder.delete(0, stringBuilder.length()); stringBuffer.delete(0, stringBuffer.length()); int builderLength = demo(stringBuilder, N); int bufferLength = demo(stringBuffer, N); System.out.println("StringBuilder/StringBuffer: " + builderLength + "/" + bufferLength); } } } // Output will be something like this: // StringBuilder/StringBuffer: 9995/10000 // StringBuilder/StringBuffer: 9996/10000 // StringBuilder/StringBuffer: 9998/10000 // StringBuilder/StringBuffer: 9997/10000 // StringBuilder/StringBuffer: 9995/10000 // StringBuilder/StringBuffer: 9996/10000 // StringBuilder/StringBuffer: 9998/10000 // StringBuilder/StringBuffer: 9998/10000 // StringBuilder/StringBuffer: 9999/10000 // StringBuilder/StringBuffer: 9999/10000
package SE.AboutString; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Random; public class ThreadSafeDemo { public static int demo(final List list, final int testCount) throws InterruptedException { ThreadGroup group = new ThreadGroup(list.getClass().getName() + "@" + list.hashCode()); final Random rand = new Random(); Runnable listAppender = new Runnable() { public void run() { try { Thread.sleep(rand.nextInt(2)); } catch (InterruptedException e) { return; } list.add("0"); } }; for (int i = 0; i < testCount; i++) { new Thread(group, listAppender, "InsertList-" + i).start(); } while (group.activeCount() > 0) { Thread.sleep(10); } return list.size(); } public static void main(String[] args) throws InterruptedException { List unsafeList = new ArrayList(); List safeList = Collections.synchronizedList(new ArrayList()); final int N = 10000; for (int i = 0; i < 10; i++) { unsafeList.clear(); safeList.clear(); int unsafeSize = demo(unsafeList, N); int safeSize = demo(safeList, N); System.out.println("unsafe/safe: " + unsafeSize + "/" + safeSize); } } } /*unsafe/safe: 9896/10000 unsafe/safe: 9931/10000 unsafe/safe: 9940/10000 unsafe/safe: 9912/10000 unsafe/safe: 9960/10000 unsafe/safe: 9954/10000 unsafe/safe: 9960/10000 unsafe/safe: 9944/10000 unsafe/safe: 9960/10000 unsafe/safe: 9957/10000*/
参考:
http://www.zhihu.com/question/20101840
http://blog.csdn.net/rmn190/article/details/1492013