Tác giả: Abdul Rahman (Bhai)
Cập nhật lần cuối: 7 ngày trước
Nghĩ rằng JsonSerializer đã nhanh? Đúng vậy – nhưng nó không phải là nhanh nhất. Khi bạn cần hiệu suất tối đa với phân bổ bộ nhớ tối thiểu, bạn cần Utf8JsonReader và Utf8JsonWriter. Trong bài viết này, chúng tôi sẽ chỉ cho bạn cách tận dụng các API cấp thấp này để xử lý JSON với tốc độ cực nhanh.
Mục lục
Mục Lục
Chúng ta sẽ làm gì?
Nghĩ JsonSerializer đã nhanh? Đúng vậy – nhưng nó không phải là nhanh nhất. Khi bạn cần hiệu suất tối đa với phân bổ bộ nhớ tối thiểu, bạn cần Utf8JsonReader và Utf8JsonWriter. Trong bài viết này, chúng tôi sẽ chỉ cho bạn cách tận dụng các API cấp thấp này để xử lý JSON với tốc độ cực nhanh.
Tại sao chúng ta làm điều này?
Đây là sự thật: JsonSerializer sử dụng Utf8JsonReader và Utf8JsonWriter ở bên dưới. Đây là các nguyên thủy cấp thấp cung cấp năng lượng cho tất cả các quá trình tuần tự hóa cấp cao mà bạn sử dụng hàng ngày. Vậy tại sao sử dụng chúng trực tiếp? Hai lý do: tốc độ và kiểm soát.
Khi bạn xử lý các tệp JSON khổng lồ (nghĩ về các tệp nhật ký nhiều gigabyte hoặc phản hồi API streaming), chi phí tạo đối tượng .NET tăng lên nhanh chóng. Utf8JsonReader và Utf8JsonWriter cho phép bạn xử lý JSON token bằng token – chỉ đọc hoặc viết những gì bạn cần mà không bao giờ phân bổ đối tượng cho các phần bạn không quan tâm.
Đây là các API chỉ tiến về phía trước, không cache. Bạn đọc hoặc viết tuần tự, đó chính là điều làm cho chúng hiệu quả. Hãy nghĩ về nó như streaming video so với tải toàn bộ tệp – bạn chỉ xử lý những gì bạn cần, khi bạn cần.
Chúng ta sẽ làm thế nào?
Đây là cách đạt được xử lý JSON hiệu suất cao với Utf8JsonReader và Utf8JsonWriter:
Bước 1: Hiểu về Utf8JsonWriter
Utf8JsonWriter là một trình ghi chỉ tiến về phía trước để tạo JSON được mã hóa UTF-8. Bạn viết các phần tử tuần tự – bắt đầu một đối tượng hoặc mảng, viết thuộc tính và giá trị, sau đó kết thúc đối tượng hoặc mảng. Nó xác nhận cấu trúc JSON khi bạn viết, vì vậy JSON không hợp lệ sẽ gây ra ngoại lệ (trừ khi bạn tắt xác nhận).
Bước 2: Viết JSON với Utf8JsonWriter
Viết vào một Stream, IBufferWriter<byte> hoặc bất kỳ đích UTF-8 có thể ghi nào.
using System.Text.Json;
var options = new JsonWriterOptions
{
Indented = true // Định dạng đẹp đầu ra
};
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream, options);
// Bắt đầu viết đối tượng JSON
writer.WriteStartObject();
writer.WriteString("userId", "12345");
writer.WriteNumber("temperature", 22);
writer.WriteBoolean("isActive", true);
// Viết một đối tượng lồng nhau
writer.WritePropertyName("location");
writer.WriteStartObject();
writer.WriteString("city", "London");
writer.WriteNumber("latitude", 51.5074);
writer.WriteNumber("longitude", -0.1278);
writer.WriteEndObject();
// Viết một mảng
writer.WritePropertyName("readings");
writer.WriteStartArray();
writer.WriteNumberValue(18);
writer.WriteNumberValue(20);
writer.WriteNumberValue(22);
writer.WriteEndArray();
writer.WriteEndObject();
writer.Flush();
// Lấy JSON dưới dạng chuỗi
string json = Encoding.UTF8.GetString(stream.ToArray());
Console.WriteLine(json);
/* Output:
{
"userId": "12345",
"temperature": 22,
"isActive": true,
"location": {
"city": "London",
"latitude": 51.5074,
"longitude": -0.1278
},
"readings": [18, 20, 22]
}
*/
Bước 3: Viết giá trị Null và JSON thô
Bạn có thể viết giá trị null và thậm chí các chuỗi JSON thô trực tiếp.
writer.WriteStartObject();
// Viết một thuộc tính null
writer.WriteNull("middleName");
// Viết JSON thô (bỏ qua xác nhận – sử dụng cẩn thận!)
writer.WritePropertyName("metadata");
writer.WriteRawValue("""{"version": "2.0", "build": 42}""");
writer.WriteEndObject();
writer.Flush();
/* Output:
{
"middleName": null,
"metadata": {"version": "2.0", "build": 42}
}
*/
Bước 4: Thực hành hiệu suất tốt nhất cho Utf8JsonWriter
- Viết UTF-8 trực tiếp: Utf8JsonWriter được tối ưu cho UTF-8. Tránh chuyển đổi UTF-16.
- Tái sử dụng trình ghi: Đặt lại và tái sử dụng trình ghi thay vì tạo phiên bản mới.
- Flush khi hoàn thành: Luôn gọi
Flush()để đảm bảo dữ liệu được ghi. - Tắt xác nhận để tăng tốc: Đặt
SkipValidation = truetrong tùy chọn nếu bạn chắc chắn JSON của bạn hợp lệ và cần thêm tốc độ.
Bước 5: Hiểu về Utf8JsonReader
Utf8JsonReader là một trình đọc chỉ tiến về phía trước, phân bổ thấp xử lý JSON token bằng token. Nó là một ref struct, có nghĩa là nó sống trên stack và không thể được truyền theo giá trị – chỉ theo tham chiếu.
Bước 6: Đọc JSON với Utf8JsonReader
Đọc từ ReadOnlySpan<byte> hoặc ReadOnlySequence<byte>. Trình đọc tiến token bằng token, và bạn kiểm tra TokenType để xác định những gì bạn đang đọc.
string jsonString = """
{
"userId": "12345",
"temperature": 22,
"readings": [18, 20, 22]
}
""";
byte[] jsonBytes = Encoding.UTF8.GetBytes(jsonString);
var reader = new Utf8JsonReader(jsonBytes);
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonTokenType.PropertyName:
string propertyName = reader.GetString();
Console.WriteLine($"Property: {propertyName}");
break;
case JsonTokenType.String:
string stringValue = reader.GetString();
Console.WriteLine($" String Value: {stringValue}");
break;
case JsonTokenType.Number:
int numberValue = reader.GetInt32();
Console.WriteLine($" Number Value: {numberValue}");
break;
case JsonTokenType.StartObject:
Console.WriteLine("Start Object");
break;
case JsonTokenType.EndObject:
Console.WriteLine("End Object");
break;
case JsonTokenType.StartArray:
Console.WriteLine("Start Array");
break;
case JsonTokenType.EndArray:
Console.WriteLine("End Array");
break;
}
}
/* Output:
Start Object
Property: userId
String Value: 12345
Property: temperature
Number Value: 22
Property: readings
Start Array
Number Value: 18
Number Value: 20
Number Value: 22
End Array
End Object
*/
Bước 7: Tìm kiếm giá trị cụ thể
Bạn có thể sử dụng ValueTextEquals để so sánh tên thuộc tính hiệu quả – nó xử lý các ký tự escape tự động.
byte[] searchProperty = Encoding.UTF8.GetBytes("temperature");
var reader = new Utf8JsonReader(jsonBytes);
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.PropertyName &&
reader.ValueTextEquals(searchProperty))
{
reader.Read(); // Di chuyển đến giá trị
int temperature = reader.GetInt32();
Console.WriteLine($"Found temperature: {temperature}°C");
break; // Dừng tìm kiếm khi tìm thấy
}
}
// Output: Found temperature: 22°C
Bước 8: Xử lý Byte Order Mark (BOM)
Nếu JSON của bạn có thể có BOM, hãy loại bỏ nó trước khi đọc để tránh lỗi.
byte[] utf8Bom = new byte[] { 0xEF, 0xBB, 0xBF };
byte[] jsonData = File.ReadAllBytes("data.json");
// Loại bỏ BOM nếu có
if (jsonData.Length >= 3 &&
jsonData[0] == utf8Bom[0] &&
jsonData[1] == utf8Bom[1] &&
jsonData[2] == utf8Bom[2])
{
jsonData = jsonData[3..]; // Bỏ qua 3 byte đầu tiên
}
var reader = new Utf8JsonReader(jsonData);
// Bây giờ an toàn để đọc
Bước 9: Thực hành hiệu suất tốt nhất cho Utf8JsonReader
- Đọc UTF-8 trực tiếp: Chuyển đổi chuỗi UTF-16 sang byte UTF-8 trước khi đọc để có hiệu suất tốt nhất.
- Sử dụng ValueTextEquals: Để so sánh thuộc tính, phương pháp này nhanh hơn và xử lý escaping.
- Biết cấu trúc của bạn: Tìm kiếm tuần tự chậm; điều hướng trực tiếp nhanh hơn.
- Đệm các stream lớn: Đối với các tệp trên 1GB, đọc theo khối để tránh tải mọi thứ vào bộ nhớ.
- Truyền theo tham chiếu: Vì nó là ref struct, truyền
ref Utf8JsonReadercho các phương thức.
Bước 10: Khi nào sử dụng API cấp thấp vs JsonSerializer
Sử dụng Utf8JsonReader/Writer khi:
- Bạn cần hiệu suất tối đa và phân bổ tối thiểu
- Xử lý các tệp khổng lồ (nhật ký nhiều GB, dữ liệu streaming)
- Bạn chỉ cần các phần cụ thể của một payload JSON lớn
- Xây dựng các serializer hoặc parser tùy chỉnh
Sử dụng JsonSerializer khi:
- Bạn có các lớp được định kiểu để tuần tự hóa/giải tuần tự hóa
- Tiện lợi và dễ sử dụng quan trọng hơn tốc độ thô
- JSON payloads có kích thước hợp lý (dưới 10MB)
Tổng kết
Utf8JsonReader và Utf8JsonWriter là vũ khí bí mật để xử lý JSON hiệu suất cao trong .NET. Chúng đánh đổi sự tiện lợi để lấy tốc độ, kiểm soát và sử dụng bộ nhớ tối thiểu. Sử dụng chúng khi bạn xử lý các tệp khổng lồ, xây dựng parser tùy chỉnh hoặc khi mỗi mili giây và megabyte đều quan trọng. Đối với mọi thứ khác, JsonSerializer đã hỗ trợ bạn – bởi vì nó sử dụng chính các API này ở bên dưới.
Thẻ: JSON, Utf8JsonReader, Utf8JsonWriter, JSON, Performance, Low-level, System.Text.Json, Streaming, UTF-8



