【问题标题】:protobuf-net Serialize a nested list of objects using SerializeWithLengthPrefixprotobuf-net 使用 SerializeWithLengthPrefix 序列化嵌套的对象列表
【发布时间】:2013-03-11 16:23:39
【问题描述】:

我目前正在尝试使用 protobuf-net 序列化以下数据结构:

[ProtoContract]
public class Recording
{
    [ProtoMember(1)]
    public string Name;

    [ProtoMember(2)]
    public List<Channel> Channels;
}

[ProtoContract]
public class Channel
{
    [ProtoMember(1)]
    public string ChannelName;

    [ProtoMember(2)]
    public List<float> DataPoints;
}

我有 12 个固定数量的通道,但是每个通道的数据点数量可能会变得非常大(所有通道都达到 Gb 范围)。 因此(并且因为数据是一个连续的流)我不想一次读取和保存一个记录的结构,而是利用 SerializeWithLengthPrefix (和 DeserializeItems )来连续保存它。 我的问题是,甚至可以用这样的嵌套结构来做到这一点,还是我必须把它弄平? 我已经看到了第一个层次结构级别中的列表示例,但没有针对我的具体情况。 另外,如果我将数据点写为 10、100、... 的“块”(例如使用 List 而不是 List)而不是直接序列化它们有什么好处吗?

提前感谢您的帮助

托比亚斯

【问题讨论】:

  • 我可以确认一下:您有单独的Recording 实例列表吗?或者这是一个单一的Recording,而我们正在谈论的“列表”是Channels?两者都可以,但我需要知道确切的场景才能给你最好的建议。
  • 每个录音都应该有自己的文件,所以只有一个录音,我要添加数据的列表是数据点列表,通道列表是固定的,通道是在录制开始之前创建的.
  • 所以这里的大名单是Channels 一个?
  • 不,最大的是数据点列表(对于 12 个通道,我每通道每秒获得 1000 个)
  • 那么您每次添加的项目是什么?子对象(例如单个通道)通常是完整写入的。是否打算用一些数据点、B 等写入通道 A 的一部分,然后用一些数据点等写入更多通道 A?

标签: c# protobuf-net


【解决方案1】:

您正在尝试做的主要挑战是它对每个对象在内部 是高度基于流的。 protobuf-net 可以以这种方式工作,但它并非微不足道。还有一个问题是您希望将来自单个通道的数据交错到多个片段上,这不是惯用的 protobuf 布局。因此,核心 object materializer 代码可能无法完全满足您的要求 - 即将其视为开放流,而不是全部加载到内存中,用于读取和写入。

也就是说:您可以使用原始读取器/写入器 API 来实现流式传输。您可能应该使用 BinaryWriter / BinaryReader 与类似的代码进行比较和对比,但基本上可以使用以下代码:

using ProtoBuf;
using System;
using System.Collections.Generic;
using System.IO;
static class Program
{
    static void Main()
    {
        var path = "big.blob";
        WriteFile(path);

        int channelTotal = 0, pointTotal = 0;
        foreach(var channel in ReadChannels(path))
        {
            channelTotal++;
            pointTotal += channel.Points.Count;
        }
        Console.WriteLine("Read: {0} points in {1} channels", pointTotal, channelTotal);
    }
    private static void WriteFile(string path)
    {
        string[] channels = {"up", "down", "top", "bottom", "charm", "strange"};
        var rand = new Random(123456);

        int totalPoints = 0, totalChannels = 0;
        using (var encoder = new DataEncoder(path, "My file"))
        {
            for (int i = 0; i < 100; i++)
            {
                var channel = new Channel {
                    Name = channels[rand.Next(channels.Length)]
                };
                int count = rand.Next(1, 50);
                var data = new List<float>(count);
                for (int j = 0; j < count; j++)
                    data.Add((float)rand.NextDouble());
                channel.Points = data;
                encoder.AddChannel(channel);
                totalPoints += count;
                totalChannels++;
            }
        }

        Console.WriteLine("Wrote: {0} points in {1} channels; {2} bytes", totalPoints, totalChannels, new FileInfo(path).Length);
    }
    public class Channel
    {
        public string Name { get; set; }
        public List<float> Points { get; set; }
    }
    public class DataEncoder : IDisposable
    {
        private Stream stream;
        private ProtoWriter writer;
        public DataEncoder(string path, string recordingName)
        {
            stream = File.Create(path);
            writer = new ProtoWriter(stream, null, null);

            if (recordingName != null)
            {
                ProtoWriter.WriteFieldHeader(1, WireType.String, writer);
                ProtoWriter.WriteString(recordingName, writer);
            }
        }
        public void AddChannel(Channel channel)
        {
            ProtoWriter.WriteFieldHeader(2, WireType.StartGroup, writer);
            var channelTok = ProtoWriter.StartSubItem(null, writer);

            if (channel.Name != null)
            {
                ProtoWriter.WriteFieldHeader(1, WireType.String, writer);
                ProtoWriter.WriteString(channel.Name, writer);
            }
            var list = channel.Points;
            if (list != null)
            {

                switch(list.Count)
                {
                    case 0:
                        // nothing to write
                        break;
                    case 1:
                        ProtoWriter.WriteFieldHeader(2, WireType.Fixed32, writer);
                        ProtoWriter.WriteSingle(list[0], writer);
                        break;
                    default:
                        ProtoWriter.WriteFieldHeader(2, WireType.String, writer);
                        var dataToken = ProtoWriter.StartSubItem(null, writer);
                        ProtoWriter.SetPackedField(2, writer);
                        foreach (var val in list)
                        {
                            ProtoWriter.WriteFieldHeader(2, WireType.Fixed32, writer);
                            ProtoWriter.WriteSingle(val, writer);
                        }
                        ProtoWriter.EndSubItem(dataToken, writer);
                        break;
                }
            }
            ProtoWriter.EndSubItem(channelTok, writer);
        }
        public void Dispose()
        {
            using (writer) { if (writer != null) writer.Close(); }
            writer = null;
            using (stream) { if (stream != null) stream.Close(); }
            stream = null;
        }
    }

    private static IEnumerable<Channel> ReadChannels(string path)
    {
        using (var file = File.OpenRead(path))
        using (var reader = new ProtoReader(file, null, null))
        {
            while (reader.ReadFieldHeader() > 0)
            {
                switch (reader.FieldNumber)
                {
                    case 1:
                        Console.WriteLine("Recording name: {0}", reader.ReadString());
                        break;
                    case 2: // each "2" instance represents a different "Channel" or a channel switch
                        var channelToken = ProtoReader.StartSubItem(reader);
                        int floatCount = 0;
                        List<float> list = new List<float>();
                        Channel channel = new Channel { Points = list };
                        while (reader.ReadFieldHeader() > 0)
                        {

                            switch (reader.FieldNumber)
                            {
                                case 1:
                                    channel.Name = reader.ReadString();
                                    break;
                                case 2:
                                    switch (reader.WireType)
                                    {
                                        case WireType.String: // packed array - multiple floats
                                            var dataToken = ProtoReader.StartSubItem(reader);
                                            while (ProtoReader.HasSubValue(WireType.Fixed32, reader))
                                            {
                                                list.Add(reader.ReadSingle());
                                                floatCount++;
                                            }
                                            ProtoReader.EndSubItem(dataToken, reader);
                                            break;
                                        case WireType.Fixed32: // simple float
                                            list.Add(reader.ReadSingle());
                                            floatCount++; // got 1
                                            break;
                                        default:
                                            Console.WriteLine("Unexpected data wire-type: {0}", reader.WireType);
                                            break;
                                    }
                                    break;
                                default:
                                    Console.WriteLine("Unexpected field in channel: {0}/{1}", reader.FieldNumber, reader.WireType);
                                    reader.SkipField();
                                    break;
                            }
                        }
                        ProtoReader.EndSubItem(channelToken, reader);
                        yield return channel;
                        break;
                    default:
                        Console.WriteLine("Unexpected field in recording: {0}/{1}", reader.FieldNumber, reader.WireType);
                        reader.SkipField();
                        break;
                }
            }
        }
    }
}

【讨论】:

  • 感谢您的帮助 :) 我首先提出了一个类似的解决方案,但后来我决定将数据放入块中,这样我就可以在记录类中制作一个数据块列表,每个块都包含下一个每个通道的 X 个数据点。然后我意识到,对于那些数据的可视化,仅仅能够从一开始就可以很好地工作(并且 protobuf 不支持像从位置 300 开始获取接下来的 10 个块这样的请求,对吧?)所以我很可能需要另一种方法。但是,再次感谢您的帮助:)
  • @Tobias 如果你能保证偏移 300 是一个合理的位置,你可以做一些事情——虽然这不是正常用法
  • 对不起,我表达得不够清楚:“从位置 300 开始”我的意思是跳过前 299 个块,然后读取接下来的 10 个块,而不是从第 300 个字节开始。为此,我可以再次使用 ProtoReader,对吧?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多