博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
字符串连接的效率问题
阅读量:5967 次
发布时间:2019-06-19

本文共 6079 字,大约阅读时间需要 20 分钟。

字符串连接,常用的三种方式:StringBuilder、+、string.Format。很多人认为StringBuilder的效率高于+,这种观念是不正确的。

一般来说,对于数量固定的字符串连接,+的效率是最高的。比如:

string sql = "update tableName set int1=" + int1.ToString() + ",int2=" + int2.ToString() + ",int3=" + int3.ToString() + " where id=" + id.ToString();

编译器会优化为

string sql = string.Concat(new string[] { "update tableName set int1=", int1.ToString(), ",int2=", int2.ToString(), ",int3=", int3.ToString(), " where id=", id.ToString() });

再来看看string.Concat是怎么实现的

string.Concat
public static string Concat(params string[] values){    int totalLength = 0;    if (values == null)    {        throw new ArgumentNullException("values");    }    string[] strArray = new string[values.Length];    for (int i = 0; i < values.Length; i++)    {        string str = values[i];        strArray[i] = (str == null) ? Empty : str;        totalLength += strArray[i].Length;        if (totalLength < 0)        {            throw new OutOfMemoryException();        }    }    return ConcatArray(strArray, totalLength);}private static string ConcatArray(string[] values, int totalLength){    string dest = FastAllocateString(totalLength);    int destPos = 0;    for (int i = 0; i < values.Length; i++)    {        FillStringChecked(dest, destPos, values[i]);        destPos += values[i].Length;    }    return dest;}private static unsafe void FillStringChecked(string dest, int destPos, string src){    int length = src.Length;    if (length > (dest.Length - destPos))    {        throw new IndexOutOfRangeException();    }    fixed (char* chRef = &dest.m_firstChar)    {        fixed (char* chRef2 = &src.m_firstChar)        {            wstrcpy(chRef + destPos, chRef2, length);        }    }}

看到没有,先计算目标字符串的长度,然后申请相应的空间,最后逐一复制。相当简练,基本上可以说没有多余操作,时间复杂度仅为o(n),而且常数项为1。总结:固定数量的字符串连接效率最高的是string.Concat,而连+被编译器优化为string.Concat,所以+的效率也是最高的。

注意:字符串的连+不要拆成多条语句,比如:

string sql = "update tableName set int1=";sql += int1.ToString();sql += ...

这样子的代码,编译器如果没有优化为string.Concat,也就变成了性能杀手,因为第i个字符串需要复制n-i次,时间复杂度就成了O(n^2)。

那如果字符串数量不固定怎么办呢?所以就有了StringBuilder,一般情况下它使用2n的空间来保证O(n)整体时间复杂度,常数项接近于2。

StringBuilder
public StringBuilder() : this(0x10){}public StringBuilder(int capacity) : this(string.Empty, capacity){}public StringBuilder(string value, int capacity) : this(value, 0, (value != null) ? value.Length : 0, capacity){}public StringBuilder(string value, int startIndex, int length, int capacity){    this.m_currentThread = Thread.InternalGetCurrentThread();    if (capacity < 0)    {        throw new ArgumentOutOfRangeException("capacity", string.Format(CultureInfo.CurrentCulture, Environment.GetResourceString("ArgumentOutOfRange_MustBePositive"), new object[] { "capacity" }));    }    if (length < 0)    {        throw new ArgumentOutOfRangeException("length", string.Format(CultureInfo.CurrentCulture, Environment.GetResourceString("ArgumentOutOfRange_MustBeNonNegNum"), new object[] { "length" }));    }    if (startIndex < 0)    {        throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_StartIndex"));    }    if (value == null)    {        value = string.Empty;    }    if (startIndex > (value.Length - length))    {        throw new ArgumentOutOfRangeException("length", Environment.GetResourceString("ArgumentOutOfRange_IndexLength"));    }    this.m_MaxCapacity = 0x7fffffff;    if (capacity == 0)    {        capacity = 0x10;    }    while (capacity < length)    {        capacity *= 2;        if (capacity < 0)        {            capacity = length;            break;        }    }    this.m_StringValue = string.GetStringForStringBuilder(value, startIndex, length, capacity);}

我们看到,默认情况下,它初始化的时候申请一个长度为16的字符串作为容器。

StringBuilder.Append
public StringBuilder Append(string value){    if (value != null)    {        string stringValue = this.m_StringValue;        IntPtr currentThread = Thread.InternalGetCurrentThread();        if (this.m_currentThread != currentThread)        {            stringValue = string.GetStringForStringBuilder(stringValue, stringValue.Capacity);        }        int length = stringValue.Length;        int requiredLength = length + value.Length;        if (this.NeedsAllocation(stringValue, requiredLength))        {            string newString = this.GetNewString(stringValue, requiredLength);            newString.AppendInPlace(value, length);            this.ReplaceString(currentThread, newString);        }        else        {            stringValue.AppendInPlace(value, length);            this.ReplaceString(currentThread, stringValue);        }    }    return this;}private string GetNewString(string currentString, int requiredLength){    int maxCapacity = this.m_MaxCapacity;    if (requiredLength < 0)    {        throw new OutOfMemoryException();    }    if (requiredLength > maxCapacity)    {        throw new ArgumentOutOfRangeException("requiredLength", Environment.GetResourceString("ArgumentOutOfRange_SmallCapacity"));    }    int capacity = currentString.Capacity * 2;    if (capacity < requiredLength)    {        capacity = requiredLength;    }    if (capacity > maxCapacity)    {        capacity = maxCapacity;    }    if (capacity <= 0)    {        throw new ArgumentOutOfRangeException("newCapacity", Environment.GetResourceString("ArgumentOutOfRange_NegativeCapacity"));    }    return string.GetStringForStringBuilder(currentString, capacity);}

当空间不够时,它重新申请至少两倍的空间以满足需求。我们可以看到,这种方式最坏的执行时间为n+n/2+n/4+n/8+...,接近于2n。因为这个算法的实用与高效,.net类库里面有很多动态集合都采用这种牺牲空间换取时间的方式,一般来说效果还是不错的。

但是StringBuilder相对于+=来说不够简洁,所以当n较小时一般使用O(n^2)的+=,当n稍大一点一般使用StringBuilder。

再来看看string.Format。有人说他比+效率高,因为它的底层是StringBuilder。很明显这种说法已经不攻自破了,抛开string.Format底层需要解析{?}的代价,StringBuilder自己就PK不过+,难道你是想PK+=,只能说你是故意的。

另外还有一种方式,那就是List<string>,我个人比较常用,也是我推荐使用的常用方式,因为它可以转换为string[]后使用string.Concat或string.Join,很多时候都比StringBuilder更高效。List与StringBuilder采用的是同样的动态集合算法,时间复杂度也是O(n),与StringBuilder不同的是:List的n是字符串的数量,复制的是字符串的引用;StringBuilder的n是字符串的长度,复制的数据。不同的特性决定的它们各自的适应环境,当子串比较大时建议使用List<string>,因为复制引用比复制数据划算。而当子串比较小,比如平均长度小于8,特别是一个一个的字符,建议使用StringBuilder。

转载于:https://www.cnblogs.com/showjim/archive/2012/05/09/2491555.html

你可能感兴趣的文章
关于CRM库存初始化的一点小总结
查看>>
IntelliJ IDEA 12 中用 Maven + Jetty 来开发Web项目
查看>>
asterisk远程注册
查看>>
03-备份压缩命令
查看>>
电子表格控件Spreadsheet 对象方法事件详细介绍
查看>>
我的友情链接
查看>>
zabbix-server 的安装-centos7
查看>>
注销其他用户
查看>>
软路由ros(MIKROTIK)安装教程:[3]ROS注册
查看>>
Java中字符串中子串的查找共有四种方法(indexof())
查看>>
mysql 存储过程
查看>>
Sql Server 从日志中恢复误删除或误Update的数据
查看>>
sql 从一张表修改另一张表
查看>>
REHL 6 安装指南
查看>>
自定义分段选择
查看>>
我的友情链接
查看>>
工作总结 项目中如何处理重复提交问题
查看>>
mysql mysql中的索引
查看>>
利用sql语句实现到datagridview显示的转换
查看>>
本文详细介绍Python 设计模式系列之二: 创建型 Simple Factory 模式(转载)
查看>>