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

薛小謙發表於2022-05-11

緊跟上篇文章 十個程式開啟十個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檔案的類庫之後,開啟十個執行緒去操作檔案速度直接減少了三分之二!!!!!!

相關文章