修复 Excel 无法表示 1900 年之前日期的问题
石头 authored at 2025-10-03 17:22:44
12.13 KiB
X
# PooledByteBufferWriter 文档 ## 概述 `PooledByteBufferWriter` 是一个基于 `ArrayPool<byte>` 的高性能缓冲区写入器,实现了 `IBufferWriter<byte>` 接口。它专为需要动态扩容、避免频繁内存分配的大块连续写入场景而设计,如网络协议构建、序列化操作等。 ## 核心特性 - **池化内存管理**:基于 `ArrayPool<byte>.Shared` 进行数组租借与归还,减少 GC 压力 - **动态扩容**:支持自动扩容,近似2倍增长策略 - **IBufferWriter接口**:与 .NET 标准序列化库完全兼容 - **零拷贝操作**:提供直接的内存视图,避免不必要的数据复制 - **流操作支持**:直接写入到 `Stream`,支持同步和异步 - **安全边界检查**:防止缓冲区溢出和内存泄露 ## 构造和生命周期 ### 基础构造 ```csharp // 指定初始容量(从数组池租用) using var writer = new PooledByteBufferWriter(1024); // 也可以不使用 using,但必须手动调用 Dispose var writer2 = new PooledByteBufferWriter(4096); // ... 使用 writer2 ... writer2.Dispose(); // 必须调用以归还内存到池中 ``` ### 重用实例 ```csharp var writer = new PooledByteBufferWriter(1024); // 第一次使用 WriteData1(writer); var result1 = writer.WrittenMemory.ToArray(); writer.Clear(); // 清空内容,但保持内存不归还 // 第二次使用(复用相同的内存) WriteData2(writer); var result2 = writer.WrittenMemory.ToArray(); // 使用完毕,归还内存 writer.Dispose(); ``` ## 主要属性 | 属性 | 类型 | 说明 | |------|------|------| | `WrittenMemory` | `ReadOnlyMemory<byte>` | 已写入的内存(只读) | | `WrittenSpan` | `ReadOnlySpan<byte>` | 已写入的数据段 | | `WrittenCount` | `int` | 已写入字节数 | | `Capacity` | `int` | 当前缓冲区总容量 | | `FreeCapacity` | `int` | 剩余可写容量 | ## IBufferWriter<byte> 接口实现 ### 获取写入缓冲区 ```csharp var writer = new PooledByteBufferWriter(512); // 获取至少256字节的可写内存 Memory<byte> buffer = writer.GetMemory(256); // 获取至少256字节的可写Span Span<byte> span = writer.GetSpan(256); // 写入一些数据到span span[0] = 0x01; span[1] = 0x02; span[2] = 0x03; // 通知已写入3字节 writer.Advance(3); ``` ### 与序列化库配合使用 ```csharp // 与 System.Text.Json 配合 var writer = new PooledByteBufferWriter(1024); using var jsonWriter = new Utf8JsonWriter(writer); jsonWriter.WriteStartObject(); jsonWriter.WriteString("name", "张三"); jsonWriter.WriteNumber("age", 25); jsonWriter.WriteEndObject(); jsonWriter.Flush(); // 获取JSON字节 ReadOnlyMemory<byte> jsonBytes = writer.WrittenMemory; string json = Encoding.UTF8.GetString(jsonBytes.Span); writer.Dispose(); ``` ### 与 MessagePack 配合 ```csharp var writer = new PooledByteBufferWriter(1024); // 使用 MessagePack 序列化 MessagePackSerializer.Serialize(writer, new { Name = "张三", Age = 25 }); // 获取序列化结果 ReadOnlyMemory<byte> msgPackBytes = writer.WrittenMemory; writer.Dispose(); ``` ## 直接操作方法 ### 清空操作 ```csharp var writer = new PooledByteBufferWriter(1024); // 写入一些数据 var span = writer.GetSpan(100); span.Fill(0x42); writer.Advance(100); Console.WriteLine(writer.WrittenCount); // 输出: 100 // 仅清空内容(不归还内存,可继续使用) writer.Clear(); Console.WriteLine(writer.WrittenCount); // 输出: 0 // 清空并归还内存到池中(之后不能再使用) writer.ClearAndReturnBuffers(); ``` ### 重新初始化 ```csharp var writer = new PooledByteBufferWriter(1024); // ... 使用writer ... // 清空并归还原有内存 writer.ClearAndReturnBuffers(); // 重新初始化为新的实例 writer.InitializeEmptyInstance(2048); // 现在可以继续使用writer ``` ## 流操作 ### 写入到流(同步) ```csharp var writer = new PooledByteBufferWriter(1024); // 写入一些数据 var span = writer.GetSpan(20); Encoding.UTF8.GetBytes("Hello World".AsSpan(), span); writer.Advance(11); // 直接写入到流 using var fileStream = File.Create("output.txt"); writer.WriteToStream(fileStream); writer.Dispose(); ``` ### 写入到流(异步) ```csharp var writer = new PooledByteBufferWriter(1024); // 写入数据... WriteData(writer); // 异步写入到流 using var networkStream = GetNetworkStream(); await writer.WriteToStreamAsync(networkStream); writer.Dispose(); ``` ### 完整的异步示例 ```csharp public async Task SerializeAndSendAsync<T>(T data, Stream stream) { using var writer = new PooledByteBufferWriter(4096); // 使用JSON序列化 using var jsonWriter = new Utf8JsonWriter(writer); JsonSerializer.Serialize(jsonWriter, data); jsonWriter.Flush(); // 异步发送到网络 await writer.WriteToStreamAsync(stream); } ``` ## 扩容机制 ### 扩容策略 ```csharp // 内部扩容逻辑(简化版): // 1. 当剩余空间不足时,计算新大小 = 当前长度 + max(请求大小, 当前长度) // 2. 从数组池租用新数组 // 3. 复制已有数据到新数组 // 4. 清理并归还旧数组 var writer = new PooledByteBufferWriter(100); Console.WriteLine(writer.Capacity); // 可能输出: 128 (数组池分配的实际大小) // 写入数据触发扩容 var largeSpan = writer.GetSpan(500); // 需要500字节,但当前容量不足 // 内部会自动扩容到合适大小(通常是当前大小的2倍左右) Console.WriteLine(writer.Capacity); // 可能输出: 512 或更大 ``` ### 容量限制 ```csharp // 最大容量接近 Array.MaxLength (约2GB) const int MaxArrayLength = 0x7FFFFFC7; // 2,147,483,591 字节 // 当接近上限时,扩容策略会更保守 var writer = new PooledByteBufferWriter(1024); try { // 对于超大数据,可能会抛出 OutOfMemoryException var hugeSpan = writer.GetSpan(int.MaxValue / 2); } catch (OutOfMemoryException ex) { Console.WriteLine($"内存不足: {ex.Message}"); } ``` ## 性能优化 ### 预分配合适的初始容量 ```csharp // 不好的做法:频繁扩容 var writer1 = new PooledByteBufferWriter(16); // 初始容量太小 for (int i = 0; i < 1000; i++) { var span = writer1.GetSpan(100); // 每次都可能触发扩容... writer1.Advance(100); } // 好的做法:预估合适的初始容量 var writer2 = new PooledByteBufferWriter(100 * 1000); // 预分配足够空间 for (int i = 0; i < 1000; i++) { var span = writer2.GetSpan(100); // 不会触发扩容 writer2.Advance(100); } ``` ### 批量写入 ```csharp public void EfficientWrite(PooledByteBufferWriter writer, byte[][] dataChunks) { // 计算总大小 int totalSize = dataChunks.Sum(chunk => chunk.Length); // 一次性获取足够空间 var span = writer.GetSpan(totalSize); // 批量复制 int offset = 0; foreach (var chunk in dataChunks) { chunk.CopyTo(span.Slice(offset)); offset += chunk.Length; } writer.Advance(totalSize); } ``` ## 错误处理 ### 常见异常 ```csharp var writer = new PooledByteBufferWriter(100); try { // ArgumentOutOfRangeException: 负数advance writer.Advance(-1); } catch (ArgumentOutOfRangeException ex) { Console.WriteLine($"无效的advance值: {ex.Message}"); } try { // ArgumentOutOfRangeException: advance超出容量 writer.Advance(writer.Capacity + 1); } catch (ArgumentOutOfRangeException ex) { Console.WriteLine($"advance超出容量: {ex.Message}"); } try { // OutOfMemoryException: 请求超大空间 writer.GetSpan(int.MaxValue); } catch (OutOfMemoryException ex) { Console.WriteLine($"内存不足: {ex.Message}"); } ``` ### 资源泄露防护 ```csharp // 使用using确保资源释放 public void SafeUsage() { using var writer = new PooledByteBufferWriter(1024); // 即使发生异常,Dispose也会被调用 DoSomeWork(writer); } // 自动调用 Dispose,归还内存到池中 // 手动管理(需要确保异常安全) public void ManualUsage() { var writer = new PooledByteBufferWriter(1024); try { DoSomeWork(writer); } finally { writer.Dispose(); // 必须在finally中调用 } } ``` ## 使用场景 ### HTTP响应构建 ```csharp public async Task<byte[]> BuildHttpResponseAsync(object data) { using var writer = new PooledByteBufferWriter(4096); // 写入HTTP头部 var headerSpan = writer.GetSpan(256); var headerBytes = Encoding.ASCII.GetBytes("HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n"); headerBytes.CopyTo(headerSpan); writer.Advance(headerBytes.Length); // 写入JSON正文 using var jsonWriter = new Utf8JsonWriter(writer); JsonSerializer.Serialize(jsonWriter, data); jsonWriter.Flush(); return writer.WrittenMemory.ToArray(); } ``` ### 网络数据包构建 ```csharp public ReadOnlyMemory<byte> BuildNetworkPacket(int messageId, byte[] payload) { using var writer = new PooledByteBufferWriter(payload.Length + 64); // 写入包头 var headerSpan = writer.GetSpan(16); BinaryPrimitives.WriteInt32LittleEndian(headerSpan, 0x12345678); // Magic BinaryPrimitives.WriteInt32LittleEndian(headerSpan[4..], messageId); BinaryPrimitives.WriteInt32LittleEndian(headerSpan[8..], payload.Length); writer.Advance(12); // 写入负载 var payloadSpan = writer.GetSpan(payload.Length); payload.CopyTo(payloadSpan); writer.Advance(payload.Length); return writer.WrittenMemory; } ``` ### 文件格式生成 ```csharp public void GenerateBinaryFile(string filePath, IEnumerable<Record> records) { using var writer = new PooledByteBufferWriter(8192); using var fileStream = File.Create(filePath); // 写入文件头 var magic = "MYFORMAT"u8; var headerSpan = writer.GetSpan(magic.Length + 4); magic.CopyTo(headerSpan); BinaryPrimitives.WriteInt32LittleEndian(headerSpan[magic.Length..], records.Count()); writer.Advance(magic.Length + 4); // 写入记录 foreach (var record in records) { WriteRecord(writer, record); // 当缓冲区积累足够数据时,刷新到文件 if (writer.WrittenCount > 4096) { writer.WriteToStream(fileStream); writer.Clear(); // 重置缓冲区继续使用 } } // 写入剩余数据 if (writer.WrittenCount > 0) { writer.WriteToStream(fileStream); } } ``` ## 与其他缓冲区类型对比 ### vs. MemoryStream ```csharp // MemoryStream: 更高级但有GC压力 using var ms = new MemoryStream(); // 写入操作... byte[] result1 = ms.ToArray(); // 需要复制数据 // PooledByteBufferWriter: 更底层但零拷贝 using var writer = new PooledByteBufferWriter(1024); // 写入操作... ReadOnlyMemory<byte> result2 = writer.WrittenMemory; // 无复制 ``` ### vs. ArrayBufferWriter<byte> ```csharp // ArrayBufferWriter: 使用托管数组 var arrayWriter = new ArrayBufferWriter<byte>(); // 写入操作... // 内存不会自动归还,依赖GC回收 // PooledByteBufferWriter: 使用数组池 using var pooledWriter = new PooledByteBufferWriter(1024); // 写入操作... // 使用完毕自动归还到池中,减少GC压力 ``` ## 最佳实践 1. **合理预估初始容量**:避免频繁扩容影响性能 2. **及时释放资源**:使用 `using` 语句或确保调用 `Dispose` 3. **批量操作**:尽量减少 `GetSpan`/`Advance` 调用次数 4. **异常安全**:在 `finally` 块中确保资源释放 5. **复用实例**:对于频繁操作,可以使用 `Clear()` 复用同一实例 6. **监控内存使用**:在高并发场景下监控数组池的使用情况 ## 限制与注意事项 1. **非线程安全**:单个实例不能跨线程并发使用 2. **生命周期管理**:必须正确调用 `Dispose` 以归还内存 3. **最大容量限制**:接近 2GB 的理论上限 4. **数组池依赖**:依赖 `ArrayPool<byte>.Shared` 的实现质量 5. **内存清理**:归还前会清理敏感数据,可能有轻微性能开销 ## 相关类型 - [`SpanReader`](/NewLife/X/Blob/dev/Doc/./SpanReader.md) - 高性能字节流读取器 - [`SpanWriter`](/NewLife/X/Blob/dev/Doc/./SpanWriter.md) - 高性能字节流写入器(固定缓冲区) - [`SpanHelper`](/NewLife/X/Blob/dev/Doc/./SpanHelper.md) - Span相关的辅助方法