Как сохранить и добавить в сериализованный двоичный файл MessagePack в C #? - PullRequest
1 голос
/ 09 ноября 2019

Я пытаюсь использовать 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);
}

Как я могу решить проблему?

1 Ответ

1 голос
/ 12 ноября 2019

То, что вы пытаетесь сделать, это добавить объект (здесь List<struct_realTime>), сериализованный с использованием MessagePackSerializer, в файл, содержащий уже сериализованную последовательность похожих объектов,таким же образом это возможно с BinaryFormatter, protobuf-net или Json.NET . Позже вы, вероятно, захотите иметь возможность десериализовать всю последовательность в список или массив объектов одного типа.

В вашем коде три проблемы, две простые и одна фундаментальная.

Вот простые проблемы:

  • На самом деле вы не пишете в fileStream. Вместо этого выполните следующие действия:

    // Append each list_temp sequentially
    using (var fileStream = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite))
    {
        MessagePackSerializer.Serialize(fileStream, list_temp);
    }
    
  • Вы не пометили struct_realTime с [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; }
    }
    

Сделав это, вы теперь можете многократно сериализовать list_temp в файл ... но вы не сможете их прочитатьпотом! Это потому, что MessagePackSerializer, кажется, читает весь файл при десериализации корневого объекта, пропуская все дополнительные данные, добавленные в файл. Таким образом, код, подобный следующему, потерпит неудачу, поскольку из файла будет прочитан только один объект:

List<List<struct_realTime>> allItemsInFile = new List<List<struct_realTime>>();
using (var fileStream = File.OpenRead(filename))
{
    while (fileStream.Position < fileStream.Length)
    {
        allItemsInFile.Add(MessagePackSerializer.Deserialize<List<struct_realTime>>(fileStream));                   
    }
}
Assert.IsTrue(allItemsInFile.Count == expectedNumberOfRootItemsInFile);

Демо-скрипта # 1 здесь .

И код, подобный следующемупотерпит неудачу, потому что (первый) корневой объект в потоке - это не массив массивов объектов, а всего лишь один массив:

List<List<struct_realTime>> allItemsInFile;
using (var fileStream = File.OpenRead(filename))
{
    allItemsInFile = MessagePackSerializer.Deserialize<List<List<struct_realTime>>>(fileStream);
}
Assert.IsTrue(allItemsInFile.Count == expectedNumberOfRootItemsInFile);

Демо-скрипта # 2 здесь .

Поскольку MessagePackSerializer, кажется, не имеет возможности десериализовать несколько корневых объектов из потока, каковы ваши варианты? Во-первых, вы можете десериализовать List<List<struct_realTime>>, добавить к нему, а затем сериализовать все обратно в файл. Предположительно, вы не хотите делать это из соображений производительности.

Во-вторых, используя спецификацию MessagePack напрямую, вы можете вручную искать начало файла для анализа и перезаписи соответствующего array 32 формат заголовка , затем найдите конец файла и используйте MessagePackSerializer для сериализации и добавления нового элемента. Следующий метод расширения выполняет свою работу:

public static class MessagePackExtensions
{
    const byte Array32 = 0xdd;
    const int Array32HeaderLength = 5;

    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);
    }

    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;
        ///5864278/kak-poluchit-dannye-s-pryamym-poryadkom-baitov-iz-big-endian-v-c-s-pomoschy-metoda-bitconverter-toint32
        //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;
    }
}

. Он может использоваться для добавления вашего list_temp следующим образом:

// Append each entry sequentially
using (var fileStream = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
    MessagePackExtensions.AppendToFile(fileStream, list_temp);
}

А затем, чтобы десериализовать весь файл, выполните:

List<List<struct_realTime>> allItemsInFile;
using (var fileStream = File.OpenRead(filename))
{
    allItemsInFile = MessagePackSerializer.Deserialize<List<List<struct_realTime>>>(fileStream);
}

Примечания:

Демонстрационная скрипка # 3 здесь .

...