如何在C#中以懒加载方式移除Stream中的CRLF(BCP场景)
懒加载实现BCP流CRLF过滤转换
需求背景
读取BCP工具生成的流,该流每X字节(示例为4,实际场景为2033)后包含CRLF换行符,需要将其转换为不含CRLF的流用于XML反序列化。当前实现会将整个流加载至内存,需要改为懒加载模式——仅当调用方读取输出流时才执行数据处理操作。
解决方案:自定义懒加载文本阅读器
核心思路是实现一个自定义TextReader,封装原流的读取逻辑,在调用方发起读取请求时,按需读取原流数据并自动跳过CRLF,避免提前加载整个流到内存。
自定义过滤阅读器代码
using System.IO; using System.Text; public class BcpCrlfFilterTextReader : TextReader { private readonly TextReader _innerReader; private readonly int _chunkSize; private int _charsReadInCurrentChunk = 0; public BcpCrlfFilterTextReader(TextReader innerReader, int chunkSize) { _innerReader = innerReader; _chunkSize = chunkSize; } public override int Read(char[] buffer, int index, int count) { int totalRead = 0; while (count > 0) { if (_charsReadInCurrentChunk < _chunkSize) { // 计算当前块剩余可读取的字符数,不超过请求的count int charsToRead = Math.Min(_chunkSize - _charsReadInCurrentChunk, count); int read = _innerReader.Read(buffer, index, charsToRead); if (read == 0) break; // 原流已读取完毕 totalRead += read; index += read; count -= read; _charsReadInCurrentChunk += read; // 块读取完成后,跳过后续的CRLF if (_charsReadInCurrentChunk == _chunkSize) { char[] crlfBuffer = new char[2]; _innerReader.Read(crlfBuffer, 0, 2); _charsReadInCurrentChunk = 0; } } else { // 重置块计数器(理论上不会进入此分支) _charsReadInCurrentChunk = 0; } } return totalRead; } public override void Close() { _innerReader.Close(); base.Close(); } protected override void Dispose(bool disposing) { if (disposing) { _innerReader.Dispose(); } base.Dispose(disposing); } }
修改转换类实现懒加载
using System.IO; using System.Text; using System.Xml.Linq; public class CREliminator { public const int BcpChunkSize = 4; // 返回自定义TextReader,直接支持懒加载读取 public TextReader Run(StreamReader reader) { return new BcpCrlfFilterTextReader(reader, BcpChunkSize); } // 若需返回Stream类型(兼容原方法签名),用匿名管道实现懒加载字节流转换 public Stream RunAsStream(StreamReader reader) { var pipeServer = new System.IO.Pipes.AnonymousPipeServerStream(); _ = Task.Run(async () => { using (pipeServer) using (var writer = new StreamWriter(pipeServer)) { char[] buffer = new char[1024]; int readCount; var filterReader = Run(reader); while ((readCount = filterReader.Read(buffer, 0, buffer.Length)) > 0) { await writer.WriteAsync(buffer, 0, readCount); await writer.FlushAsync(); } pipeServer.DisposeLocalCopyOfClientHandle(); } }); return new System.IO.Pipes.AnonymousPipeClientStream(pipeServer.ClientSafePipeHandle); } }
单元测试验证
using System.IO; using System.Text; using System.Xml.Linq; using Xunit; public class UnitTest1 { [Fact] public void TestLazyLoadingFilter() { var CRLF = "\r\n"; var stringData = $"<doc{CRLF}umen{CRLF}t>A<{CRLF}/doc{CRLF}umen{CRLF}t>"; var expected = stringData.Replace(CRLF, ""); // 构造测试流 var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(stringData)); var streamReader = new StreamReader(memoryStream); // 执行懒加载转换 var resultReader = new CREliminator().Run(streamReader); // 验证XML反序列化结果 var doc = XDocument.Load(resultReader); Assert.Equal(expected, doc.ToString(SaveOptions.DisableFormatting)); } }
实现说明
- 自定义
BcpCrlfFilterTextReader完全遵循懒加载逻辑:只有当调用方调用Read方法时,才会从原流读取数据、处理CRLF跳过操作,不会提前加载整个流到内存。 - 若业务要求必须返回
Stream类型,使用匿名管道流实现字符流到字节流的异步转换,同样避免了内存预加载。 - 保持了原有的CRLF过滤逻辑:每读取指定大小的块后,自动跳过后续的2个CRLF字符。
内容的提问来源于stack exchange,提问作者dani herrera
相关产品推荐
相关产品推荐

