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

C#如何通过Stream直接为FlatBufferBuilder创建String避免二次内存拷贝

实现从Stream直接写入FlatBuffer字符串的方案

完全可以实现该需求,核心思路是绕过中间string对象生成步骤,直接按照FlatBuffer字符串的存储规范,把Stream读取的内容分段写入FlatBufferBuilder的内部缓冲区,仅需要固定大小的中转缓冲即可完成操作,无需加载全量数据到内存。


核心原理

FlatBuffer中string类型的存储结构为:4字节长度前缀(存储UTF-8字节数,不含终止符) + UTF-8编码字节数组 + 1字节\0终止符,且需要满足4字节对齐规则。我们只需要按照这个结构直接操作FlatBufferBuilder的底层写入方法,就能跳过CreateString的string入参重载,实现流数据直写。


实现代码

我们可以给FlatBufferBuilder写一个扩展方法,兼容直接读UTF-8字节流、以及非UTF-8编码文本流转码写入两种场景:

using Google.FlatBuffers;
using System.Text;

public static class FlatBufferExtensions
{
    public static Offset<String> CreateStringFromStream(this FlatBufferBuilder fbb, Stream stream, Encoding textEncoding = null)
    {
        // 默认用UTF-8编码,和FlatBuffer官方字符串编码规则一致
        textEncoding ??= Encoding.UTF8;
        
        // 先做4字节对齐,满足FlatBuffer的对齐要求
        fbb.Prep(4, 1);
        int lengthPrefixPos = fbb.Offset;
        // 先预留4字节的长度位,等写完所有内容再回填
        fbb.Offset += 4;

        long totalUtf8Bytes = 0;
        // 4KB中转缓冲,可根据业务场景调整大小,全程复用不需要扩容
        byte[] byteBuffer = new byte[4096];

        // ========== 如果是文本流需要转码,用这段逻辑替代下面的直接读字节逻辑 ==========
        // using var reader = new StreamReader(stream, textEncoding);
        // char[] charBuffer = new char[1024];
        // var encoder = textEncoding.GetEncoder();
        // int charReadCount;
        // while ((charReadCount = reader.Read(charBuffer, 0, charBuffer.Length)) > 0)
        // {
        //     int byteNeedCount = encoder.GetByteCount(charBuffer, 0, charReadCount, false);
        //     if (byteBuffer.Length < byteNeedCount) byteBuffer = new byte[byteNeedCount];
        //     int byteWriteCount = encoder.GetBytes(charBuffer, 0, charReadCount, byteBuffer, 0, false);
        //     fbb.PutBytes(byteBuffer, 0, byteWriteCount);
        //     totalUtf8Bytes += byteWriteCount;
        // }
        // =======================================================================

        // 直接读取UTF-8字节流的逻辑
        int byteReadCount;
        while ((byteReadCount = stream.Read(byteBuffer, 0, byteBuffer.Length)) > 0)
        {
            fbb.PutBytes(byteBuffer, 0, byteReadCount);
            totalUtf8Bytes += byteReadCount;
        }

        // 写入字符串要求的\0终止符
        fbb.PutByte(0);

        // 回填4字节长度前缀
        int currentOffset = fbb.Offset;
        fbb.Offset = lengthPrefixPos;
        fbb.PutInt((int)totalUtf8Bytes);
        fbb.Offset = currentOffset;

        // 返回FlatBuffer要求的字符串偏移量
        return new Offset<String>(fbb.Offset - lengthPrefixPos - 4);
    }
}

调用示例

替换你原来的读全量字符串逻辑即可,无需修改其他FlatBuffer构造代码:

using var myStream = File.OpenRead("你的大文件路径");
var fbb = new FlatBufferBuilder(1024); // 初始容量随便设,内部会自动扩容
var dataOffset = fbb.CreateStringFromStream(myStream);
var message = Message.CreateMessage(fbb, dataOffset);
Message.FinishMessageBuffer(fbb, message);

注意事项

  • 该方案全程只占用固定大小的中转缓冲,不管流多大都不会把全量数据加载到内存,也没有多余的内存拷贝
  • 天然支持不可Seek的流,不需要提前知道流的总长度
  • 如果遇到超长字符串超出int范围的特殊场景,只需要修改长度回填逻辑适配即可(不过FlatBuffer官方限制字符串长度为int范围,一般不需要调整)

内容的提问来源于stack exchange,提问作者QuiMax

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 15:27:01