编写目的

在频繁的字符串拼接中,为了提升程序的性能,我们往往会用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, , Position);
}

  设置缓冲区

既然有缓冲区,那么就要考虑缓冲区不足时的处理

我设计2个方法解决这个问题

//设置缓冲区容量
void SetCapacity(int capacity)
{
if (capacity > Buff.Length)
{
if (capacity > * ) //6000W
{
throw new OutOfMemoryException("QuickStringWriter容量不能超过6000万个字符");
}
}
var newbuff = new char[capacity];
Array.Copy(Buff, , newbuff, , Math.Min(Position, capacity));
Buff = newbuff;
Position = Math.Min(Position, Buff.Length);
}
//翻倍空间
void ToDouble()
{
SetCapacity(Math.Min(Buff.Length * , * ));
}

第一个方法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个方法可以方面的调用

比如在追加单个字符的时候可以调用Try

在追加指定长度字符之前可以调用Check

  性能

在性能上,我只要考虑每一个方法的性能都能快StringBuilder就可以了,这点其实并不是非常困难

public QuickStringWriter Append(Boolean val)
{
if (val)
{
Check();
Buff[Position++] = 't';
Buff[Position++] = 'r';
Buff[Position++] = 'u';
Buff[Position++] = 'e';
}
else
{
Check();
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();
if (val.Year < )
{
Buff[Position++] = '';
if (val.Year < )
{
Buff[Position++] = '';
if (val.Year < )
{
Buff[Position++] = '';
}
}
}
Append((long)val.Year);
Buff[Position++] = '-'; if (val.Month < )
{
Buff[Position++] = '';
}
Append((long)val.Month);
Buff[Position++] = '-'; if (val.Day < )
{
Buff[Position++] = '';
}
Append((long)val.Day);
Buff[Position++] = ' '; if (val.Hour < )
{
Buff[Position++] = '';
}
Append((long)val.Hour);
Buff[Position++] = ':'; if (val.Minute < )
{
Buff[Position++] = '';
}
Append((long)val.Minute);
Buff[Position++] = ':'; if (val.Second < )
{
Buff[Position++] = '';
}
Append((long)val.Minute);
return this;
}

DateTime类型处理

十万次追加 DateTime.Now

StringBuilder         90ms

QuickStringWriter  55ms

Char[] NumberBuff;
public QuickStringWriter Append(Int64 val)
{
if (val == )
{
Buff[Position++] = '';
return this;
} var pos = ;
if (val < )
{
Buff[Position++] = '-';
NumberBuff[pos] = (char)(~(val % ) + '');
if (val < -)
{
val = val / -;
NumberBuff[--pos] = (char)(val % + '');
}
}
else
{
NumberBuff[pos] = (char)(val % + '');
}
while ((val = val / 10L) != 0L)
{
NumberBuff[--pos] = (char)(val % 10L + '');
}
var length = - 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;
}

char类型处理

百万次追加             'a'

StringBuilder         7ms

QuickStringWriter  4ms

public QuickStringWriter Append(String val)
{
if (val == null || val.Length == )
{
return this;
}
else if (val.Length <= )
{
Check(val.Length);
Buff[Position++] = val[];
if (val.Length > )
{
Buff[Position++] = val[];
if (val.Length > )
{
Buff[Position++] = val[];
}
}
}
else
{
Check(val.Length);
val.CopyTo(, 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中试试

//protected StringBuilder Buff = new StringBuilder(4096);
protected QuickStringWriter Buff = new QuickStringWriter();//字符缓冲区

调用需要修改一个地方

public string ToJsonString(object obj)
{
//Buff.Length = 0; //StringBuilder清空方法
Buff.Clear();//QuickStringWriter清空方法
AppendObject(obj);
return Buff.ToString();
}

再来看看前后的差距

  • 原StringBuilder缓冲

211ms | 166ms | 162ms | 164ms | 164ms | 160ms | 163ms | 160ms | 179ms | 156ms |

  • 更换为QuickStringWriter后

167ms | 134ms | 134ms | 134ms | 135ms | 134ms | 134ms | 133ms | 134ms | 134ms |

ps:在公司的破电脑上也测了,发现配置越差差距越大,在公司的酷睿双核上差距应该是40%左右

  完整代码

using System;
using System.Collections.Generic;
using System.Text; namespace blqw
{
public class QuickStringWriter : IDisposable
{
public QuickStringWriter() : this() { }
/// <summary>
/// 实例化新的对象,并且指定初始容量
/// </summary>
/// <param name="capacity"></param>
public QuickStringWriter(int capacity)
{
NumberBuff = new Char[];
Buff = new Char[capacity];
} //设置缓冲区容量
void SetCapacity(int capacity)
{
if (capacity > Buff.Length)
{
if (capacity > * ) //6000W
{
throw new OutOfMemoryException("QuickStringWriter容量不能超过6000万个字符");
}
}
var newbuff = new char[capacity];
Array.Copy(Buff, , newbuff, , Math.Min(Position, capacity));
Buff = newbuff;
Position = Math.Min(Position, Buff.Length);
}
//当容量不足的时候,尝试翻倍空间
void ToDouble()
{
SetCapacity(Math.Min(Buff.Length * , * ));
} Char[] NumberBuff;
Char[] Buff;
int Position; public void Dispose()
{
NumberBuff = null;
Buff = null;
} public QuickStringWriter Append(Boolean val)
{
if (val)
{
Check();
Buff[Position++] = 't';
Buff[Position++] = 'r';
Buff[Position++] = 'u';
Buff[Position++] = 'e';
}
else
{
Check();
Buff[Position++] = 'f';
Buff[Position++] = 'a';
Buff[Position++] = 'l';
Buff[Position++] = 's';
Buff[Position++] = 'e';
}
return this;
}
public QuickStringWriter Append(DateTime val)
{
Check();
if (val.Year < )
{
Buff[Position++] = '';
if (val.Year < )
{
Buff[Position++] = '';
if (val.Year < )
{
Buff[Position++] = '';
}
}
}
Append((long)val.Year);
Buff[Position++] = '-'; if (val.Month < )
{
Buff[Position++] = '';
}
Append((long)val.Month);
Buff[Position++] = '-'; if (val.Day < )
{
Buff[Position++] = '';
}
Append((long)val.Day);
Buff[Position++] = ' '; if (val.Hour < )
{
Buff[Position++] = '';
}
Append((long)val.Hour);
Buff[Position++] = ':'; if (val.Minute < )
{
Buff[Position++] = '';
}
Append((long)val.Minute);
Buff[Position++] = ':'; if (val.Second < )
{
Buff[Position++] = '';
}
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, , Position);
} public QuickStringWriter Append(Int64 val)
{
if (val == )
{
Buff[Position++] = '';
return this;
} var pos = ;
if (val < )
{
Buff[Position++] = '-';
NumberBuff[pos] = (char)(~(val % ) + '');
if (val < -)
{
val = val / -;
NumberBuff[--pos] = (char)(val % + '');
}
}
else
{
NumberBuff[pos] = (char)(val % + '');
}
while ((val = val / 10L) != 0L)
{
NumberBuff[--pos] = (char)(val % 10L + '');
}
var length = - 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 == )
{
return this;
}
else if (val.Length <= )
{
Check(val.Length);
Buff[Position++] = val[];
if (val.Length > )
{
Buff[Position++] = val[];
if (val.Length > )
{
Buff[Position++] = val[];
}
}
}
else
{
Check(val.Length);
val.CopyTo(, 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 == )
{
Buff[Position++] = '';
return this;
}
var pos = ; NumberBuff[pos] = (char)(val % + ''); while ((val = val / 10L) != 0L)
{
NumberBuff[--pos] = (char)(val % 10L + '');
}
var length = - pos;
Check(length);
Array.Copy(NumberBuff, pos, Buff, Position, length);
Position += length;
return this;
} public QuickStringWriter Clear()
{
Position = ;
return this;
} //当容量不足的时候,尝试翻倍空间
void Try()
{
if (Position >= Buff.Length)
{
ToDouble();
}
}
//测试剩余空间大小,如果不足,则扩展至可用大小后再翻倍
void Check(int count)
{
var pre = Position + count;
if (pre >= Buff.Length)
{
SetCapacity(pre * );
}
} }
}

QuickStringWriter完整代码

这样就OK了,在很多时间他就是一个完全可以满足需求的精简提速版的StringBuilder!

包括以后如果机会放出个人用的简易ORM也会发现里面的字符串拼接也是用的这个对象

精简版StringBuilder,提速字符串拼接的更多相关文章

  1. StringBuilder(字符串拼接类)

    StringBuilder是在using System.Text命名空间下的一个成员. 在做字符串拼接的时候,因为字符串是引用类型,新的字符串是会再内存中创建的,所以用+号拼接字符串是比较耗效率的. ...

  2. C# 利用StringBuilder提升字符串拼接性能

    一个项目中有数据图表呈现,数据量稍大时显得很慢. 用Stopwatch分段监控了一下,发现耗时最多的函数是SaveToExcel 此函数中遍列所有数据行,通过Replace替换标签生成Excel行,然 ...

  3. 为什么阿里巴巴Java开发手册中不建议在循环体中使用+进行字符串拼接?

    之前在阅读<阿里巴巴Java开发手册>时,发现有一条是关于循环体中字符串拼接的建议,具体内容如下: 那么我们首先来用例子来看看在循环体中用 + 或者用 StringBuilder 进行字符 ...

  4. java数组、字符串拼接

    1. 数组实现拼接 int[] arr ={11,22,33,44,55,66}; System.out.print("["); for (int i = 0; i <arr ...

  5. 测试一下StringBuffer和StringBuilder及字面常量拼接三种字符串的效率

    之前一篇里写过字符串常用类的三种方式<java中的字符串相关知识整理>,只不过这个只是分析并不知道他们之间会有多大的区别,或者所谓的StringBuffer能提升多少拼接效率呢?为此写个简 ...

  6. 从源代码的角度聊聊java中StringBuffer、StringBuilder、String中的字符串拼接

    长久以来,我们被教导字符串的连接最好用StringBuffer.StringBuilder,但是我们却不知道这两者之间的区别.跟字符串相关的一些方法中总是有CharSequence.StringBuf ...

  7. StringBuilder字符串拼接类

    StringBuilder StringBuilder是在using System.Text命名空间下的一个成员. 在做字符串拼接的时候,因为字符串是引用类型,新的字符串是会再内存中创建的,所以用+号 ...

  8. C#的StringBuilder 以及string字符串拼接的效率对照

    今天公司一个做Unity3d的人在说字符串拼接的一个效率问题,他觉得string拼接会产生新的一个内存空间,假设不及时回收会产生大量的碎片,特别是在Unity3d这样一个Updata环境下,由于每一帧 ...

  9. Java中测试StringBuilder、StringBuffer、String在字符串拼接上的性能

    应一个大量字符串拼接的任务 测试一下StringBuilder.StringBuffer.String在操作字符串拼接时候的性能 性能上理论是StringBuilder  >  StringBu ...

随机推荐

  1. IDEA快捷键+使用小技巧

    一 常用快捷键 Alt+回车 导入包,自动修正,当引入的类需要异常捕获的时候 Ctrl+Shift+Space 自动补全代码,"new"字符,还可以引入强制转换的 Ctrl-Alt ...

  2. [leetcode]题型整理之用bit统计个数

    137. Single Number II Given an array of integers, every element appears three times except for one. ...

  3. Android Studio中获取SHA1或MD5的方法

    原来在Eclipse中获取SHA1或者MD5,在IDE界面上就可以查找到. 切换到Android Studio后,如何查看呢?找了半天没找到.那就老办法命令行. 第一步.打开Android Studi ...

  4. Block Markov Coding & Decoding

    Block Markov coding在一系列block上进行.在除了第一个和最后一个block上,都发送一个新消息.但是,每个block上发送的码字不仅取决于新的信息,也跟之前的一个或多个block ...

  5. ODAC (odp.net) 从开发到部署

    2013-09-30 16:08 4097人阅读 评论(0) 收藏 举报  分类: Oracle(10)  版权声明:本文为博主原创文章,未经博主允许不得转载. 1. 确定你开发机和服务器的操作系统是 ...

  6. Python中的生成器与yield

    对于python中的yield有些疑惑,然后在StackOverflow上看到了一篇回答,所以搬运过来了,英文好的直接看原文吧. 可迭代对象 当你创建一个列表的时候,你可以一个接一个地读取其中的项.一 ...

  7. Holographic Remoting

    看到微软官方的 Holographic Remoting Player https://developer.microsoft.com/en-us/windows/holographic/hologr ...

  8. HTTP 使用期及新鲜度算法

    使用期算法: /* * age_value 当代理服务器用自己的头部去响应请求时,Age标明实体产生到现在多长时间了. * date_value HTTP 服务器应答中的Date字段 原始服务器 * ...

  9. 弱省互测#1 t3

    题意 给出一棵n个点的树,求包含1号点的第k小的连通块权值和.(\(n<=10^5\)) 分析 k小一般考虑堆... 题解 堆中关键字为\(s(x)+min(a)\),其中\(s(x)\)表示\ ...

  10. 【ORACLE】MD5加密

        今天乌干达充值卡入库时,发现有资源已经存在的异常, 异常原因经过核实是由于卡资源密码在库中已经存在, 为进一步查找存在的原因, 因此需要对导入文件密码的MD5 加密, 通过MD5加密后的字符串 ...