U3D客户端框架之 拓展StringBuilder实现InsertNoGC、IndexOf、LastIndexOf、ReplaceNoGC、AppendNoGC API 减少GC
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());
*/
}
相关文章
- 客户端SDK测试是什么?如何测?
- c#实例化继承类,必须对被继承类的程序集做引用 .net core Redis分布式缓存客户端实现逻辑分析及示例demo 数据库笔记之索引和事务 centos 7下安装python 3.6笔记 你大波哥~ C#开源框架(转载) JSON C# Class Generator ---由json字符串生成C#实体类的工具
- nodejs socket server 强制关闭客户端连接
- 74分布式电商项目 - CAS客户端Demo
- [转]客户端js判断文件类型和文件大小即限制上传大小
- SSH客户端 (通过密码连接远程linux主机)
- 【C/S架构安全测试】客户端安全测试小结
- TCP客户端与服务端的连接
- 《HTML5和JavaScript Web应用开发》——第 1 章 客户端架构 1.1了解HTML5
- Android 开源框架ActionBarSherlock 和 ViewPager 仿网易新闻客户端
- H3C配置路由器作为TFTP客户端
- Intellij Idea 下 生成WebServiceClient (WS客户端)
- vue实战入门后台篇八:springboot+mybatis实现网站后台-客户端界面数据对接
- 【HMS core】【Wallet Kit】【解决方案】华为钱包的客户端示例代码为何无法运行
- 最简单易懂的webService客户端之soap+xml请求
- zookeeper源码分析之三客户端发送请求流程
- 不引用服务而使用WCF,手动编写客户端代理类
- U3D客户端框架(资源管理篇)之资源加载管理器
- U3D客户端框架(资源管理篇)之主资源加载器模块设计实现
- U3D客户端框架(资源管理篇)之可写区资源管理器
- U3D客户端框架之支持断点续传的文件下载器实现方案
- U3D客户端框架之实现基于UnityWebRequest的Http服务 实现HttpCallBackArgs参数类、HttpRoutine访问器、HttpManager管理器
- U3D客户端框架之小堆顶高性能定时器测试10W计时器耗时1.9ms