大字节数组 - 在字节数组中存储长度有啥好处?

Posted

技术标签:

【中文标题】大字节数组 - 在字节数组中存储长度有啥好处?【英文标题】:Large byte array - any benefit of storing length within the byte array?大字节数组 - 在字节数组中存储长度有什么好处? 【发布时间】:2020-09-21 22:46:03 【问题描述】:

问:在数组本身中存储大数组的长度有什么好处吗?

说明:

假设我们使用 System.IO.Compression 命名空间的 GZipStream 类来压缩一些大型二进制序列化对象。 输出将是一些压缩字节数组的 Base64 字符串。 在稍后的某个时间点,Base64 字符串被转换回字节数组,并且需要解压缩数据。

在压缩数据时,我们创建了一个新的字节数组,其大小为压缩字节数组的大小 + 4。 在前 4 个字节中,我们存储压缩字节数组的长度/大小,然后将长度和数据块复制到新数组中。这个新数组被转换为 Base64 字符串。

在解压缩时,我们将 Base64 字符串转换为字节数组。 现在我们可以使用 BitConverter 类提取实际压缩数据的长度,该类将从前 4 个字节中提取一个 Int32。 然后我们分配一个字节数组,其长度是我们从前 4 个字节中得到的长度,并让 Stream 将解压缩的字节写入字节数组。

我无法想象这样的事情实际上有什么好处。 它增加了代码的复杂性,需要执行更多的操作。 可读性也降低了。 单独的 BlockCopy 操作应该会消耗太多资源,以至于这根本没有好处,对吧?

压缩示例代码:

byte[] buffer = new byte[0xffff] // Some large binary serialized object
// Compress in-memory.
using (var mem = new MemoryStream())

    // The actual compression takes place here.
    using (var zipStream = new GZipStream(mem, CompressionMode.Compress, true)) 
        zipStream.Write(buffer, 0, buffer.Length);
    

    // Store compressed byte data here.
    var compressedData = new byte[mem.Length];
    mem.Position = 0;                
    mem.Read(compressedData, 0, compressedData.Length);

    /* Increase the size by 4 to accommadate for an Int32 that
    ** will store the total length of the compressed data. */
    var zipBuffer = new byte[compressedData.Length + 4];
    // Store length of compressedData array in the first 4 bytes.
    Buffer.BlockCopy(compressedData, 0, zipBuffer, 4, compressedData.Length);
    // Store the compressedData array after the first 4 bytes which store the length.
    Buffer.BlockCopy(BitConverter.GetBytes(buffer.Length), 0, zipBuffer, 0, 4);
    return Convert.ToBase64String(zipBuffer);
 

解压示例代码:

byte[] zipBuffer = Convert.FromBase64String("some base64 string");
using (var inStream = new MemoryStream())

    // The length of the array that was stored in the first 4 bytes.
    int dataLength = BitConverter.ToInt32(zipBuffer, 0);
    // Allocate array with specific size.
    byte[] buffer = new byte[dataLength];

    // Writer data to buffer array.
    inStream.Write(zipBuffer, 4, zipBuffer.Length - 4);                
    inStream.Position = 0;

    // Decompress data.
    using (var zipStream = new GZipStream(inStream, CompressionMode.Decompress)) 
        zipStream.Read(buffer, 0, buffer.Length);
    

    ... code
    ... code 
    ... code

【问题讨论】:

I can't image that something like this actually has any benefit at all. 当涉及数据流时,它会受益。您读取了流的开头 - 它告诉您在这个数据“块”结束之前要读取多少字节(例如可变长度字符串)。我相信 protobuf 是这样工作的,例如,在处理 Length-delimited 时,你知道这个数据“块”何时结束。 mem.Read(compressedData, 0, compressedData.Length); 这行代码让我很担心。看起来您假设如果您要求它读取一定数量的字节,那么它就会这样做。在处理流时,这是一个危险的(读作:愚蠢的)假设。你真的应该检查那个调用的返回值。 你所做的就像很多 Linux/Unix 操作系统创建结构一样。对于二进制数据,字节数组使用长度属性进行处理,因此当数据从一个应用程序移动/发送到另一个应用程序时,系统的接收端知道数据在哪里结束。流(管道)的接收端没有包含字节数的对象。 数据有长度吗?如果这是一个好主意,您应该询问任何网络协议。对于每个帧、包和数据报格式,答案都是“是”。如果不出意外,只是为了有办法避免对数据进行缓冲区溢出攻击。 【参考方案1】:

您将问题标记为 C#,即 .NET,因此问题无关紧要:

框架已经用数组存储长度。这是如何数组类对索引器进行完整性检查。它如何防止托管代码中的溢出攻击。仅此一项帮助就值得任何轻微的低效率(请注意,JiT 实际上能够修剪大多数检查。例如,对于循环,它只会在每个循环中查看一次运行变量)。

您必须一直深入到非托管代码并处理裸指针才能摆脱它。但你为什么要?差异是如此之小,它属于speed rant。如果它很重要,您可能会得到一个实时编程案例。从 .NET 开始是个坏主意。

【讨论】:

真的不会使问题变得无关紧要吗?您实际上已经回答了这个问题,A:不,没有任何好处。 @GeorgeKerwood 我的回答是“它提供缓冲区溢出保护”和“这值得任何代价”。如果原来的心跳代码使用了它,我们就不会心血来潮了。我不能足够强烈地强调溢出保护的重要性 而且我完全理解为什么有人纵容 .NET 可能无法理解它。 |我还回答“只有在进行实时编程时才重要”和“如果您在进行实时编程,.NET 是错误的起点”。 @Chistopher 我将问题理解为:“手动将长度元数据存储在数组中是否有任何用处?” (使用 .NET 时)。根据您的观点“框架已经将长度与数组一起存储”,答案是“否”。我不相信这个建议是存储长度的框架是多余的/可选的,因为你已经解释了所有的原因。 @GeorgeKerwood 听起来他正在传输、存储或检索阵列。并且不使用处理这些问题的现有方法(如 XML 或 REST)。在这一点上,某种程度的“手动”将大小存储在流中。

以上是关于大字节数组 - 在字节数组中存储长度有啥好处?的主要内容,如果未能解决你的问题,请参考以下文章

在本机内存中存储多个字节数组

c语言中整型的一维数组中每个元素地址的长度为啥会是4个字节??

编辑存储在字节数组中的数据

如何使用 LINQ to Entities 获取字节数组长度?

在 AVR 的程序存储器中构建编译时任意长度数组

快速计算数组中零值字节的数量