MemoryMappedViewStream.ReadAsync读取超出内存映射文件末尾问题
MemoryMappedViewStream读取最后一块时返回满chunkSize的问题分析与解决
你遇到的问题是MemoryMappedViewStream的ReadAsync行为特性导致的,而非你的代码逻辑错误。当使用内存映射文件的视图流读取时,即使文件剩余字节不足chunkSize,ReadAsync仍会返回请求的全部长度(剩余部分补0),而非实际剩余的有效字节数。
以你的测试场景为例:文件总长度8320字节,chunkSize=4096,前两次读取共8192字节,剩余128字节,但MemoryMappedViewStream的ReadAsync会读取4096字节(其中128是有效数据,剩下3968字节为0),因此read值始终是4096。
解决方案
方案一:显式计算剩余字节数,限制每次读取长度
在每次读取前计算剩余未处理的字节数,只读取对应长度的数据,避免读取到填充的0字节:
public static async IAsyncEnumerable<byte[]> ConvertInChunks(string filePath, int chunkSize, [EnumeratorCancellation] CancellationToken cancellationToken = default) { byte[]? buffer = null; try { buffer = ArrayPool<byte>.Shared.Rent(chunkSize); var fileLength = new FileInfo(filePath).Length; using var mm = MemoryMappedFile.CreateFromFile(filePath); await using var accessor = mm.CreateViewStream(); var bytesRead = 0L; while (bytesRead < fileLength) { // 计算剩余字节数,取chunkSize和剩余字节的较小值作为本次读取长度 var remainingBytes = fileLength - bytesRead; var readLength = (int)Math.Min(chunkSize, remainingBytes); var memory = buffer.AsMemory(0, readLength); var read = await accessor.ReadAsync(memory, cancellationToken); bytesRead += read; yield return memory[..read].ToArray(); } } finally { if (buffer is not null) ArrayPool<byte>.Shared.Return(buffer); } }
方案二:改用FileStream替代内存映射文件
如果你的场景不需要内存映射文件的特性,直接使用FileStream更符合预期,它的ReadAsync会在文件末尾返回实际剩余的字节数:
public static async IAsyncEnumerable<byte[]> ConvertInChunks(string filePath, int chunkSize, [EnumeratorCancellation] CancellationToken cancellationToken = default) { byte[]? buffer = null; try { buffer = ArrayPool<byte>.Shared.Rent(chunkSize); var memory = buffer.AsMemory(0, chunkSize); await using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: chunkSize, useAsync: true); int read; while ((read = await stream.ReadAsync(memory, cancellationToken)) > 0) { yield return memory[..read].ToArray(); } } finally { if (buffer is not null) ArrayPool<byte>.Shared.Return(buffer); } }
原因说明
内存映射文件是将文件内容映射到进程的虚拟内存空间,读取操作基于内存页机制实现。当读取到文件末尾时,系统会自动填充0来填满当前内存页,因此ViewStream的ReadAsync会返回请求的全部长度,而非实际剩余的有效数据量。这是内存映射文件的设计特性,并非bug。
内容的提问来源于stack exchange,提问作者pstrjds
相关产品推荐
相关产品推荐

