C#字符串解析相关优化
来源:互联网 发布:淘宝爱国者官方旗舰店 编辑:程序博客网 时间:2024/06/06 03:52
前两天做了游戏(unity项目)中新手引导配置文件检查的功能,配置写在Lua脚本中,询问项目中的前辈后,调用了统一的接口加载游戏中所有Lua文件,获取到LuaTable,对其中按钮路径进行了检查,主要目的是防止prefab更新后未更新配置文件,这会导致新手引导中对应的按钮找不到。反复调试检查后,自信的提交了任务,主程来验收后表示错漏百出,代码格式没遵守规范,循环中重复加载资源,反复调用得到相同结果的函数……
代码风格每个人习惯不一样没有好坏之分就忽略他了,其他通用的记下来自省,也希望能对大家有帮助。
1.函数重复调用
先上优化前代码
Hashtable ReadLuaTableAsHash() { Hashtable table = new Hashtable(); // ...具体实现读取LuaTable功能 // ... return table; } bool CheckSingle(string wnd, string btn) { bool result = true; // 具体实现检查路径功能 return result; } void Main() { Hashtable userGuideConfigTable = ReadLuaTableAsHash(); if(userGuideConfigTable!=null) { IEnumerator itr = userGuideConfigTable.Keys.GetEnumerator(); while(itr.MoveNext()) { // 得到临时的table,其中包含了单个配置信息 Hashtable temp = userGuideConfigTable[itr.Current] as Hashtable; // 单个配置信息中 interWndName 记录prefab名字 // interBtnPath 记录了prefab中 btn的路径 if (!CheckSingle(temp["interWndName"].ToString(), temp["interBtnPath"].ToString())) { Debug.Log(temp["interWndName"] + "中的节点" + temp["interBtnPath"] + " 请修改" + temp["interWndName"] + " 或者配置文件"); } } } }
代码运行起来是完全正确的,程序老鸟和细心的朋友应该已经发现哪里不对了,“[]”只是重载了方法,不管数据结构如何优化都是需要耗费性能的,需要重复使用的数据,只获取一次到变量中,于是做了如下修改。
void Main() { Hashtable userGuideConfigTable = ReadLuaTableAsHash(); if (userGuideConfigTable != null) { IEnumerator itr = userGuideConfigTable.Keys.GetEnumerator(); while (itr.MoveNext()) { // 得到临时的table,其中包含了单个配置信息 Hashtable temp = userGuideConfigTable[itr.Current] as Hashtable; // 单个配置信息中 interWndName 记录prefab名字 // interBtnPath 记录了prefab中 btn的路径 object objInterWndName = temp["interWndName"]; object objInterBtnPath = temp["objInterBtnPath"]; if (objInterWndName == null || objInterBtnPath == null) { continue; } string strInterWndName = objInterWndName.ToString(); string strInterBtnPath = objInterBtnPath.ToString(); if (!CheckSingle(strInterWndName, strInterBtnPath)) { Debug.Log(strInterWndName + "中的节点" + strInterBtnPath + " 请修改" + strInterWndName + " 或者配置文件"); } } } }
其实这个问题很基础,偶尔会忘记,但却是代码中常用到的,用得多了对性能影响自然也就大了。
2.字符串解析优化
调用统一接口加载Lua文件的时间有些长,而且对于检查配置这一功能来说,这样的操作有些浪费,所以新的需求是用读文本的形式读取Lua文件。
小科普
所有文件本质上都是二进制文件,一堆0和1,文件本身并没有意义,关键看如何解读。
于是把Lua文件当做纯文本来解析,要解决的问题主要有两个:
1.去除备注以及文本中LuaTable以外的元素
2.将LuaTable解析出来
这其中肯定要识别关键字,例如
public static Hashtable ReadLuaTable(string[] content) { Hashtable table = new Hashtable();#if LOG_PARSE_TIME long currentTime = System.DateTime.Now.Ticks;#endif string strFinal = ""; bool isFunction = false; for (int i = 0; i < content.Length; i++) { string tempStr = content[i]; #region 检查是否为注释行 if (tempStr.Contains("--")) { if (tempStr.IndexOf("--") - 1 > 0) { tempStr = tempStr.Substring(0, tempStr.IndexOf("--")); } else { tempStr = ""; } } if (tempStr.Replace(" ", "").Replace("\t", "") == "") { tempStr = ""; } #endregion #region 检查是否为函数,忽略他 if (tempStr.Contains("function")) { isFunction = true; tempStr = ""; } else if (tempStr.Contains("end")) { if (tempStr.IndexOf("end") == 0) { isFunction = false; tempStr = ""; } } if (isFunction) { tempStr = ""; } #endregion content[i] = tempStr; } strFinal = string.Join("", content); dicMatchLength = new Hashtable();#if LOG_PARSE_TIME long elapsed = System.DateTime.Now.Ticks - currentTime; Debug.Log("预处理lua文件消耗时长 : " + elapsed); currentTime = System.DateTime.Now.Ticks;#endif table = ReadLuaTable(strFinal.Replace("\\\\", "\\"), 0) as Hashtable;#if LOG_PARSE_TIME elapsed = System.DateTime.Now.Ticks - currentTime; Debug.Log("解析lua文件消耗时长 : " + elapsed);#endif return table; }
我一开始的做法显得比较笨拙,首先读取文件,采用了File.ReadAllLines()来读取文件,这里会把二进制文件逐行读成string,对每行string查找关键字,以检测是否为注释或函数,这些地方会影响到之后解析LuaTable。解析LuaTable需要逐字解析,所以最后我用string.Join()将数组重新变为单个string,不难察觉,生成这么多临时的string并不合适,这将会占用存储空间,且效率不高。
同时有几个小技巧,可以进一步优化代码。
1.string.IndexOf()与string.Contains()这个函数能够达到相同效果,当string.IndexOf(‘,’)得到的值小于0时则当前string不存在’,’。
2.content.Replace();content.Substring();等函数将会生成临时string以返回结果,尽量少用。
3.当有大量字符串操作时StringBuilder.Append();StringBuilder.Remove();要比直接用几个string相加,或者substring要快得多,StringBuilder已经事先申请好内存,所以在频繁操作的过程中省下了申请内存的时间。
嗯,这几点我当然本来也没注意,又是主程一秒点破,于是之后重构了代码。
public static Hashtable ReadLuaTable2(System.IO.StreamReader file) { string totalFile = file.ReadToEnd(); int nFileLength = totalFile.Length, nLineStartAt = 0, nLineEndAt = 0, nCount = 0; Hashtable table = new Hashtable(); System.Text.StringBuilder strFinal = new System.Text.StringBuilder(1 << 19); bool isFunction = false;#if LOG_PARSE_TIME long currentTime = System.DateTime.Now.Ticks;#endif for (int i = 0; i < nFileLength; nLineStartAt = nLineEndAt + 1, i++) { //TODO: \r nLineEndAt = totalFile.IndexOf('\n', nLineStartAt); if (nLineEndAt < 0) break; int nSubStringLength = nCount = nLineEndAt - nLineStartAt; #region 检查是否为注释行 if (!isFunction) { int indexOfComment = totalFile.IndexOf("--", nLineStartAt, nCount); if (indexOfComment > 0) { nSubStringLength = indexOfComment - nLineStartAt; } else if (indexOfComment == 0) { continue; } bool bEmptyLine = true; for (int j = nLineStartAt, jMax = nLineStartAt + nSubStringLength; j < jMax; j++) { char character = totalFile[j]; if (character != ' ' && character != '\t') { bEmptyLine = false; break; } } if (bEmptyLine) { continue; } } #endregion #region 检查是否为函数,忽略他 if (!isFunction) { if (totalFile[nLineStartAt] == 'f' && totalFile[nLineStartAt + 1] == 'u' && totalFile[nLineStartAt + 2] == 'n' && totalFile[nLineStartAt + 3] == 'c' && totalFile[nLineStartAt + 4] == 't' && totalFile[nLineStartAt + 5] == 'i' && totalFile[nLineStartAt + 6] == 'o' && totalFile[nLineStartAt + 7] == 'n') { isFunction = true; continue; } } else { if (totalFile[nLineStartAt] == 'e' && totalFile[nLineStartAt + 1] == 'n' && totalFile[nLineStartAt + 2] == 'd') { isFunction = false; continue; } } if (isFunction) { continue; } #endregion strFinal.Append(totalFile, nLineStartAt, nSubStringLength); }#if LOG_PARSE_TIME long elapsed = System.DateTime.Now.Ticks - currentTime; Debug.Log("预处理lua文件消耗时长 : " + elapsed);#endif string strFinalFinal = strFinal.ToString().Replace("\\\\", "\\"); dicMatchLength = new Hashtable();#if LOG_PARSE_TIME currentTime = System.DateTime.Now.Ticks;#endif table = ReadLuaTable(strFinalFinal, 0) as Hashtable;#if LOG_PARSE_TIME elapsed = System.DateTime.Now.Ticks - currentTime; Debug.Log("解析lua文件消耗时长 : " + elapsed);#endif return table; }
前后对比了时间,后者更稳定且一直略快于前者,第一次快20ms,多运行几次后,前者时间越来越接近后者,应该是unity内部对string的操作进行了什么神秘的优化,但总体来说,后者策略是要优于前者的,当然也视各位的具体情况而定。
3.资源重复加载
动态加载资源,无论是游戏本身还是类似我在写的检查工具都经常用到。 UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(path);
这次我吸取了上次的教训,没有重复调用这个函数,而是赋给了局部变量,之后的检查过程中,调用局部变量无需重新加载。
这么做仍然有可以优化的地方,拿我做的工具举例,每当有prefab修改都会进行一次检查,每次检查都会需要加载许多资源,但其实,常常有一些资源并没有发生改变,这里可以做一个“池”,存放我们需要的资源,避免重复加载的现象。
GameObject GetAssets(string path) { if(dicAssets.ContainsKey(path)) { return dicAssets[path]; } else { GameObject temp = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(path); dicAssets.Add(path, temp); return temp; } }
封装这么一个方法就可以在第一次检查之后省下加载资源的时间。
好的不喜欢说自己是萌新的萌新分享结束了,有错误的地方大家纠正,想要讨论问题也欢迎发邮件到lguanyuan@qq.com,之前放qq号上去发现这样qq会加了许多平时不联系的人,还是邮箱来得舒服,哈哈。
- C#字符串解析相关优化
- C# 字符串 相关操作
- c# String字符串相关
- C#字符串相关
- C#优化字符串操作
- C#优化字符串操作
- C#优化字符串操作
- C#优化字符串操作
- C#优化字符串操作
- C#优化字符串操作
- C#优化字符串操作
- C#优化字符串操作
- C#优化字符串操作
- C#优化字符串操作
- C#优化字符串操作
- C#优化字符串操作
- C#优化字符串操作
- C#优化字符串操作
- Laravel的核心概念
- source insight研究——正则表达式篇 收藏
- networking restart is deprecated
- CentOS7下使用yum安装PostgreSQL9.6
- SVM之对偶问题
- C#字符串解析相关优化
- android硬件加速
- 关于$.jBox.open从打开的新页面中传值到原来页面
- 线性表
- C++中野指针
- nginx正则表达式
- redis应用场景及实例
- autocomplete
- Java并发编程:如何创建线程?