Unity 如何高效的解析資料
昨天和朋友聊天時,他遇到這麼一個問題:現在有按照一定格式的資料,例如:
#code==text 此處是註釋
101==取消
key==value這麼個格式的,說白了就是怎樣解析這些固定格式字串的Key和Value而已。他們專案已經做過了資料的解析,現在他在做專案優化,發現這一塊資料解析部分GC偏高,
何謂GC,就是 Garbage Collection, 在.Net中GC是由系統自動呼叫的,對記憶體的釋放和回收 。 程式員是特別害怕遇上GC的,資料結構設計不合理,導致系統頻繁呼叫GC,從而導致GC的代數增加,最終結果就是你的程式就會越來越卡,直到無響應!
他就問我:“要是我,我會選擇什麼樣的方式去解析這些資料?"
我當時不假思索:“格式固定,那就按這個固定的格式去解析,不就可以了!"
方法一:我們可以按照”==“進行分割,分割後剛好得到我們想要的Key和Value!剛好系統提供了N多分割的方法,在這裡剛好用字串的分割
string[] keyPair = Regex.Split(data,@"==",RegexOptions.IgnoreCase);
/// <summary> /// 正則表示式分割字串 /// </summary> /// <param name="filePath">檔案路徑</param> /// <param name="infoDic">解析後的kv</param> public static void ParseDataTable(string filePath,ref Dictionary<string, string> infoDic) { infoDic.Clear(); if (File.Exists(filePath)) { using (FileStream fs = new FileStream(filePath,FileMode.Open,FileAccess.Read)) { StreamReader sr = new StreamReader(fs); string data = sr.ReadLine(); while (data != null) { if (data.StartsWith("#"))//忽略註釋行 { data = sr.ReadLine(); continue; } else { //分割key和value string[] keyPair = Regex.Split(data,@"==",RegexOptions.IgnoreCase); if (keyPair.Length > 0) { if (!infoDic.ContainsKey(keyPair[0])) { infoDic.Add(keyPair[0], keyPair[1]); } else { Debug.LogError(string.Format("[ERROR]:Has same key:{0},value:{1}",keyPair[0],keyPair[1])); } } } data = sr.ReadLine(); } sr.Close(); fs.Close(); } } }
執行結果:

好了,方法一到此完美分割出key和value! But......
朋友說這就是他們正在使用的解析方式,正因為正則表示式使用及其的方便不需要關心它是怎麼實現的所有產生大量的GC,導致我們束手無策,因為字串匹配解析的同時會生成許多字串臨時變數,這些都要在記憶體堆上申請空間,在profiler中看到解析時有大概10M的GC。所以我想到的這個解析方式被否了!朋友讓我繼續想想有沒有什麼好的辦法,過了一會他給我說了他的想法,為何我們不自己去寫一種解析方式呢,正則耗記憶體,我們可以不用它,string臨時變數佔用記憶體我們也可以不用它改用StringBuilder來替代它。這麼說是可行的啊!無論是我們自己解析還是使用正則去解析,這個讀取還是肯定要做的,讀取後針對這個string我們逐位元組去解析,特殊字元就去特殊處理,新增特殊的標記,例如:
‘ # ’:表示該行是註釋行,解析時可以忽略;
‘ \n ’:表示要換行了,也意味著接下來key要出現了;
‘ \r ’:回車鍵的識別符號號;
‘ = ’:這是一個很重要的符號,這個要特殊照顧,對它採取計數, 奇數個 出現時剛好是 key的結束 位置, 偶數個 出現時剛好是 value的起始 位置,這裡是不是資訊量很大,你會很快的想到計數個數對2取餘做判斷處理;
也就這幾個關鍵字元,那麼接下來看如何處理,取出我們想要的key和value呢!
/// <summary> /// 資料解析 /// </summary> /// <param name="msg">內容</param> /// <returns>資料字典kv</returns> public static Dictionary<string, string> ParseDatatable(string msg) { bool isKey = false;//key開始 bool isValue = false;//value開始 bool isValueStart = false;//是否value首次檢測 int equalIndex = 0;// = 出現次數的計數 int valueStartIndex = 0;//Value的起始索引 StringBuilder sbKey = new StringBuilder(); StringBuilder sbvalue = new StringBuilder(); for (int i = 0; i < msg.Length; i++) { switch (msg[i]) { case '#': isKey = false; if (isValue)//收集顏色碼中的# sbvalue.Append(msg[i]); break; case '\r': continue; case '\n': isKey = true; isValue = false; if (!string.IsNullOrEmpty(sbKey.ToString()) && !string.IsNullOrEmpty(sbvalue.ToString())) { if (infoDic.ContainsKey(sbKey.ToString())) Debug.LogError(string.Format("[ERROR]:has the same key:{0}, value:{1}", sbKey.ToString(), sbvalue.ToString().Replace("\\n", "\n"))); else infoDic.Add(sbKey.ToString(), sbvalue.ToString().Replace("\\n","\n")); } sbKey.Remove(0, sbKey.Length); sbvalue.Remove(0, sbvalue.Length); break; case '=': if (!isValue)//忽略value裡的 = 計數 equalIndex++; if (equalIndex % 2 == 0 && equalIndex > 1)//key end { isKey = false; isValue = true; if (valueStartIndex != equalIndex) { isValueStart = true; valueStartIndex = equalIndex; } } if (isValue) { if (isValueStart && msg[i - 1] == '=')//忽略==value前最開始的那個= { isValueStart = false; continue; } sbvalue.Append(msg[i]); } break; default: if (isKey) sbKey.Append(msg[i]); else if (isValue) { if (msg[i - 1] == '\\' && msg[i + 1] == 'n')//忽略轉義字元'\' continue; sbvalue.Append(msg[i]); DealEndLine((i == msg.Length - 1), ref infoDic, sbKey, sbvalue); } break; } } return infoDic; } /// <summary> /// 行尾特殊處理 /// </summary> /// <param name="lastLine">是否最後一行</param> /// <param name="dictionary">infoDic</param> /// <param name="key">key</param> /// <param name="value">value</param> public static void DealEndLine(bool lastLine, ref Dictionary<string, string> dictionary, StringBuilder key, StringBuilder value) { if (lastLine) { if (infoDic.ContainsKey(key.ToString())) Debug.Log(string.Format("[ERROR]:has the same key:{0}, value:{1}", key.ToString(), value.ToString().Replace("\\n", "\n"))); else infoDic.Add(key.ToString(), value.ToString().Replace("\\n", "\n")); } }
這是後期比較完善的程式碼了,這裡做了以下錯誤相容:
1,相容了策劃在value裡配置==或者===,均不影響解析。
2,相容了顏色碼<color=#7893AA>{1}</color>的’#‘和’=‘,此處不再是特殊轉義字元處理。
3,相容了系統預設會新增"\\n"多個轉義字元’\‘導致在Text上無法換行的問題。
4,相容了策劃最後一行無回車換行導致無法解析的bug。
目前就發現以上問題,對以上發現問題進行了解決!
不早了,寫這麼點東西花了近三個多小時,如果有幸被您讀到請留下你的腳印,得洗洗睡了,明天回家了,祝大家十一玩的愉快!!!
PS:“紙上得來終覺淺 絕知此事要躬行”只有在實踐中才能發現問題,交流是很好的靈感碰撞,遇到問題了多和小夥伴交流可能會有不一樣的解決方案!
傳送門: UnityParseData.git" target="_blank" rel="nofollow,noindex">https://gitee.com/wuzhang/UnityParseData.git