Unity 如何高效的解析數據


昨天和朋友聊天時,他遇到這么一個問題:現在有按照一定格式的數據,例如:
#code==text 此處是注釋
100==確定

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:“紙上得來終覺淺 絕知此事要躬行”只有在實踐中才能發現問題,交流是很好的靈感碰撞,遇到問題了多和小伙伴交流可能會有不一樣的解決方案!

傳送門:https://gitee.com/wuzhang/UnityParseData.git


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM