StringBuilder与StringBuffer的区别(转) &精简版StringBuilder,提速字符串拼接
以下这部分转子连接 http://www.cnblogs.com/Fskjb/archive/2010/04/19/1715176.html
相信大家看到过很多比较String和StringBuffer区别的文章,也明白这两者的区别,然而自从Java 5.0发布以后,我们的比较列表上将多出一个对象了,这就是StringBuilder类。String类是不可变类,任何对String的改变都会引发新的String对象的生成;而StringBuffer则是可变类,任何对它所指代的字符串的改变都不会产生新的对象,可变和不可变类这一对对象已经齐全了,那么为什么还要引入新的StringBuilder类干吗?相信大家都有此疑问,我也如此。下面,我们就来看看引入该类的原因。
为什么会出现那么多比较String和StringBuffer的文章?
原因在于当改变字符串内容时,采用StringBuffer能获得更好的性能。既然是为了获得更好的性能,那么采用StringBuffer能够获得最好的性能吗?
答案是NO!
为什么?
如果你读过《Think in Java》,而且对里面描述HashTable和HashMap区别的那部分章节比较熟悉的话,你一定也明白了原因所在。对,就是支持线程同步保证线程安全而导致性能下降的问题。HashTable是线程安全的,很多方法都是synchronized方法,而HashMap不是线程安全的,但其在单线程程序中的性能比HashTable要高。StringBuffer和StringBuilder类的区别也在于此,新引入的StringBuilder类不是线程安全的,但其在单线程中的性能比StringBuffer高。如果你对此不太相信,可以试试下面的例子:
package com.hct.test; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * @author: chengtai.he * @created:2009-12-9 上午09:59:57 */ public class StringBuilderTester { private static final String base = " base string. "; private static final int count = 2000000; public static void stringTest() { long begin, end; begin = System.currentTimeMillis(); String test = new String(base); for (int i = 0; i < count/100; i++) { test = test + " add "; } end = System.currentTimeMillis(); System.out.println((end - begin) + " millis has elapsed when used String. "); } public static void stringBufferTest() { long begin, end; begin = System.currentTimeMillis(); StringBuffer test = new StringBuffer(base); for (int i = 0; i < count; i++) { test = test.append(" add "); } end = System.currentTimeMillis(); System.out.println((end - begin) + " millis has elapsed when used StringBuffer. "); } public static void stringBuilderTest() { long begin, end; begin = System.currentTimeMillis(); StringBuilder test = new StringBuilder(base); for (int i = 0; i < count; i++) { test = test.append(" add "); } end = System.currentTimeMillis(); System.out.println((end - begin) + " millis has elapsed when used StringBuilder. "); } public static String appendItemsToStringBuiler(List list) { StringBuilder b = new StringBuilder(); for (Iterator i = list.iterator(); i.hasNext();) { b.append(i.next()).append(" "); } return b.toString(); } public static void addToStringBuilder() { List list = new ArrayList(); list.add(" I "); list.add(" play "); list.add(" Bourgeois "); list.add(" guitars "); list.add(" and "); list.add(" Huber "); list.add(" banjos "); System.out.println(StringBuilderTester.appendItemsToStirngBuffer(list)); } public static String appendItemsToStirngBuffer(List list) { StringBuffer b = new StringBuffer(); for (Iterator i = list.iterator(); i.hasNext();) { b.append(i.next()).append(" "); } return b.toString(); } public static void addToStringBuffer() { List list = new ArrayList(); list.add(" I "); list.add(" play "); list.add(" Bourgeois "); list.add(" guitars "); list.add(" and "); list.add(" Huber "); list.add(" banjos "); System.out.println(StringBuilderTester.appendItemsToStirngBuffer(list)); } public static void main(String[] args) { stringTest(); stringBufferTest(); stringBuilderTest(); addToStringBuffer(); addToStringBuilder(); } }
上面的程序结果如下:
5266 millis has elapsed when used String.
375 millis has elapsed when used StringBuffer.
281 millis has elapsed when used StringBuilder.
I play Bourgeois guitars and Huber banjos
I play Bourgeois guitars and Huber banjos
从上面的结果来看,这三个类在单线程程序中的性能差别一目了然,采用String对象时,即使运行次数仅是采用其他对象的1/100,其执行时间仍然比其他对象高出25倍以上;而采用StringBuffer对象和采用StringBuilder对象的差别也比较明显,前者是后者的1.5倍左右。由此可见,如果我们的程序是在单线程下运行,或者是不必考虑到线程同步问题,我们应该优先使用StringBuilder类;当然,如果要保证线程安全,自然非StringBuffer莫属了。
除了对多线程的支持不一样外,这两个类的使用几乎没有任何差别,上面的例子就是个很好的说明。appendItemsToStringBuiler和appendItemsToStirngBuffer两个方法除了采用的对象分别为StringBuilder和StringBuffer外,其他完全相同,而效果也完全相同。
以下部分转载自链接:http://www.cnblogs.com/blqw/p/QuickStringWriter.html
编写目的:
在频繁的字符串拼接中,为了提升程序的性能,我们往往会用StringBuilder代替String+=String这样的操作;
而我在实际编码中发现,大部分情况下我用到的只是StringBuilder的Append方法;
一些极端的情况下,我希望我的程序性能更高,这时从StringBuilder入手是一个不错的主意;
所以我希望用一种简单的方案代替StringBuilder,我将这个方案命名为QuickStringWriter;
初步设计:
对于StringBuilder来说他除了Append之外还会有更多的方法,比如Insert,AppendFormat等
而QuickStringWriter这个方案,仅仅是用来代替简单的字符串+=这样的操作,所以我不会考虑他们,只需要重新实现Append,并让他们比StringBuilder更快
class QuickStringWriter : IDisposable { public QuickStringWriter Append(bool val); public QuickStringWriter Append(byte val); public QuickStringWriter Append(char val); public QuickStringWriter Append(DateTime val); public QuickStringWriter Append(DateTime val, string format); public QuickStringWriter Append(decimal val); public QuickStringWriter Append(double val); public QuickStringWriter Append(Guid val); public QuickStringWriter Append(Guid val, string format); public QuickStringWriter Append(short val); public QuickStringWriter Append(int val); public QuickStringWriter Append(long val); public QuickStringWriter Append(sbyte val); public QuickStringWriter Append(float val); public QuickStringWriter Append(string val); public QuickStringWriter Append(ushort val); public QuickStringWriter Append(uint val); public QuickStringWriter Append(ulong val); public QuickStringWriter Clear(); void Dispose(); string ToString(); }
结构:
QuickStringWriter将使用一个Char数组作为缓冲区(Buff)
使用一个属性Position作为当前字符位置,或者说是当前字符数
重写ToString方法,将当前缓冲区中的内容,从0到Position转为string对象输出
char[] Buff; int Position; public override string ToString() { return new string(Buff, 0, Position); }
设置缓冲区:
既然有缓冲区,那么就要考虑缓冲区不足时的处理
我设计2个方法解决这个问题
//设置缓冲区容量 void SetCapacity(int capacity) { if (capacity > Buff.Length) { if (capacity > 6000 * 10000) //6000W { throw new OutOfMemoryException("QuickStringWriter容量不能超过6000万个字符"); } } var newbuff = new char[capacity]; Array.Copy(Buff, 0, newbuff, 0, Math.Min(Position, capacity)); Buff = newbuff; Position = Math.Min(Position, Buff.Length); } //翻倍空间 void ToDouble() { SetCapacity(Math.Min(Buff.Length * 2, 10 * 10000)); }
第一个方法SetCapacity,我预留了一个缩小当前缓冲区的处理,虽然现在不会使用
第二个方法就是翻倍缓冲区,这里也是有个条件的,如果当前缓冲区大于5W,最多一次也只能扩容10W字符的容量
//当容量不足的时候,尝试翻倍空间 void Try() { if (Position >= Buff.Length) { ToDouble(); } } //测试剩余空间大小,如果不足,则扩展至可用大小后再翻倍 void Check(int count) { var pre = Position + count; if (pre >= Buff.Length) { SetCapacity(pre * 2); } }
这里还需要2个方法可以方面的调用
比如在追加单个字符的时候可以调用Try
在追加指定长度字符之前可以调用Check
性能:
在性能上,我只要考虑每一个方法的性能都能快StringBuilder就可以了,这点其实并不是非常困难
public QuickStringWriter Append(Boolean val) { if (val) { Check(4); Buff[Position++] = 't'; Buff[Position++] = 'r'; Buff[Position++] = 'u'; Buff[Position++] = 'e'; } else { Check(5); Buff[Position++] = 'f'; Buff[Position++] = 'a'; Buff[Position++] = 'l'; Buff[Position++] = 's'; Buff[Position++] = 'e'; } return this; } bool类型处理
百万次追加 false
StringBuilder 19ms
QuickStringWriter 9ms
ps:系统的bool转换为String后首字母都是大小,这里我为了使用更方面直接转为小写的了
public QuickStringWriter Append(DateTime val) { Check(18); if (val.Year < 1000) { Buff[Position++] = '0'; if (val.Year < 100) { Buff[Position++] = '0'; if (val.Year < 10) { Buff[Position++] = '0'; } } } Append((long)val.Year); Buff[Position++] = '-'; if (val.Month < 10) { Buff[Position++] = '0'; } Append((long)val.Month); Buff[Position++] = '-'; if (val.Day < 10) { Buff[Position++] = '0'; } Append((long)val.Day); Buff[Position++] = ' '; if (val.Hour < 10) { Buff[Position++] = '0'; } Append((long)val.Hour); Buff[Position++] = ':'; if (val.Minute < 10) { Buff[Position++] = '0'; } Append((long)val.Minute); Buff[Position++] = ':'; if (val.Second < 10) { Buff[Position++] = '0'; } Append((long)val.Minute); return this; } DateTime类型处理
十万次追加 DateTime.Now
StringBuilder 90ms
QuickStringWriter 55ms
Char[] NumberBuff; public QuickStringWriter Append(Int64 val) { if (val == 0) { Buff[Position++] = '0'; return this; } var pos = 63; if (val < 0) { Buff[Position++] = '-'; NumberBuff[pos] = (char)(~(val % 10) + '1'); if (val < -10) { val = val / -10; NumberBuff[--pos] = (char)(val % 10 + '0'); } } else { NumberBuff[pos] = (char)(val % 10 + '0'); } while ((val = val / 10L) != 0L) { NumberBuff[--pos] = (char)(val % 10L + '0'); } var length = 64 - pos; Check(length); Array.Copy(NumberBuff, pos, Buff, Position, length); Position += length; return this; } 整数类型的处理
百万次追加 long.MaxValue sbyte.MaxValue
StringBuilder 190ms 120ms
QuickStringWriter 115ms 33ms
public QuickStringWriter Append(Char val) { Try(); Buff[Position++] = val; return this; }
百万次追加 'a'
StringBuilder 7ms
QuickStringWriter 4ms
public QuickStringWriter Append(String val) { if (val == null || val.Length == 0) { return this; } else if (val.Length <= 3) { Check(val.Length); Buff[Position++] = val[0]; if (val.Length > 1) { Buff[Position++] = val[1]; if (val.Length > 2) { Buff[Position++] = val[2]; } } } else { Check(val.Length); val.CopyTo(0, Buff, Position, val.Length); Position += val.Length; } return this; } String处理
嗯..这个和StringBuilder几乎相同
然后其他的类型就直接按照调用Append(string str) 的处理方式就可以了
public QuickStringWriter Append(Guid val) { Append(val.ToString()); return this; } public QuickStringWriter Append(Decimal val) { Append(val.ToString(System.Globalization.NumberFormatInfo.InvariantInfo)); return this; } 其他类型处理
代入JsonBuilder:
全部完成了之后 我把他加入到之前的JsonBuilder中试试
public string ToJsonString(object obj) { //Buff.Length = 0; //StringBuilder清空方法 Buff.Clear();//QuickStringWriter清空方法 AppendObject(obj); return Buff.ToString(); }
完整代码:
using System; using System.Collections.Generic; using System.Text; namespace blqw { public class QuickStringWriter : IDisposable { public QuickStringWriter() : this(2048) { } /// <summary> /// 实例化新的对象,并且指定初始容量 /// </summary> /// <param name="capacity"></param> public QuickStringWriter(int capacity) { NumberBuff = new Char[64]; Buff = new Char[capacity]; } //设置缓冲区容量 void SetCapacity(int capacity) { if (capacity > Buff.Length) { if (capacity > 6000 * 10000) //6000W { throw new OutOfMemoryException("QuickStringWriter容量不能超过6000万个字符"); } } var newbuff = new char[capacity]; Array.Copy(Buff, 0, newbuff, 0, Math.Min(Position, capacity)); Buff = newbuff; Position = Math.Min(Position, Buff.Length); } //当容量不足的时候,尝试翻倍空间 void ToDouble() { SetCapacity(Math.Min(Buff.Length * 2, 10 * 10000)); } Char[] NumberBuff; Char[] Buff; int Position; public void Dispose() { NumberBuff = null; Buff = null; } public QuickStringWriter Append(Boolean val) { if (val) { Check(4); Buff[Position++] = 't'; Buff[Position++] = 'r'; Buff[Position++] = 'u'; Buff[Position++] = 'e'; } else { Check(5); Buff[Position++] = 'f'; Buff[Position++] = 'a'; Buff[Position++] = 'l'; Buff[Position++] = 's'; Buff[Position++] = 'e'; } return this; } public QuickStringWriter Append(DateTime val) { Check(18); if (val.Year < 1000) { Buff[Position++] = '0'; if (val.Year < 100) { Buff[Position++] = '0'; if (val.Year < 10) { Buff[Position++] = '0'; } } } Append((long)val.Year); Buff[Position++] = '-'; if (val.Month < 10) { Buff[Position++] = '0'; } Append((long)val.Month); Buff[Position++] = '-'; if (val.Day < 10) { Buff[Position++] = '0'; } Append((long)val.Day); Buff[Position++] = ' '; if (val.Hour < 10) { Buff[Position++] = '0'; } Append((long)val.Hour); Buff[Position++] = ':'; if (val.Minute < 10) { Buff[Position++] = '0'; } Append((long)val.Minute); Buff[Position++] = ':'; if (val.Second < 10) { Buff[Position++] = '0'; } Append((long)val.Minute); return this; } public QuickStringWriter Append(Guid val) { Append(val.ToString()); return this; } public QuickStringWriter Append(DateTime val, string format) { Append(val.ToString(format)); return this; } public QuickStringWriter Append(Guid val, string format) { Append(val.ToString(format)); return this; } public QuickStringWriter Append(Decimal val) { Append(val.ToString()); return this; } public QuickStringWriter Append(Double val) { Append(Convert.ToString(val)); return this; } public QuickStringWriter Append(Single val) { Append(Convert.ToString(val)); return this; } public QuickStringWriter Append(SByte val) { Append((Int64)val); return this; } public QuickStringWriter Append(Int16 val) { Append((Int64)val); return this; } public QuickStringWriter Append(Int32 val) { Append((Int64)val); return this; } public override string ToString() { return new string(Buff, 0, Position); } public QuickStringWriter Append(Int64 val) { if (val == 0) { Buff[Position++] = '0'; return this; } var pos = 63; if (val < 0) { Buff[Position++] = '-'; NumberBuff[pos] = (char)(~(val % 10) + '1'); if (val < -10) { val = val / -10; NumberBuff[--pos] = (char)(val % 10 + '0'); } } else { NumberBuff[pos] = (char)(val % 10 + '0'); } while ((val = val / 10L) != 0L) { NumberBuff[--pos] = (char)(val % 10L + '0'); } var length = 64 - pos; Check(length); Array.Copy(NumberBuff, pos, Buff, Position, length); Position += length; return this; } public QuickStringWriter Append(Char val) { Try(); Buff[Position++] = val; return this; } public QuickStringWriter Append(String val) { if (val == null || val.Length == 0) { return this; } else if (val.Length <= 3) { Check(val.Length); Buff[Position++] = val[0]; if (val.Length > 1) { Buff[Position++] = val[1]; if (val.Length > 2) { Buff[Position++] = val[2]; } } } else { Check(val.Length); val.CopyTo(0, Buff, Position, val.Length); Position += val.Length; } return this; } public QuickStringWriter Append(Byte val) { Append((UInt64)val); return this; } public QuickStringWriter Append(UInt16 val) { Append((UInt64)val); return this; } public QuickStringWriter Append(UInt32 val) { Append((UInt64)val); return this; } public QuickStringWriter Append(UInt64 val) { if (val == 0) { Buff[Position++] = '0'; return this; } var pos = 63; NumberBuff[pos] = (char)(val % 10 + '0'); while ((val = val / 10L) != 0L) { NumberBuff[--pos] = (char)(val % 10L + '0'); } var length = 64 - pos; Check(length); Array.Copy(NumberBuff, pos, Buff, Position, length); Position += length; return this; } public QuickStringWriter Clear() { Position = 0; return this; } //当容量不足的时候,尝试翻倍空间 void Try() { if (Position >= Buff.Length) { ToDouble(); } } //测试剩余空间大小,如果不足,则扩展至可用大小后再翻倍 void Check(int count) { var pre = Position + count; if (pre >= Buff.Length) { SetCapacity(pre * 2); } } } } QuickStringWriter完整代码