Sự phát triển của các phần cứng hiện đại giúp tăng sức mạnh xử lý, băng thông mạng và không gian đĩa. Nhờ điều này, chúng ta có nên tận dụng tối đa những nguồn lực này mà không cần suy nghĩ? Ồ không! Chúng ta nên lưu ý đến việc sử dụng tài nguyên của mình và cách nó ảnh hưởng đến việc chạy ứng dụng tổng thể của chúng ta.
Bài đăng này sẽ chỉ ra cách chúng ta có thể sử dụng các thuật toán nén trong namespace System.IO.Compression
để nén và giải nén một giá trị string
. Nén các giá trị sẽ dẫn đến giảm đáng kể số lượng byte.
Nén trong vật lý là sự giảm kích thước bằng cách dùng các lực đè lên một vật. Về mặt dữ liệu, nén sẽ chuyển đổi dữ liệu sang một định dạng nhỏ hơn mà không làm mất thông tin có thể nhận thấy được.
Nén dữ liệu sử dụng các thuật toán để mã hóa thông tin hiện có thành những bit nhỏ nhất có thể. Các thuật toán khác nhau có mức độ hiệu quả khác nhau nhưng thường có sự đánh đổi về thời gian nén hoặc yêu cầu xử lý CPU để đạt được kết quả mong muốn. Trong khoa học máy tính, đây là sự đánh đổi giữa sự phức tạp không-thời gian .
Các nhà phát triển nên đánh giá các yếu tố sau khi chọn một thuật toán nén dữ liệu:
Các thuật toán nén dữ liệu có sẵn cho các nhà phát triển .NET là gì?
Khi sử dụng .NET 5, các nhà phát triển có quyền truy cập vào namespace System.IO.Compression
có hai thuật toán GZip
và Brotli
để nén dữ liệu.
Gzip là một thuật toán nén dữ liệu không mất dữ liệu. Thuật toán bao gồm kiểm tra dự phòng để phát hiện hỏng dữ liệu. Người dùng Linux có thể đã quen thuộc với tiện ích mở rộng .gz
này, vì nó thường được sử dụng trong Unix.
Người sáng tạo đã tối ưu hóa Gzip cho dữ liệu chưa được nén. Do đó, việc nén dữ liệu đã được nén bằng Gzip có thể tăng kích thước so với kích thước đã nén ban đầu.
Brotli là một thuật toán nén dữ liệu không mất dữ liệu khác được phát triển tại Google và phù hợp nhất để nén văn bản. Như bạn có thể đã đoán, Brotli lý tưởng để phân phối nội dung web, chủ yếu hoạt động trên HTML, JavaScript và CSS.
Brotli được coi là người kế nhiệm của Gzip và hầu hết các trình duyệt web lớn đều hỗ trợ nó. Nó cũng cung cấp khả năng nén dữ liệu tốt hơn nhiều so với người tiền nhiệm của nó, Gzip.
May mắn thay, các nhà phát triển .NET có quyền truy cập vào cả hai thuật toán nén dữ liệu được đề cập ở trên dưới dạng GZipStream
và BrotliStream
. Cả hai lớp đều có API và đầu vào giống hệt nhau.
var value = "hello world";
var level = CompressionLevel.Fastest;
var bytes = Encoding.Unicode.GetBytes(value);
await using var input = new MemoryStream(bytes);
await using var output = new MemoryStream();
// GZipStream with BrotliStream
await using var stream = new GZipStream(output, level);
await input.CopyToAsync(stream);
var result = output.ToArray();
var resultString = Convert.ToBase64String(result);
Chúng ta cũng có thể tạo các phương thức mở rộng để làm cho các thuật toán nén này dễ sử dụng hơn trong codebase của chúng ta.
public static class Compression
{
public static async Task<CompressionResult> ToGzipAsync(this string value, CompressionLevel level = CompressionLevel.Fastest)
{
var bytes = Encoding.Unicode.GetBytes(value);
await using var input = new MemoryStream(bytes);
await using var output = new MemoryStream();
await using var stream = new GZipStream(output, level);
await input.CopyToAsync(stream);
var result = output.ToArray();
return new CompressionResult(
new CompressionValue(value, bytes.Length),
new CompressionValue(Convert.ToBase64String(result), result.Length),
level,
"Gzip");
}
public static async Task<CompressionResult> ToBrotliAsync(this string value, CompressionLevel level = CompressionLevel.Fastest)
{
var bytes = Encoding.Unicode.GetBytes(value);
await using var input = new MemoryStream(bytes);
await using var output = new MemoryStream();
await using var stream = new BrotliStream(output, level);
await input.CopyToAsync(stream);
await stream.FlushAsync();
var result = output.ToArray();
return new CompressionResult(
new CompressionValue(value, bytes.Length),
new CompressionValue(Convert.ToBase64String(result), result.Length),
level,
"Brotli"
);
}
public static async Task<string> FromGzipAsync(this string value)
{
var bytes = Convert.FromBase64String(value);
await using var input = new MemoryStream(bytes);
await using var output = new MemoryStream();
await using var stream = new GZipStream(input, CompressionMode.Decompress);
await stream.CopyToAsync(output);
await stream.FlushAsync();
return Encoding.Unicode.GetString(output.ToArray());
}
public static async Task<string> FromBrotliAsync(this string value)
{
var bytes = Convert.FromBase64String(value);
await using var input = new MemoryStream(bytes);
await using var output = new MemoryStream();
await using var stream = new BrotliStream(input, CompressionMode.Decompress);
await stream.CopyToAsync(output);
return Encoding.Unicode.GetString(output.ToArray());
}
}
public record CompressionResult(
CompressionValue Original,
CompressionValue Result,
CompressionLevel Level,
string Kind
)
{
public int Difference =>
Original.Size - Result.Size;
public decimal Percent =>
Math.Abs(Difference / (decimal) Original.Size);
}
public record CompressionValue(
string Value,
int Size
);
Bây giờ chúng ta có thể sử dụng chúng để nén bất kỳ chuỗi nào.
var comedyOfErrors = await File.ReadAllTextAsync("the-comedy-of-errors.txt");
var compressions = new[]
{
await comedyOfErrors.ToGzipAsync(),
await comedyOfErrors.ToBrotliAsync()
};
var table = new Table()
.MarkdownBorder()
.Title("compression in bytes")
.ShowHeaders()
.AddColumns("kind", "level", "before", "after", "difference", "% reduction");
foreach (var result in compressions)
{
table
.AddRow(
result.Kind,
result.Level.ToString(),
result.Original.Size.ToString("N0"),
result.Result.Size.ToString("N0"),
result.Difference.ToString("N0"),
result.Percent.ToString("P")
);
}
AnsiConsole.Render(table);
Loại | Xếp loại | Trước | Sau | Giảm | Tỷ lệ nén |
---|---|---|---|---|---|
Gzip | Fastest | 186,500 | 30,310 | 156,190 | 83.75 % |
Brotli | Fastest | 186,500 | 49,424 | 137,076 | 73.50 % |
Trong ví dụ này, tôi tải vở kịch The Comedy of Errors của Shakespeare từ một tệp văn bản và nén nó. Điều thú vị là nén Gzip tốt hơn Brotli trong trường hợp này.
Namespace System.IO.Compression
cũng có một lớp BrotliEncoder
mà chúng ta có thể sử dụng để nén các chuỗi. Để sử dụng nó một cách hiệu quả, chúng ta sẽ cần tham chiếu đến gói nuget System.Memory
. Gói bổ sung cho phép chúng tôi dịch các mảng hiện có thành các kiểu Span
rõ ràng hoặc ngầm định.
// compression
var source = Encoding.Unicode.GetBytes(comedyOfErrors);
var memory = new byte[source.Length];
var encoded = BrotliEncoder.TryCompress(
source,
memory,
out var encodedBytes
);
Console.WriteLine($"compress bytes: {encodedBytes}");
// decompression
var target = new byte[memory.Length];
BrotliDecoder.TryDecompress(memory, target, out var decodedBytes);
Console.WriteLine($"decompress bytes: {decodedBytes}");
var value = Encoding.Unicode.GetString(target);
Điều thú vị là, khi sử dụng BrotliEncoder
, chúng tôi nhận được kết quả là 33,090
byte, hiệu quả hơn so với sử dụng BrotliStream
trực tiếp có kích thước là 49,424
byte.
Một thành viên trong cộng đồng nhận thấy rằng tôi đã không đạt được mức nén tối ưu nhất bằng thuật toán nén Brotli
của mình. Theo cách nói của bạn: “Khi CompressionLevel.Optimal đang được sử dụng, luồng nén đích nên được flush trước khi cố gắng trích xuất các byte từ luồng bên dưới.”
private static async Task<CompressionResult> ToCompressedStringAsync(
string value,
CompressionLevel level,
string algorithm,
Func<Stream, Stream> createCompressionStream)
{
var bytes = Encoding.Unicode.GetBytes(value);
await using var input = new MemoryStream(bytes);
await using var output = new MemoryStream();
await using var stream = createCompressionStream(output);
await input.CopyToAsync(stream);
// calling to flush the stream first to get optimal
// compression results
await output.FlushAsync();
var result = output.ToArray();
return new CompressionResult(
new(value, bytes.Length),
new(Convert.ToBase64String(result), result.Length),
level,
algorithm);
}
Với mẫu mã cập nhật đang được cập nhật như sau:
public static class Compression
{
private static async Task<CompressionResult> ToCompressedStringAsync(
string value,
CompressionLevel level,
string algorithm,
Func<Stream, Stream> createCompressionStream)
{
var bytes = Encoding.Unicode.GetBytes(value);
await using var input = new MemoryStream(bytes);
await using var output = new MemoryStream();
await using var stream = createCompressionStream(output);
await input.CopyToAsync(stream);
await stream.FlushAsync();
var result = output.ToArray();
return new CompressionResult(
new(value, bytes.Length),
new(Convert.ToBase64String(result), result.Length),
level,
algorithm);
}
public static async Task<CompressionResult> ToGzipAsync(this string value, CompressionLevel level = CompressionLevel.Fastest)
=> await ToCompressedStringAsync(value, level, "GZip", s => new GZipStream(s, level));
public static async Task<CompressionResult> ToBrotliAsync(this string value, CompressionLevel level = CompressionLevel.Fastest)
=> await ToCompressedStringAsync(value, level, "Brotli", s => new BrotliStream(s, level));
private static async Task<string> FromCompressedStringAsync(string value, Func<Stream, Stream> createDecompressionStream)
{
var bytes = Convert.FromBase64String(value);
await using var input = new MemoryStream(bytes);
await using var output = new MemoryStream();
await using var stream = createDecompressionStream(input);
await stream.CopyToAsync(output);
await output.FlushAsync();
return Encoding.Unicode.GetString(output.ToArray());
}
public static async Task<string> FromGzipAsync(this string value)
=> await FromCompressedStringAsync(value, s => new GZipStream(s, CompressionMode.Decompress));
public static async Task<string> FromBrotliAsync(this string value)
=> await FromCompressedStringAsync(value, s => new BrotliStream(s, CompressionMode.Decompress));
}
public record CompressionResult(
CompressionValue Original,
CompressionValue Result,
CompressionLevel Level,
string Kind
)
{
public int Difference =>
Original.Size - Result.Size;
public decimal Percent =>
Math.Abs(Difference / (decimal) Original.Size);
}
public record CompressionValue(
string Value,
int Size
);
Kết quả sau khi thay đổi mã phản ánh đầu ra chính xác hơn.
┌────────┬─────────┬─────────┬────────┬────────────┬─────────────┐
│ kind │ level │ before │ after │ difference │ % reduction │
├────────┼─────────┼─────────┼────────┼────────────┼─────────────┤
│ GZip │ Fastest │ 180,098 │ 52,272 │ 127,826 │ 70.976% │
│ GZip │ Optimal │ 180,098 │ 41,175 │ 138,923 │ 77.137% │
│ Brotli │ Fastest │ 180,098 │ 48,408 │ 131,690 │ 73.121% │
│ Brotli │ Optimal │ 180,098 │ 32,833 │ 147,265 │ 81.769% │
└────────┴─────────┴─────────┴────────┴────────────┴─────────────┘
compress bytes: 32832
decompress bytes: 180098
Tuy nhiên, rất ấn tượng khi thấy tỷ lệ nén trên 70%.
Nén dữ liệu là một phần không thể thiếu trong phát triển phần mềm hiện đại; trong hầu hết các trường hợp, nén là một tính năng cấp thấp của máy chủ web hoặc framework.
Chúng ta chỉ cần kích hoạt nó và nhận được lợi ích của tải trọng nhỏ hơn và giảm sử dụng băng thông. Thật tuyệt vời khi biết rằng chúng ta có thể tận dụng namespace System.IO.Compression
để nén bất kỳ dữ liệu nào mà chúng ta chọn theo cách thủ công. Và như mọi khi, hãy đảm bảo flush luồng của bạn.
Tôi hy vọng bạn thấy bài viết này hữu ích và cảm ơn bạn đã đọc.
Bạn có thể vui lòng tắt trình chặn quảng cáo ❤️ để hỗ trợ chúng tôi duy trì hoạt động của trang web.
Trong loạt bài này, tôi sẽ xem xét một số
Ngôn ngữ C# đã bật các bộ tăng áp liên
Trong bài viết này, chúng ta sẽ tìm hiểu lớp tiện ích ZipFile trong C#, cách nén tập tin và thư mục, cùng với giải nén tập tin zip.
Bài viết này sẽ giới thiệu cách đơn giản nhất mà tôi đã tìm thấy để đọc và ghi file Excel bằng C# sử dụng ExcelMapper.