如何在C#中保存序列化的MessagePack二进制文件?

问题描述 投票:0回答:1

我正在尝试使用MessagePack保存多个结构列表,因为我读到它的性能优于BinaryFormatter序列化。

[我想做的是接收实时时间序列数据,并定期将其定期保存(追加)到磁盘上,例如,如果列表中的元素数为100。我的问题是:

1)在这种情况下,序列化结构列表并将其异步保存到磁盘是否更好?

2)如何使用MessagePack将其简单地保存到磁盘?

public struct struct_realTime
{
    public int indexNum { get; set; }
    public string currentTime { get; set; }
    public string currentType { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        List<struct_realTime> list_temp = new List<struct_realTime>(100000);

        for (int num=0; num < 100000; num++)
        {
            list_temp.Add(new struct_realTime
            {
                indexNum = 1,
                currentTime = "time",
                currentType = "type",
            });
        }

        string filename = "file.bin";

        using (var fileStream = new FileStream(filename, FileMode.Append, FileAccess.Write))
        {
            byte[] bytes = MessagePackSerializer.Serialize(list_temp);
            Console.WriteLine(MessagePackSerializer.ToJson(bytes));
        }
    }
}

当我运行此代码时,它将创建file.bin并打印出100000个结构,但文件为0字节。

[当我使用BinaryFormatter时,我这样做:

using (var fileStream = new FileStream("file.bin", FileMode.Append))
{
    BinaryFormatter formatter = new BinaryFormatter();
    formatter.Serialize(fileStream, list_temp);
}

如何解决该问题?我想得到一些帮助。

c# serialization msgpack
1个回答
0
投票

您要做的是附加使用MessagePackSerializer序列化的对象到包含已经序列化的相似对象序列的文件中,就像使用MessagePackSerializerBinaryFormatter一样。稍后,您大概希望能够将整个序列反序列化为列表或数组。

您的代码有三个问题,两个简单,一个基本。

简单的问题如下:

  • 您实际上没有写BinaryFormatter。可以将其固定如下:

    Json.NET
  • 您尚未用fileStream标记foreach (var item in list_temp) { using (var fileStream = new FileStream(filename, FileMode.Append, FileAccess.Write)) { MessagePackSerializer.Serialize(fileStream, item); } } 。可以将其固定如下:

    struct_realTime

完成此操作后,您现在可以将多个[MessagePackObject] attributes序列化为一个文件...,但是之后将无法读取它们!这是因为[MessagePackObject]将在反序列化根对象类型时读取整个文件,从而跳过附加到该文件的所有其他数据。因此,如下所示的代码将失败,因为仅从文件中读取了一个对象:

[MessagePackObject]
public struct struct_realTime
{
    [Key(0)]
    public int indexNum { get; set; }
    [Key(1)]
    public string currentTime { get; set; }
    [Key(2)]
    public string currentType { get; set; }
}

演示小提琴#1 struct_realTime

并且如下代码将失败,因为流中的根对象是单个对象,而不是对象数组:

MessagePackSerializer

演示小提琴#2 var readList = new List<struct_realTime>(); using (var fileStream = File.OpenRead(filename)) { while (fileStream.Position < fileStream.Length) { var item = MessagePackSerializer.Deserialize<struct_realTime>(fileStream); readList.Append(item); } } Assert.IsTrue(readList.Count == list_temp.Count);

由于here似乎缺乏反序列化流中多个根对象的能力,您有什么选择?首先,您可以反序列化List<struct_realTime> readList; using (var fileStream = File.OpenRead(filename)) { readList = MessagePackSerializer.Deserialize<List<struct_realTime>>(fileStream); } Assert.IsTrue(readList.Count == list_temp.Count); ,将其追加,然后将整个对象反序列化回文件。大概出于性能原因,您不想这样做。

第二,您可以直接使用here手动解析并重写适当的MessagePackSerializer到文件,然后使用List<struct_realTime>本身进行序列化并将对象附加到文件。

以下扩展方法完成了这一工作,方法是在文件的开头查找要写入新数组的数量,然后在末尾查找以追加对象:

MessagePack specification

可以如下使用:

array 32 format header

注意:

  • MessagePack协议具有3种不同的数组格式:

    • [array 32存储一个数组,该数组的长度最多为15个元素。
    • [MessagePackSerializer存储一个数组,该数组的长度最多为(2 ^ 16)-1个元素。
    • [public static class MessagePackExtensions { const byte Array32 = 0xdd; const int Array32HeaderLength = 5; static void FormatArray32Header(byte [] buffer, uint value) { buffer[0] = Array32; buffer[1] = unchecked((byte)(value >> 24)); buffer[2] = unchecked((byte)(value >> 16)); buffer[3] = unchecked((byte)(value >> 8)); buffer[4] = unchecked((byte)value); } static uint ParseArray32Header(byte [] buffer, int readCount) { if (readCount < 5 || buffer[0] != Array32) throw new ArgumentException("Stream was not positioned on an Array32 header."); int i = 1; //https://stackoverflow.com/questions/8241060/how-to-get-little-endian-data-from-big-endian-in-c-sharp-using-bitconverter-toin //https://stackoverflow.com/a/8241127 by https://stackoverflow.com/users/23354/marc-gravell var value = unchecked((uint)((buffer[i++] << 24) | (buffer[i++] << 16) | (buffer[i++] << 8) | buffer[i++])); return value; } public static void AppendToFile<T>(Stream stream, T item) { if (stream == null) throw new ArgumentNullException(nameof(stream)); if (!stream.CanSeek) throw new ArgumentException("!stream.CanSeek"); stream.Position = 0; var buffer = new byte[Array32HeaderLength]; var read = stream.Read(buffer, 0, Array32HeaderLength); stream.Position = 0; if (read == 0) { FormatArray32Header(buffer, 1); stream.Write(buffer, 0, Array32HeaderLength); } else { var count = ParseArray32Header(buffer, read); FormatArray32Header(buffer, count + 1); stream.Write(buffer, 0, Array32HeaderLength); } stream.Position = stream.Length; MessagePackSerializer.Serialize(stream, item); } } 存储一个数组,该数组的长度最多为(2 ^ 32)-1个元素。

    扩展方法要求根数组为// Append each item sequentially foreach (var item in list_temp) { using (var fileStream = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite)) { MessagePackExtensions.AppendToFile(fileStream, item); } } // Then deserialize the entire sequence as a List<T>> using (var fileStream = File.OpenRead(filename)) { var array = MessagePackSerializer.Deserialize<List<struct_realTime>>(fileStream); Console.WriteLine(MessagePackSerializer.ToJson(array)); Assert.IsTrue(array.Count == list_temp.Count); Assert.IsTrue(list_temp.SequenceEqual(array)); } ,以便在新大小变得大于fixarrayarray 16的容量时,无需重新格式化整个数组。但是,array 32将始终以最紧凑的格式写入,因此不能保证将其附加到先前由array 32序列化的集合中。

演示小提琴#3 fixarray

© www.soinside.com 2019 - 2024. All rights reserved.