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完整代码

 

posted @ 2015-11-25 18:26  liglercy  阅读(271)  评论(0编辑  收藏  举报