遲來的Json反序列化


  源碼發布

搞了一個下午,終於搞定了這個號稱中國的github...以后源碼直接在這里發布了(github實在用不來,英文實在太爛了)

https://code.csdn.net/jy02305022/blqw-json

  相關回顧

  廢話

自從上次發表了Json序列化的方案之后,已經整整一個月了。

原本是想序列化寫完馬上開始寫反序列化的,但是來看了大家的回復之后得到了很多啟示,所以這一個月直接在做優化的工作(當然還有帶BB)。

我發現博客園真是個好地方,以前在QQ空間,點點,微博發表技術文章的時候根本沒有人回復,了不起有幾個轉載的。。。

在這里大家一起參與討論,才能獲得更多的啟示和發現,才能更好的提高自己! 

  blqw.Json方案整體結構

blqw.Json

├─JsonBuilder                   //用於將C#轉換為Json字符串

├─QuickJsonBuilder           //快速的將任意C#對象轉換為Json字符串,繼承自JsonBuilder

├─UnsafeStringWriter        //程序集可用,未公開對象.以非安全方式訪問指針操作字符串直接寫入內存,以提高字符串拼接效率

├─JsonParser                    //用於將Json字符串轉換為C#對象

└─UnsafeJsonReader         //程序集可用,未公開對象.以非安全方式訪問指針遍歷內存中的字符串,以提高訪問效率

ps:1,2,3是序列化用的,4,5是反序列化用的,項目還引用了Literacy用於IL方式反射訪問對象屬性 

  反序列化設計

反序列化相關的類只有2個UnsafeJsonReader,JsonParser 。一個用於讀字符串,一個用於生成對象

UnsafeJsonReader 負責從Json字符串中讀取指定的內容,讀出的內容只能是最基本的String,Number,DateTime,true,false等

JsonParser 負責解析具體對象,並命令UnsafeJsonReader讀取需要的內容,如果有必要,得到對象后進行一次轉換然后賦值給對象

ps:JsonParser目前不支持轉為DataSet或DataTable,因為把我覺得沒什么必要,即使轉了轉過來都是String的也沒什么用是吧

  • 圖例

 

  • 示例代碼
//json:{"Name":"blqw","Age":27}

class User
{
    public string Name { get; set; }
    public int Age { get; set; }
}
User ToUser(string json)
{
    UnsafeJsonReader reader = new UnsafeJsonReader(json);   //構造UnsafeJsonReader,這里只是例子而已,真是情況不是這樣的

    if (reader.SkipChar('{') == false)  //跳過 左花括號 ,此操作忽略所有空格
    {                                   //如果跳過空格后第一個字符不是{,返回false,如果是返回true,且跳過{
        ThrowMissingCharException('{'); //返回false 則拋出異常 "缺少{符號"
    }

    User user = new User();
    while (true)
    {
        var pn = reader.ReadString();       //讀取一個String ,String第一次字符必須是 雙引號 或 單引號,否則會拋出異常
        if (reader.SkipChar(':') == false)  //跳過 冒號
        {
            ThrowMissingCharException(':'); //失敗拋出異常
        }
        if (pn == "Name")                   //判斷讀出的String是Name還是Age  這只是個例子.....
        {
            user.Name = reader.ReadString();//如果是Name 繼續出一個String,作為名稱
        }
        else if (pn == "Age")               
        {
            var num = reader.ReadConsts();  //如果是Age,則讀出一個常量,可能是true,false,number,null,-Infinity,Infinity
                                            //讀取失敗拋出異常
            user.Age = (int)Convert.ChangeType(num, typeof(int));//轉為int
        }
if (reader.SkipChar(',')) //跳過一個 逗號
{ continue; //成功說明還有下一個屬性 } else if (reader.Current == '}') //如果失敗,直接判斷當前字符,如果是 右花括號,說明已經結束了 { break; } else //既不是 逗號 也不是 右花括號 ,那就是作死的節奏了.... { ThrowException("錯誤的結束符號:" + reader.Current); } } reader.MoveNext(); //能到這里說明遇到右花括號了,跳過這個字符 if (reader.IsEnd()) //判斷字符串是否已經結尾了,這個操作依然會跳過空白和回車字符 { return user; } else //如果他還沒有結束,我只能遺憾的說,你贏了! { ThrowException(); } }

整體的流程大致就是這么一個情況,當然上面那個是精簡的不能再精簡的例子,真實情況要復雜很多,不過只要知道思路了對於我們程序猿來說,不是都差不多嘛

  異常設計

  • 如果User.Age是int類型的,但是Json字符串是這樣的{age:"aa"}

    這點我參考了大多數人的做法,直接拋出。第一這樣做對於性能的影響最小,第二這樣做對於調用者來說最直觀

    我之前的做法是忽略這個屬性,后來發現這樣做雖然程序沒有異常了,但往往錯誤的時候也不知道,很多值都被直接以默認值的形式插入到數據庫去了

  • 如果是Json屬性不存在對象中 json:{"Name":"blqw", "Address":"gz"},Address不是類User的屬性

    我使用一個叫SkipValue的方法,在字符串中立即跳過這個屬性對應的值部分的字符串

  • 如果對象中的屬性不在Json字符串中,就不管了,也不會有異常拋出 

  性能設計

這里是比較重點要說明的,因為這部分是花時間最多的部分。

怎么才能設計出高效的反序列化方法?

  • 1.字符串盡量只遍歷一次(UnsafeJsonReader就是用來干這個的),重復遍歷毫無意義。當然有的時候為了保持程序的可讀性不能不這樣或者那樣設計。這個時候就需要權衡可讀性和性能的取舍了。
    例如:{"number":"123.165aafdsafdsafdsafds"},有些人會這樣處理,先取出123.165aafdsafdsafdsafds,然后再判斷是否是數字,這就是多此一舉了,在遍歷到第一個a的時候到直接就可以給出不是數字的結論了,為什么還要繼續?
  • 2.所有對象只解析一次,在fastJson中,他將所有的字符串先解析成為一個List或者一個Dictionary,然后再吧Dictionary或者List解析成別的對象,這樣做雖然可以使得程序可讀性更好,便於維護。但是性能上的浪費是顯而易見的。
  • 3.在字符串轉換較慢的類型上重新實現轉換方法,比如DateTime,Number(所有數字類型)---這里比較看個人水品了
  • 4.了解不同類型在性能上的細微差別,特別是可空值類型,我覺得他就是性能殺手(參考:學習筆記1,學習筆記2)
  • 5.使用最適合的方法處理,比如ReadString這個方法會從Json字符串當前位置讀取一個String並返回,有的時候我只想跳過,不想返回,當然使用ReadString也是可以達到呀求的,只要不處理返回值就行了。但是這在性能上就有所浪費了,既然不用,那就不要返回,重新實現一個SkipString會比較合適
/// <summary> 讀取字符串
/// </summary>
public string ReadString()
{
    if (IsEnd())//已到結尾
    {
        ThrowException();
    }

    Char quot = _Current;
    if (quot != '"' && quot != '\'')
    {
        ThrowException();
    }
    MoveNext();
    if (_Current == quot)
    {
        MoveNext();
        return "";
    }

    var index = _Position;
    MiniBuffer buff = null;

    do
    {
        if (_Current == '\\')//是否是轉義符
        {
            if ((WordChars[_Current] & 16) == 0)
            {
                ThrowException();
            }
            if (buff == null)
            {
                //鎖定指針
                char* p = stackalloc char[255];
                buff = new MiniBuffer(p);
            }
            buff.AddString(_P, index, _Position - index);
            MoveNext();
        }
        MoveNext();
    } while (_Current != quot);//是否是結束字符
    string str;
    if (buff == null)
    {
        str = new string(_P, index, _Position - index);
    }
    else
    {
        buff.AddString(_P, index, _Position - index);
        str = buff.ToString();
    }
    MoveNext();
    return str;
}
ReadString
/// <summary> 跳過一個字符串
/// </summary>
public void SkipString()
{
    if (IsEnd())//已到結尾
    {
        ThrowException();
    } 
    Char quot = _Current;
    if (quot != '"' && quot != '\'')
    {
        ThrowException();
    }
    MoveNext();
    while (_Current != quot)//是否是結束字符
    {
        MoveNext();
        if (_Current == '\\')//是否是轉義符
        {
            if ((WordChars[_Current] & 16) == 0)
            {
                ThrowException();
            }
            MoveNext();
        }
    }
    MoveNext();
}
SkipString

可以看到這2個方法的代碼量都不是一個級別的,性能自然不用說

  • 6.使用指針.在類中使用指針一定要注意鎖定指針,不然很容易讀取到錯誤的內存塊
  • 7.善於使用性能分析工具幫助你找出你程序中占用時間長的函數,並合理的修改他
  • 8.防止過度設計!(這點我感覺非常重要,最早版本的反序列化一共設計了4個類,就屬於過度設計了,不僅增加了代碼的復雜度而且降低了性能,都后都精簡了)
  • 9.合理利用一些小技巧

比如,如何判斷一個char是數字還是字母?

常規的方法是

char c = '\0';
if (c >= '0' && c <= '9')
{
    // c是數字
}
else if ((c >= 'a' && c <= 'z') || c >= 'A' && c <= 'Z')
{
    //c是字母
}
else if (c == '\'' && c == '"')
{
    //c是單引號或雙引號
}
else if (c == ':')
{
    //c是冒號
}
else//if ....
{
    //....
}

修改后的方法

/// <summary>
/// <para>1: 數字</para>
/// <para>2: 字母</para>
/// <para>3: 雙引號或單引號</para>
/// <para>4: 冒號</para>
/// <para>...</para>
/// <para></para>
/// </summary>
readonly static byte[] Chars = new byte[char.MaxValue];

static Program()
{
    for (char c = '0'; c <= '9'; c++)
    {
        Chars[c] = 1;
    }
    for (char c = 'a'; c <= 'z'; c++)
    {
        Chars[c] = 2;
    }
    for (char c = 'A'; c <= 'Z'; c++)
    {
        Chars[c] = 2;
    }
    Chars['\''] = 3;
    Chars['"'] = 3;
    Chars[':'] = 4;
    //...
}
View Code
char c = '\0';
switch (Chars[c])
{
    case 1:
        // c是數字
        break;
    case 2:
        //c是字母
        break;
    case 3:
        //c是單引號或雙引號
        break;
    case 4:
        //c是冒號
        break;
    default:
        //...
        break;
}

 

  效果

  • 序列化

  • 反序列化

 

  性能

這里提供一份幾個常用Json組件的性能測試,也可以看出優化后的性能變化

  •  測試對象:

   源碼下載

包含測試代碼,這個以后就不更新了,要更新的去下面的code.csdn下載

http://files.cnblogs.com/blqw/blqw.Json.rar

 

  源碼發布

搞了一個下午,終於搞定了這個號稱中國的github...以后源碼直接在這里發布了(github實在用不來,英文實在太爛了)

https://code.csdn.net/jy02305022/blqw-json

各位看官博友,看完之后如果對你有所啟發請別忘了點一下推薦,讓其他人也可以看到

如果有不同意見歡迎留言一起討論


免責聲明!

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



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