You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 00:10:33