zl程序教程

您现在的位置是:首页 >  云平台

当前栏目

U3D客户端框架之 拓展StringBuilder实现InsertNoGC、IndexOf、LastIndexOf、ReplaceNoGC、AppendNoGC API 减少GC

客户端框架API 实现 拓展 减少 GC StringBuilder
2023-09-11 14:22:31 时间

StringBuilderExtenion模块设计

为什么要使用StringBuilder类?

String 是引用类型,在堆上分配内存,运算时会产生一个新的实例。 的缺点是每次字符串变量的内容发生了改变时,都必须重新分配内存。试想如创建一个迭代100000次的循环,每次迭代都将一个字符连接到字符串,这样内存中就会有100000个字符串,每个字符串仅仅与前一个字符只是有一个字符不同,性能影响是很大的。

StringBuilder通过分配一个缓存,就是一个工作区来解决这些问题,在工作区中用字符串StringBuilder类的相关方法。包括添加,删除,移除,插入和替换字符等等。执行完之后,将调用ToString方法把工作区中的内容转换为一个字符串,这样StringBuilderGC次数上会有一些减少。

StringBuilderEx类 实现了 正向反向模式串匹配函数,其他的基本上都是在原API的基础上做了一些减少GC的优化。

1 InsertNoGC函数

仅允许在主线程中使用,要把已有的和新增的先拷贝到bufferCharArray里面,然后在拷贝回sb

    public static void InsertNoGC(this StringBuilder sb, string from, int startIndex, int count = -1)
    {
        if (count < 0)
        {
            count = from.Length;
        }

        if (null == from || from.Length < count || null == sb)
            return;

        //插入的位置,比stringBuilder的总长度还要长,位置无效
        if (startIndex > sb.Length)
            return;

        int originLen = sb.Length;
        if (sb.Capacity < sb.Length + count)
        {

        }

        //sb的长度-索引+本次的数量
        //512-0+512=1024
        //要把已有的和新增的先拷贝到bufferCharArray里面,然后在拷贝回sb
        //数组的动态扩容
        if (originLen - startIndex + count > bufferCharArray.Length)
        {
            /*
             * 如果没超过 MaxBufferCharLen,每次扩容就2倍扩容,
             * 如果超过了 MaxBufferCharLen,new 一个实际需要的大小
             */
            bufferCharArray = new char[Math.Max(bufferCharArray.Length * 2 > MaxBufferCharLen ?
                                                MaxBufferCharLen : bufferCharArray.Length * 2,
                                                originLen - startIndex + count)];
        }

        //把from放到最前面,容器里原本的数据往后移,有点像push_front的感觉
        from.CopyTo(0, bufferCharArray, 0, count);

        //从插入之后的位置开始拷贝数据,拷贝到bufferCharArray后面(插入startIndex之前的数据保留)
        sb.CopyTo(startIndex, bufferCharArray, count, originLen - startIndex);

        //把startIndex插入位置往后的数据全部删除,前面就是保留了原始的数据,中间是from的数据,后面又是原始的数据,这样一操作,就完成了
        //从startIndex之前插入from字符串的目的。
        sb.Remove(startIndex, originLen - startIndex);

        //把字符串buffer写入到stringBuilder里去
        sb.Append(bufferCharArray, 0, originLen - startIndex + count);
    }

2 IndexOf函数

//正向匹配,返回匹配到的第一个的开始索引


    public static int IndexOf(this StringBuilder sb, string value, int startIndex = 0, bool ignoreCase = false)
    {
        int index;
        int lenth = value.Length;
        int maxSearchLenth = (sb.Length - lenth) + 1;

        if (ignoreCase)
        {
            for (int i = startIndex; i < maxSearchLenth; ++i)
            {
                if (Char.ToLower(sb[i]) == Char.ToLower(value[0]))
                {
                    index = 1;
                    while ((index < lenth) && (Char.ToLower(sb[i + index]) == Char.ToLower(value[index])))
                        ++index;

                    if (index == lenth)
                        return i;
                }
            }

            return -1;
        }

        for (int i = startIndex; i < maxSearchLenth; ++i)
        {
            if (sb[i] == value[0])
            {
                index = 1;
                while ((index < lenth) && (sb[i + index] == value[index]))
                    ++index;

                if (index == lenth)
                    return i;
            }
        }

        return -1;
    }

3 LastIndexOf函数

仅在主线程中使用
在主字符串中查找模式串的位置
从后向前查找
查找在主串中最后一次出现模式串的起始位置(例如主串abcabc,模式串abc,最后一次出现在模式串中的起始位置是3)
如果要查找的子字符串没有出现,则返回-1

public static int LastIndexOf(this StringBuilder sb, string value)
    {

        if (string.IsNullOrEmpty(value))
            return -1;

        int index;
        int lenth = value.Length;
        int sblen = sb.Length;
        int maxSearchLenth = (sb.Length - lenth) + 1;
        //abcddd abc = 6-3 = 3

        for (int i = 0; i < maxSearchLenth; ++i)
        {
            //反着匹配
            if (sb[sblen - 1 - i] == value[lenth - 1])
            {
                index = 1;
                while ((index < lenth) && (sb[sblen - 1 - i - index] == value[lenth - 1 - index]))
                    ++index;

                //循环结束后,判断是否匹配上,如果匹配上了,则返回最后一个匹配到的起始位置
                if (index == lenth)
                    return sblen - 1 - i - lenth + 1;
            }
        }

        return -1;
    }

4 ReplaceNoGC函数

 public static StringBuilder ReplaceNoGC(this StringBuilder sb, string oldValue, string newValue, int startIndex, int count)
    {
        if (sb.Length == 0 || string.IsNullOrEmpty(oldValue) || count == 0)
        {
            return sb;
        }

        int findIdx = sb.IndexOf(oldValue, startIndex);

        //找到的index+替换旧值得长度要<=开始索引+总长度。不能超长,否则会越界
        while (findIdx >= 0 && findIdx + oldValue.Length <= startIndex + count)
        {
            //移除从找到的位置开始,到这个字符串的结束的这段长度(说的再直白点就是删除这个字符串)
            sb.Remove(findIdx, oldValue.Length);

            //新的字符串插入到老字符串的位置
            sb.InsertNoGC(newValue, findIdx);

            //位置偏移+=newValue的长度
            findIdx += newValue.Length;

            //偏移一个newValue字符串的长度,往后继续找
            findIdx = sb.IndexOf(oldValue, findIdx);
        }

        return sb;
    }

5 AppendFormatNoGC函数

 public static StringBuilder AppendFormatNoGC(this StringBuilder sb, string format, params object[] args)
    {
        int argsLen = args.Length;
        int realLen = 0;
        sb.Clear();
        sb.Append(format);

        for (int i = 0; i < argsLen; ++i)
        {
            if (null != args[i])
            {
                Type t = args[i].GetType();

                if (t.IsValueType)
                {
                    if (t == typeof(double))
                    {
                        StringHelper.m_StringParamBuffer[i] = StringHelper.DoubleToStr((double)args[i]);
                    }
                    else if (t == typeof(int))
                    {
                        StringHelper.m_StringParamBuffer[i] = StringHelper.IntToStr((int)args[i]);
                    }
                    else if (t == typeof(ulong))
                    {
                        StringHelper.m_StringParamBuffer[i] = StringHelper.ULongToStr((ulong)args[i]);
                    }
                    else if (t == typeof(float))
                    {
                        StringHelper.m_StringParamBuffer[i] = StringHelper.FloatToStr((float)args[i]);
                    }
                    else
                    {
                        StringHelper.m_StringParamBuffer[i] = args[i].ToString();
                    }
                }
                else
                {
                    StringHelper.m_StringParamBuffer[i] = args[i].ToString();
                }
                ++realLen;
            }
        }

        for (int j = 0; j < realLen; ++j)
        {
            if (null != StringHelper.m_StringParamBuffer[j])
            {
                if (j < ParamNumArray.Length)
                {
                    sb.ReplaceNoGC(ParamNumArray[j], StringHelper.m_StringParamBuffer[j]);
                }
                else
                {
                    sb.ReplaceNoGC("{" + j.ToString() + "}", StringHelper.m_StringParamBuffer[j]);
                }
            }
        }

        for (int i = 0; i < argsLen; ++i)
        {
            StringHelper.m_StringParamBuffer[i] = null;
        }

        return sb;
    }

7 测试

功能开发完成之后就要测试以下功能是否正常,有无BUG,经过测试修改好功能完好

private void TestStringBuilderEx()
    {
        Debug.Log("开始测试StringBuilderEx类");

        /*
        //insertNoGC
        Debug.Log("测试1 InsertNoGC方法测试");
        StringBuilder sbIns = new StringBuilder();
        sbIns.Append("今天是个好日子today is good day");
        Debug.Log("1.1 Append原始字符串后:" + sbIns.ToString());

        sbIns.InsertNoGC("2022年11月6日",0);
        Debug.Log("1.2 InsertNoGC 往0插入string:"+sbIns.ToString());

        sbIns.InsertNoGC('h', 4);
        Debug.Log("1.3 InsertNoGC 往4插入h字符后"+sbIns.ToString());

        sbIns.InsertNoGC(new StringBuilder("我是StringBuilder,我插进来啦!"),0);
        Debug.Log("1.4 InsertNoGC 往0插入StringBuilder后:"+sbIns.ToString());

        Debug.Log("测试1 InsertNoGC方法测试 完毕\n\r");
        */

        /*
        //indexOf
        Debug.Log("测试2 IndexOf方法测试 ");
        StringBuilder sbIdxOf = new StringBuilder("大象abcd_abc_123!!()7878*(&*…abc");

        int idx;
        idx = sbIdxOf.IndexOf("大象");
        Debug.Log("2.1 大象 index:"+ idx);

        idx = sbIdxOf.IndexOf("abcd");
        Debug.Log("2.2 abcd index:" + idx);

        idx = sbIdxOf.IndexOf("ABC",0,true);
        Debug.Log("2.3 ABC ignoreCase index:" + idx);

        idx= sbIdxOf.IndexOf(new StringBuilder("7878"), 10);
        Debug.Log("2.4 StringBuilder=7878 startIndex:10 index:" + idx);

        Debug.Log("测试2 IndexOf方法测试 完毕\n\r");
        */

        /*lastIndexOf
        Debug.Log("测试3 LastIndexOf方法测试 ");
        int lastIdx;
        StringBuilder sbLastIdxOf = new StringBuilder("我是个大笨蛋xyz_abcd_abc_123!!()笨蛋 999感冒灵*(&*…abc");
        lastIdx = sbLastIdxOf.LastIndexOf("笨蛋");
        Debug.Log("3.1 笨蛋 lastOf idx:"+lastIdx);

        lastIdx = sbLastIdxOf.LastIndexOf('a');
        Debug.Log("3.1 'a' lastOf idx:" + lastIdx);
        
        Debug.Log("测试3 LastIndexOf方法测试 完毕\n\r");
        */

        /*replaceNoGC
        StringBuilder sbRep = new StringBuilder("x.docx     y.docx      z.docx");

        Debug.Log("测试4 ReplaceNoGC 方法测试");
        Debug.Log(" 4.1 sbRep 原始字符串:"+sbRep.ToString());

        sbRep.ReplaceNoGC(".docx", ".pdf");
        Debug.Log("4.2 string string 替换成.pdf后:"+sbRep.ToString());

        sbRep.ReplaceNoGC(".pdf", new StringBuilder(".xml"));
        Debug.Log("4.2 string StringBuilder 替换成.xml 后:" + sbRep.ToString());
        Debug.Log("测试4 ReplaceNoGC 方法测试 完毕\n\r");
        */

        /*
         * appendNoGC
        Debug.Log("测试5 appendNoGC 方法测试 ");
        StringBuilder sbAppend = new StringBuilder("noting");
        Debug.Log("5.1 原始字符串:"+sbAppend.ToString());
        
        sbAppend.AppendFormatNoGC_Int(" {0}",1);
        Debug.Log("5.2 appendNoGc_Int:"+ sbAppend.ToString());

        sbAppend.AppendFormatNoGC_Int_String(" {0} {1}",2,"apple");
        Debug.Log("5.3 appendNoGc_Int_String:" + sbAppend.ToString());

        sbAppend.AppendFormatNoGC_String_Int(" {0} {1}","todayisgoodDay",3);
        Debug.Log("5.4 appendNoGc_String_Int:" + sbAppend.ToString());

        sbAppend.AppendFormatNoGC_String_String(" {0}/{1}","player:1","enemy:1");
        Debug.Log("5.5 appendNoGc_String_Int:" + sbAppend.ToString());

        sbAppend.AppendStringBuilderNoGC(new StringBuilder(" 阿巴阿巴"));
        Debug.Log("5.6 AppendStringBuilderNoGC:" + sbAppend.ToString());

        string fmt = "last 20 param:";
        int[] arrParam = new int[20];
        for (int i = 0; i < 20; ++i)
        {
            fmt += "{" + (i).ToString() + "}/";
            arrParam[i] = i + 1;
        }
        sbAppend.AppendFormatNoGC(fmt, 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20);
        Debug.Log(" 5.7 AppendFormat:"+ sbAppend.ToString());
        */
    }