zoukankan      html  css  js  c++  java
  • .Net的StringBuilder实现在4.0和2.0区别

    .Net 4.0重构了StringBuilder的实现,采用了新的数据存储方式,不仅在效率上有大的提高,并且彻底避免了中间处理过程出现临时String对象进入LOH大对象堆的情况。本文对此进行分析。

    回顾.Net 2.0的StringBuilder实现

    Reflector查看StringBuilder的实现:

    clip_image002

    其内部数据存储结构为string(对应成员变量m_StringValue)。StringBuilder的构造函数可以依据指定的字符串和容量来初始化,默认为空串(string.Empty),默认容量为16。构造函数使用string的GetStringForStringBuilder方法对m_StringValue变量赋值:

    clip_image004

     

    查看GetStringForStringBuilder的实现,m_StringValue被视为char数组,其大小由capacity容量决定,该值可以大于实际存储的string对象的大小:

    clip_image006

    在Append等方法中,若发现当前字符数组已不能满足存储要求,则调用GetNewString方法扩展,否则用wstrcpy拷贝字符到存储区域:

    clip_image008

    GetNewString采用容量翻倍的方式扩展字符数组,如果发现翻倍的扩展不能满足要求,则直接设置为requiredLength大小

    clip_image010

    由以上分析可知,容量增长并非一定严格以16为基数增长,例如16、32、64、128,某次扩展可能导致基数发生变化。例如,假设用字符串“ABCDE”初始化一个StringBuilder,则字符数组容量为16,目前使用了其中的5个位置。随后Append一个长度为40的字符串,由于翻倍增长到32仍不能满足要求,将调整capacity=45,容量增长序列将变为16、45、90、180。

     

    .Net 2.0 StringBuilder的容量扩展有几个缺陷:

    1. 每次容量扩展都会生成一个新的string对象,其内容来自于复制老的string对象。之后,老的string对象就被废弃掉。在此过程中,若string对象大于85k(严格讲应该是85k / 2,因为是unicode字符),将会进入LOH大对象堆

    2. 随容量的增长,例如大于1M,在LOH大对象堆中寻找连续的内存空间会变得困难,尤其若LOH碎片化严重时

    3. 存在额外的拷贝消耗,会有轻微的性能损失

    4. 要处理线程安全问题,包括m_StringValue本身就是被定义为volatile,也会有轻微的性能损失

     

    可见,.Net 2.0实现的StringBuilder,内部的存储结构就是一个简单的string对象。当内部的string对象其容量不足以容纳新的Append数据时,就需要扩展容量,扩展方式为容量加倍。在容量扩展后,老的string对象及新Append的数据要拷贝到新的内存区域。这个过程中,会生成新的string对象及废弃老的string对象。如果string对象的尺寸已经大于85k,那么每次容量扩展就会有两个string对象进入到LOH中。本质上,相对于直接用“+”方式连接两个string对象,StringBuilder只是大幅降低了临时string对象的生成频率,而并不能彻底规避临时string对象的生成

    .Net 4.0新的StringBuilder实现

    .Net 4.0实现StringBuilder的方式非常精彩:

    clip_image012

    存储结构不再是string对象,而明确为字符数组(对应成员变量m_ChunkChars)。每个StringBuilder字符数组的最大大小由Max_ChunkSize限定,取值为8000(对应16进制的0x1F40)。如果需要存储的数据超过8000,怎么处理呢?奥秘就在m_ChunkPrevious成员变量上,每个StringBuilder内部会维护一个指向StringBuilder实例的指针,由此构成一个StringBuilder链表。即一个逻辑上的存储,实际是被链表上所有StringBuilder实例共同分担的。

     

    构造函数初始化m_ChunkChars的代码如下,默认字符数组大小仍为16:

    clip_image014

    容量扩展由如下方法完成:

    clip_image016

    在容量扩展时,让m_ChunkPrevious指向自己!很精彩的实现,避免了.Net 2.0时的Copy开销,仅仅就是一个指针的赋值:

    clip_image018

    而Math.Max计算决定了新字符数组的分配,其大小被控制在8000以内,从而确保不会进入LOH大对象堆。

     

    总体看,.Net 2.0主要的实现缺陷都被优化掉了。当然,由于链表方式的引入,部分方法的处理变得复杂。例如:

    clip_image020

    不过实现同样精彩,并不需要从头到尾开始复制字符,从尾到头是高效的做法。

    总结

    .Net 4.0的StringBuilder实现,将原来单一String对象的存储方式,优化为一系列的内存块(Memory Chunk)。每个StringBuilder包含一个内存块,通过引用其它StringBuilder对象的方式,形成了一个内存块的链表。通过限定每个内存块的最大大小,确保StringBuilder在容量扩展过程中不会有大对象的产生。从实现算法看,最常用的Append方法效率较.Net 2.0要高,ToString大致等效,而Insert方法效率较.Net 2.0会下降。总体看,实现相当精彩。

     

    个人认为,StringBuilder默认容量沿用16实在太小了,常常导致最初需要多次容量扩张。如果16的容量就能满足要求,那实际上就不会用StringBuilder,而直接用String+了。个人认为512或1024是较为合适的选择。

  • 相关阅读:
    flex + bison multiple parsers
    Educational Codeforces Round 95 (Rated for Div. 2)
    python学习笔记 day20 序列化模块(二)
    python学习笔记 day20 常用模块(六)
    python 学习笔记 常用模块(五)
    python学习笔记 day19 常用模块(四)
    python学习笔记 day19 常用模块(三)
    python学习笔记 day19 常用模块(二)
    python学习笔记 day19 作业讲解-使用正则表达式实现计算器
    python学习笔记 day19 常用模块
  • 原文地址:https://www.cnblogs.com/leo7718/p/3760256.html
Copyright © 2011-2022 走看看