Json序列化與反序列化導致多執行緒運行速度和單執行緒運行速度一致問題

緊跟上篇文章 十個進程開啟十個bash後一致寫入命令執行完畢之後產生了很多很多的文件,部落客需要對這些文件同意處理,也就是說對幾十萬個文件進行處理,想了又想,單執行緒處理那麼多數據肯定不行,於是乎想到了使用多執行緒,緊接著就引發了一系列問題,其中做大的問題就是json序列化,導致了多條執行緒運行和單執行緒運行時間一致問題。

我們正常去讀取json文件轉成一般是通過實體類去使用JsonConvert.DeserializeObject方法進行接收的,然後再通過實體類去進行一系列的操作,目前遇到的問題就是讀取上萬的json文件進行反序列化與序列化進行操作,如果一條一條的去操作的話速度可謂是非常非常慢,然後經過大佬的推薦和自己了解決定使用微軟專門推出的一個操作json的類。

這個類說的也非常清楚,提供高性能、低分配和標準兼容的功能,以處理 JavaScript 對象表示法 (JSON),其中包括將對象序列化為 JSON 文本以及將 JSON 文本反序列化為對象(內置 UTF-8 支援)。 它還提供類型以用於讀取和寫入編碼為 UTF-8 的 JSON 文本,以及用於創建記憶體中文檔對象模型 (DOM) 以在數據的結構化視圖中隨機訪問 JSON 元素。

雖然這個類庫專門針對於性能來優化的,但是使用起來往往非常的困難,沒有我們大眾所使用的JsonConvert.DeserializeObject方便,但是如像我一樣對待性能有極致的要求的話,可以使用這個類庫,用法也非常簡單,下面我給出例子

引入命名空間:

System.Text.Json

那麼具體如何使用呢?我這裡給出具體的使用方法以及詳細的例子。

JsonDocument document1 = null;

StreamReader f2 = new StreamReader(filePath, Encoding.UTF8);
String line;
while ((line = f2.ReadLine()) != null)
{
    document1 = JsonDocument.Parse(line);
}
f2.Close();
f2.Dispose();                

這裡我們去讀取filepath,filepath是json文件路徑。我們使用  JsonDocument.Parse對json文件進行操作。這個方法表示單個 JSON 位元組值的 UTF-8 編碼文本形式的序列分析為 JsonDocument。

把json文件序列分析為JsonDocument類型了下面就是對這個類型進行操作,為什麼現在好多人不喜歡使用這種方法,大概率是因為取值比較難

對於JsonDocument如何取出我們想要的值或者節點呢?看下面的例子:

{
  "ClassName": "Science",
  "Teacher\u0027s Name": "Jane",
  "Semester": "2019-01-01",
  "Students": [
    {
      "Name": "John",
      "Grade": 94.3
    },
    {
      "Name": "James",
      "Grade": 81.0
    },
    {
      "Name": "Julia",
      "Grade": 91.9
    },
    {
      "Name": "Jessica",
      "Grade": 72.4
    },
    {
      "Name": "Johnathan"
    }
  ],
  "Final": true
}

上述是一個json文件內的數據,我們解析JsonDocument如何解析出Class Name節點值呢?

可以使用如下操作

//定義變數去接收
var className = "0";
//判斷我們解析出來的JsonDocument是否為空
if (document1 != null)
{
    JsonElement root = document1.RootElement;
    className = root.TryGetProperty("ClassName", out var temp) ? temp.GetInt32().ToString() : "0";
}

首先使用 JsonDocument.RootElement屬性 獲取此 JSON 文檔的根元素。

對於根元素進行解析,JsonElemet.TryGetProperty() 查找當前對象中名為 ClassName 的屬性,返回一個指示此類屬性是否存在的值。 如果此屬性存在,會將其值分配給 value 參數。現在value參數對應temp,解析時還應注意屬性值類型,如果為string類型則使用GetString(),去進行轉換,int類型可以使用GetInt32(),進行轉換,具體其他類型可以查看微軟官方給出的類型。

使用TryGetProperty()方法有什麼好處呢?

可以去判斷json文件內有沒有當前的屬性,如果有的話,去返回屬性值,無,則返回null。

現在我們取單獨屬性已經會了,那麼如何取數組呢?如何取數組對象呢?

例子:

double sum = 0;
int count = 0;

using (JsonDocument document = JsonDocument.Parse(jsonString))
{
    JsonElement root = document.RootElement;
    JsonElement studentsElement = root.GetProperty("Students");
    count = studentsElement.GetArrayLength();

    foreach (JsonElement student in studentsElement.EnumerateArray())
    {
        if (student.TryGetProperty("Grade", out JsonElement gradeElement))
        {
            sum += gradeElement.GetDouble();
        }
        else
        {
            sum += 70;
        }
    }
}

double average = sum / count;
Console.WriteLine($"Average grade : {average}");

上面這個例子是微軟官方給出的例子,但是使用與我們已經知道json文件格式的情況。

使用之前我們可以使用TryGetProperty方法先判斷json屬性是否存在,存在的話去定義一個value值進行接收,先判斷是否為存在,存在則把value值進行遍歷,然後再去判斷這個數組內的屬性是否存在,存在的話返回屬性值,以此類推便可以拿到所有的json文件中的屬性值啦!

性能提升:說了這麼多到底這種方法能夠提升多少速度呢,經過部落客測試JsonConvert.DeserializeObject方法在執行緒或者進程內使用的話開啟多個和單個執行緒(進程)速度相差基本不大,就好比我開了十個執行緒,去讀取十萬個文件,結果和單個執行緒去讀取十萬個文件相差幾秒??是不是非常離譜。

換用這種高性能處理json文件的類庫之後,開啟十個執行緒去操作文件速度直接減少了三分之二!!!!!!