2

我目前正在尝试使用 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)的“块”而不是直接序列化它们,有什么好处吗?

在此先感谢您的帮助

托拜厄斯

4

1 回答 1

3

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

也就是说:您可以使用原始读取器/写入器 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;
                }
            }
        }
    }
}
于 2013-03-12T09:06:18.577 回答