编写目的

在频繁的字符串拼接中,为了提升程序的性能,我们往往会用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. RSA密钥之C#格式与Java格式转换

    前言 最近由于项目需求,服务端由c#编写,客户端由java编写.通信数据使用RSA非对称加密.但是java和c#生成的密钥格式是不一样的,所以需要转换格式才可以正常使用.网上搜到使用java进行格式转 ...

  2. [转]ORACLE中Like与Instr模糊查询性能大比拼

    instr(title,'手册')>0  相当于  title like '%手册%' instr(title,'手册')=1  相当于  title like '手册%' instr(titl ...

  3. JAVA正则表达式中如何匹配反斜杠 \

    有时候我们需要匹配反斜杠,你可能会把对应的正则表达式写成 "\\" 然后可能会有如下输出: Exception in thread "main" java.ut ...

  4. java复习集合类之List接口

    List 为有序可重复列表 实现List接口的类主要是ArrayList 下面为ArrayList的测试代码 import java.util.ArrayList; public class demo ...

  5. Windows使用总结

    虚拟桌面快捷键: 新建虚拟桌面 Control+Win+D 切换虚拟桌面 Control+Win+左/右方向键 关闭虚拟桌面 Control+Win+F4 显示虚拟桌面列表 Win+Tab  

  6. Windows中使用TortoiseGit提交项目到GitLab配置

    下文来给各位介绍Windows中使用TortoiseGit提交项目到GitLab配置过程,下在全部图片希望对各位带来方便面. Gitlab默认的配置推荐使用shell命令行与server端进行交互,作 ...

  7. JavaScript对象状态

    有限状态机(Finite-state machine)是一个非常有用的模型,可以模拟世界上大部分事物. 简单说,它有三个特征: * 状态总数(state)是有限的. * 任一时刻,只处在一种状态之中. ...

  8. CodeForces 618A Slime Combining

    http://www.codeforces.com/contest/618/problem/A 明明觉得是水题,而我却做了一个小时. 明明觉得代码没有错,而我却错了好几次. 因为我的名字不叫明明,也不 ...

  9. 第三周作业(一):安装VS以及创建单元测试

    安装的时候找的是最新版本的VS2015,因为不想花钱也不想用破解版,所以用社区版本. 下了一个IOS文件,社区版VS2015,个人免费版,强行表示不用盗版来表现自己高尚的情操:D 放入虚拟光驱软件后, ...

  10. 本周PSP流程进度

    一计划 (1)估计这个任务需要多少时间:8天 二开发 (1)需求分析:作为一名观众,我希望了解每一场的比赛成绩,以便加深对自己喜爱球队的了解,以及赛况. (2)生成设计文档: (3)设计复审(和同学交 ...