我继承了一些执行以下步骤的代码:
- 从压缩数据的字节数组开始,对其进行流式传输和解压缩
- 将其反序列化为对象列表
- 添加至列表
- 序列化列表
- 将数据重新压缩为字节数组
private static readonly RecyclableMemoryStreamManager MemoryStreamManager = new RecyclableMemoryStreamManager();
static async Task Main(string[] args)
{
using (FileStream fileStream = new FileStream("C:\\data.txt", FileMode.Open, FileAccess.Read))
{
var dataList = await DecompressData(fileStream);
dataList.Add(new MyObject { });
using(var stream = MemoryStreamManager.GetStream())
{
await JsonSerializer.SerializeAsync(stream, dataList);
stream.Position = 0;
var b = await CompressData(stream);
}
}
Console.WriteLine("All done");
}
private static async Task<List<SipTraceRecord>> DecompressData(Stream data)
{
using (var resultStream = MemoryStreamManager.GetStream())
{
GZipStream gzip = new GZipStream(data, CompressionMode.Decompress);
List<SipTraceRecord> recordsList = await JsonSerializer.DeserializeAsync<List<MyObject>>(gzip);
return recordsList;
}
}
private static async Task<byte[]> CompressData(Stream data)
{
byte[] compressedData;
using (var ms = MemoryStreamManager.GetStream())
{
using (GZipStream gzip = new GZipStream(ms, CompressionMode.Compress))
{
data.CopyTo(gzip);
compressedData = ms.GetBuffer();
}
}
return compressedData;
}
这与代码开始时有很大不同,我只是试图尽可能地优化内存。测试输入文件为 600Kb,解压后为 22Mb,之前它使用 100Mb 的内存。现在内存已降至 90Mb。内存使用率高的区域仍然存在,例如await JsonSerializer.SerializeAsync(stream, dataList);
使用 10Mb 将数据写入流。这是否可以像在其他方向上一样进行优化,其中没有字节数组,只是根据需要进行流式传输?
这data.CopyTo(gzip);
也是重复的数据,但此时数据已被压缩,因此仅使用<1Mb
您无需反序列化和重新序列化列表,而是可以
JsonSerializer.DeserializeAsyncEnumerable()
分块流式传输输入数据,动态解压缩,然后动态压缩到包含 JSON 数组开头的某个输出流。之后,您可以流式传输和压缩新值,并将它们添加到 JSON 数组中。实现该操作的方法如下:
现在,如果您完全使用文件(这是我的建议),您可以按如下方式创建初始 JSON 文件:
要附加到文件,您可以执行以下操作:
或者,如果您确实需要使用内存中的字节数组来存储压缩数据,则可以按如下方式创建初始数组:
并创建一个连接数组,如下所示:
笔记:
即使您使用
DeserializeAsyncEnumerable()
来流式传输MemoryStream
,也必须以异步方式执行,因为没有易于使用的 API 来通过 System.Text.Json 同步流式传输 JSON 数组。DeserializeAsyncEnumerable()
将尝试从大小等于JsonSerializerOptions.DefaultBufferSize
默认值 16,384 字节的流中读取一个字节块,反序列化该块中的所有数组项,然后一次性返回它们。这可以防止在流过大型数组时内存无限增长。在.NET 9中,System.Text.Json 添加了对NDJSON(换行符分隔的 JSON格式)的支持,该格式由一系列没有外部数组括号的连接 JSON 对象组成。如果您能够迁移到 .NET 9,那么切换到该格式可能会更容易,因为您可以将
SipTraceRecord
记录附加到文件末尾,而无需通过流式传输来查找数组末尾。有关详细信息,请参阅在 .NET 中解析不带括号(没有根对象)的 JSON 对象序列文本? 。
调用
MemoryStream.ToArray()
或RecyclableMemoryStream.ToArray()
效率低下,因为它们总是会返回一个新数组,可能大到足以放入大对象堆中。由于您会遇到内存不足错误,因此这是不可取的,但由于您的 API 适用于byte[]
数组,因此很难避免这种情况。您可以考虑将 API 更改为直接处理文件或RecyclableMemoryStream
对象,而不是字节数组。.NET 6
IAsyncEnumerable<T>
中已添加对 System.Text.Json的支持。此方法在早期版本中不起作用。此处的演示小提琴:.NET 9,.NET 8。