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
相关产品推荐
相关产品推荐

