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; }
6 测试
功能开发完成之后就要测试以下功能是否正常,有无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()); */ }