Как сохранить и добавить к сериализованному двоичному файлу MessagePack на C #?

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

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


person maynull    schedule 09.11.2019    source источник
comment
почему вы хотите добавить двоичный файл? Не имеет смысла.   -  person jdweng    schedule 09.11.2019
comment
@jdweng Я не программист, пожалуйста, поймите меня! Я хочу постоянно получать данные и сохранять их на диск. Раньше я использовал только текстовые файлы, но кто-то здесь сказал мне, что двоичные файлы лучше с точки зрения производительности. Но я не хочу создавать несколько двоичных файлов, и вот как я подошел к этому вопросу. Не могли бы вы порекомендовать мне другие способы сделать это?   -  person maynull    schedule 09.11.2019
comment
Проблема заключается в получении данных. Просто поместив двоичные файловые системы в файл, вы, вероятно, не сможете удалить данные позже. Если каждый файл не имеет размера, вы не сможете получить данные позже.   -  person jdweng    schedule 09.11.2019
comment
По причинам не использовать BinaryFormatter для этой (или любой другой) цели см. Каковы недостатки встроенной сериализации .Net на основе BinaryFormatter?.   -  person dbc    schedule 12.11.2019


Ответы (1)


Вы пытаетесь добавить объект (здесь List<struct_realTime>), сериализованный с помощью MessagePackSerializer в файл, содержащий уже сериализованную последовательность похожих объектов, точно так же, как это возможно с _3 _, 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;
        //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;
    }
}

Его можно использовать для добавления вашего 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 здесь.

person dbc    schedule 11.11.2019
comment
Спасибо за подробное объяснение и код! Я многому у тебя научился! Еще раз спасибо! - person maynull; 12.11.2019