如何访问序列化数组中的单个项目?

How to access individual items in serialized array?

我想在二进制平面文件中存储一组时间戳。 我的一个要求是我以后可以访问单个时间戳以进行高效查询,而不必先读取和反序列化整个数组(我使用二进制搜索算法找到文件位置一个开始时间戳和结束时间戳,这反过来决定了在这两个时间戳之间读取和反序列化哪些字节,因为整个二进制文件的大小可能有数 GB。

显然,简单但缓慢的方法是使用BitConverter.GetBytes(timestamp)将每个时间戳转换为字节,然后将它们存储在文件中。然后,我可以单独访问文件中的每个项目,并使用我的自定义二进制搜索算法来查找与所需时间戳匹配的时间戳。

但是,我发现 BinaryFormatter 对于 serialization/deserialization 值类型数组非常高效(比 protobuf-net 和我尝试过的任何其他序列化程序快数倍)。因此,我试图尝试将一组时间戳序列化为二进制形式。但是,显然这将阻止我访问文件中的各个时间戳,而不必先反序列化整个数组。

在通过 BinaryFormatter 序列化整个项目数组后,是否仍然可以访问二进制形式的单个项目?

这里有一些代码片段可以说明我的意思:

var sampleArray = new int[5] { 1,2,3,4,5};

        var serializedSingleValueArray = sampleArray.SelectMany(x => BitConverter.GetBytes(x)).ToArray();
        var serializedArrayofSingleValues = Serializers.BinarySerializeToArray(sampleArray);

        var deserializesToCorrectValue = BitConverter.ToInt32(serializedSingleValueArray, 0); //value = 1 (ok)
        var wrongDeserialization = BitConverter.ToInt32(serializedArrayofSingleValues, 0); //value = 256 (???)

这里是序列化函数:

public static byte[]BinarySerializeToArray(object toSerialize)
    {
        using (var stream = new MemoryStream())
        {
            Formatter.Serialize(stream, toSerialize);
            return stream.ToArray();
        }
    }

编辑:我不需要担心有效的内存消耗或文件大小,因为这些目前还不是瓶颈。序列化和反序列化的速度是我处理数 GB 大型二进制文件和非常大的基元数组的瓶颈。

Bitconverter 不是 "slow" 版本,它只是一种将所有内容转换为 byte[] 序列的方法。这实际上并不昂贵,它只是对内存的不同解释。

计算文件中的位置,加载8个字节,将其转换为DateTime,完成。

您应该只对简单的结构化文件执行此操作,并且对于简单的结构化文件,您不需要二进制格式化程序。只是 load/save 你的一个数组到一个文件。这样你就可以确定你的文件位置可以被计算出来。

换句话说。自己保存数组,日期字节日期,然后你也可以按日期加载它。

用一种处理风格写作,用另一种处理风格阅读,总是一个坏主意。

如果您的问题只是 "how to convert an array of struct,to byte[]",除了 BitConverter,您还有其他选择。 BitConverter 用于单个值,Buffer class 用于数组。

        double[] d = new double[100];
        d[4] = 1235;
        d[8] = 5678;
        byte[] b = new byte[800];
        Buffer.BlockCopy(d, 0, b, 0, d.Length*sizeof(double));

        // just to test it works
        double[] d1 = new double[100];
        Buffer.BlockCopy(b, 0, d1, 0, d.Length * sizeof(double));

这会进行字节级复制,不会转换任何内容,也不会遍历项目。

您可以将此字节数组直接放入流中(不是 StreamWriter,也不是 Formatter)

        stream.Write(b, 0, 800);

这绝对是写入文件的最快方法,但它涉及完整副本,但可能还有任何其他可想到的方法,将读取项目,出于某种原因首先存储它,然后再进入文件。

如果这是您写入文件的唯一内容 - 您不需要在文件中写入数组长度,您可以为此使用文件长度。

读取文件中的第 100 个双精度值:

    file.Seek(100*sizeof(double), SeekOrigin.Begin);
    byte[] tmp = new byte[8];
    f.Read(tmp, 0, 8);
    double value = BitConverter.ToDouble(tmp, 0);

这里,对于单值,可以使用BitConverter.

这是 .NET Framework 的解决方案,C# <= 7.0

对于 .NET Standard/.NET Core、C# 8.0,您有更多选项 Span<T>,这使您无需复制数据即可访问内部存储器。