序列化一个包含XAML的ConcurrentBag

12

我在代码中使用了一个ConcurrentBag<Point3DCollection>

我正在尝试弄清楚如何序列化它们。当然,我可以通过迭代或使用提供程序模型类打包它们,但我想知道是否已经有人这样做过。

Point3DCollection本身可能非常大,并且可以进行压缩以加快从磁盘读写的速度,但是我需要的响应时间主要在用户界面方面。换句话说,出于性能原因,我更喜欢二进制格式而不是XAML文本格式。(Helix 3D CodeProject中有一个不错的XAML文本序列化器,但速度比我想要的慢。)

这是一种情况,我需要自己编写序列化程序,还是已经有针对这种数据的现成解决方案了?


我认为这是非常有争议的。Point3D是由3个双精度值组成的。一个双精度值占用8个字节,因此二进制序列化的Point3D占用24个字节。在XAML中使用的许多由人类(甚至是Blend)创建的双精度值可以比字符串短(我假设您将它们写为ANSI而不是UNICODE)。如果不是这种情况,那么压缩将是无用的,因为在这种情况下,一系列二进制序列化的双精度值的熵将很高。我建议您简单地循环遍历该集合,并使用现成的Point3DCollectionConverter类(ConvertTo方法)。 - Simon Mourier
@SimonMourier,这些值通常是由CAD系统生成的,因此双精度ANSI字符串会比8个字节长得多。但是您对Point3DCollectionConverter的推荐是一个可能的答案。您是否愿意将其编写为答案,以便我可以授予点赞等奖励? - Rob Perkins
对于大量数据,为什么不考虑使用Sqlite等可以将结构化数据存储在文件中的数据库呢?我见过许多3D程序使用数据库来存储结构和关系,这使得它们能够部分地插入/更新/删除数据。 Sqlite的好处是,您可以使用多线程序列化来提高速度,但是您需要在Sqlite上做一些工作以启用多线程Sqlite连接,否则您可以使用SQL Express的LocalDB甚至Sql Compact。 - Akash Kava
@AkashKava,请写成一个答案!:-)我想更深入地研究一下。 - Rob Perkins
4个回答

6
这里有一些处理Point3DCollection集合的字符串和二进制序列化的扩展方法。正如我在评论中所说,我认为没有一种最好的方法适用于所有情况,因此您可能需要尝试两种方法。还请注意,它们使用Stream参数作为输入,因此您可以将其与调用GZipStreamDeflateStream的方法链接起来。
public static class Point3DExtensions
{
    public static void StringSerialize(this ConcurrentBag<Point3DCollection> bag, Stream stream)
    {
        if (bag == null)
            throw new ArgumentNullException("bag");

        if (stream == null)
            throw new ArgumentNullException("stream");

        StreamWriter writer = new StreamWriter(stream);
        Point3DCollectionConverter converter = new Point3DCollectionConverter();
        foreach (Point3DCollection coll in bag)
        {
            // we need to use the english locale as the converter needs that for parsing...
            string line = (string)converter.ConvertTo(null, CultureInfo.GetCultureInfo("en-US"), coll, typeof(string));
            writer.WriteLine(line);
        }
        writer.Flush();
    }

    public static void StringDeserialize(this ConcurrentBag<Point3DCollection> bag, Stream stream)
    {
        if (bag == null)
            throw new ArgumentNullException("bag");

        if (stream == null)
            throw new ArgumentNullException("stream");

        StreamReader reader = new StreamReader(stream);
        Point3DCollectionConverter converter = new Point3DCollectionConverter();
        do
        {
            string line = reader.ReadLine();
            if (line == null)
                break;

            bag.Add((Point3DCollection)converter.ConvertFrom(line));

            // NOTE: could also use this:
            //bag.Add(Point3DCollection.Parse(line));
        }
        while (true);
    }

    public static void BinarySerialize(this ConcurrentBag<Point3DCollection> bag, Stream stream)
    {
        if (bag == null)
            throw new ArgumentNullException("bag");

        if (stream == null)
            throw new ArgumentNullException("stream");

        BinaryWriter writer = new BinaryWriter(stream);
        writer.Write(bag.Count);
        foreach (Point3DCollection coll in bag)
        {
            writer.Write(coll.Count);
            foreach (Point3D point in coll)
            {
                writer.Write(point.X);
                writer.Write(point.Y);
                writer.Write(point.Z);
            }
        }
        writer.Flush();
    }

    public static void BinaryDeserialize(this ConcurrentBag<Point3DCollection> bag, Stream stream)
    {
        if (bag == null)
            throw new ArgumentNullException("bag");

        if (stream == null)
            throw new ArgumentNullException("stream");

        BinaryReader reader = new BinaryReader(stream);
        int count = reader.ReadInt32();
        for (int i = 0; i < count; i++)
        {
            int pointCount = reader.ReadInt32();
            Point3DCollection coll = new Point3DCollection(pointCount);
            for (int j = 0; j < pointCount; j++)
            {
                coll.Add(new Point3D(reader.ReadDouble(), reader.ReadDouble(), reader.ReadDouble()));
            }
            bag.Add(coll);
        }
    }
}

这是一个小型控制台应用程序测试程序,可供试玩:

    static void Main(string[] args)
    {
        Random rand = new Random(Environment.TickCount);
        ConcurrentBag<Point3DCollection> bag = new ConcurrentBag<Point3DCollection>();
        for (int i = 0; i < 100; i++)
        {
            Point3DCollection coll = new Point3DCollection();
            bag.Add(coll);

            for (int j = rand.Next(10); j < rand.Next(100); j++)
            {
                Point3D point = new Point3D(rand.NextDouble(), rand.NextDouble(), rand.NextDouble());
                coll.Add(point);
            }
        }

        using (FileStream stream = new FileStream("test.bin", FileMode.Create))
        {
            bag.StringSerialize(stream); // or Binary
        }

        ConcurrentBag<Point3DCollection> newbag = new ConcurrentBag<Point3DCollection>();
        using (FileStream stream = new FileStream("test.bin", FileMode.Open))
        {
            newbag.StringDeserialize(stream); // or Binary
            foreach (Point3DCollection coll in newbag)
            {
                foreach (Point3D point in coll)
                {
                    Console.WriteLine(point);
                }
                Console.WriteLine();
            }
        }
    }
}

仅作为信息点,对于我的用例,我在二进制gzipped流上获得了3:1的压缩比。我还没有测试过测试gzipped方法。 - Rob Perkins

2

压缩可能会利用重复的坐标。序列化程序通常也会使用引用来重复对象,尽管我不确定有多少序列化程序可以处理结构体(如Point3D)。无论如何,下面是一些序列化示例。要使用标准格式化程序,您需要将数据类型转换为大多数格式化程序支持的东西:列表/数组。以下代码使用Nuget包NUnit和Json.NET。

using Newtonsoft.Json;
using Newtonsoft.Json.Bson;
using NUnit.Framework;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Windows.Media.Media3D;

namespace DemoPoint3DSerialize
{
    [TestFixture]
    class Tests
    {
        [Test]
        public void DemoBinary()
        {
            // this shows how to convert them all to strings
            var collection = CreateCollection();
            var data = collection.Select(c => c.ToArray()).ToList(); // switch to serializable types
            var formatter = new BinaryFormatter();

            using (var ms = new MemoryStream())
            {
                formatter.Serialize(ms, data);
                Trace.WriteLine("Binary of Array Size: " + ms.Position);
                ms.Position = 0;
                var dupe = (List<Point3D[]>)formatter.Deserialize(ms);
                var result = new ConcurrentBag<Point3DCollection>(dupe.Select(r => new Point3DCollection(r)));
                VerifyEquality(collection, result);
            }
        }

        [Test]
        public void DemoString()
        {
            // this shows how to convert them all to strings
            var collection = CreateCollection();
            IEnumerable<IList<Point3D>> tmp = collection;
            var strings = collection.Select(c => c.ToString()).ToList();

            Trace.WriteLine("String Size: " + strings.Sum(s => s.Length)); // eh, 2x for Unicode
            var result = new ConcurrentBag<Point3DCollection>(strings.Select(r => Point3DCollection.Parse(r)));

            VerifyEquality(collection, result);
        }

        [Test]
        public void DemoDeflateString()
        {
            // this shows how to convert them all to strings
            var collection = CreateCollection();
            var formatter = new BinaryFormatter(); // not really helping much: could 
            var strings = collection.Select(c => c.ToString()).ToList();

            using (var ms = new MemoryStream())
            {
                using (var def = new DeflateStream(ms, CompressionLevel.Optimal, true))
                {
                    formatter.Serialize(def, strings);
                }
                Trace.WriteLine("Deflate Size: " + ms.Position);
                ms.Position = 0;
                using (var def = new DeflateStream(ms, CompressionMode.Decompress))
                {
                    var stringsDupe = (IList<string>)formatter.Deserialize(def);
                    var result = new ConcurrentBag<Point3DCollection>(stringsDupe.Select(r => Point3DCollection.Parse(r)));

                    VerifyEquality(collection, result);
                }
            }
        }

        [Test]
        public void DemoStraightJson()
        {
            // this uses Json.NET
            var collection = CreateCollection();
            var formatter = new JsonSerializer();

            using (var ms = new MemoryStream())
            {
                using (var stream = new StreamWriter(ms, new UTF8Encoding(true), 2048, true))
                using (var writer = new JsonTextWriter(stream))
                {
                    formatter.Serialize(writer, collection);
                }
                Trace.WriteLine("JSON Size: " + ms.Position);
                ms.Position = 0;
                using (var stream = new StreamReader(ms))
                using (var reader = new JsonTextReader(stream))
                {
                    var result = formatter.Deserialize<List<Point3DCollection>>(reader);
                    VerifyEquality(collection, new ConcurrentBag<Point3DCollection>(result));
                }
            }
        }

        [Test]
        public void DemoBsonOfArray()
        {
            // this uses Json.NET
            var collection = CreateCollection();
            var formatter = new JsonSerializer();

            using (var ms = new MemoryStream())
            {
                using (var stream = new BinaryWriter(ms, new UTF8Encoding(true), true))
                using (var writer = new BsonWriter(stream))
                {
                    formatter.Serialize(writer, collection);
                }
                Trace.WriteLine("BSON Size: " + ms.Position);
                ms.Position = 0;
                using (var stream = new BinaryReader(ms))
                using (var reader = new BsonReader(stream, true, DateTimeKind.Unspecified))
                {
                    var result = formatter.Deserialize<List<Point3DCollection>>(reader); // doesn't seem to read out that concurrentBag
                    VerifyEquality(collection, new ConcurrentBag<Point3DCollection>(result));
                }
            }
        }

        private ConcurrentBag<Point3DCollection> CreateCollection()
        {
            var rand = new Random(42);
            var bag = new ConcurrentBag<Point3DCollection>();

            for (int i = 0; i < 10; i++)
            {
                var collection = new Point3DCollection();
                for (int j = 0; j < i + 10; j++)
                {
                    var point = new Point3D(rand.NextDouble(), rand.NextDouble(), rand.NextDouble());
                    collection.Add(point);
                }
                bag.Add(collection);
            }
            return bag;
        }

        private class CollectionComparer : IEqualityComparer<Point3DCollection>
        {
            public bool Equals(Point3DCollection x, Point3DCollection y)
            {
                return x.SequenceEqual(y);
            }

            public int GetHashCode(Point3DCollection obj)
            {
                return obj.GetHashCode();
            }
        }

        private void VerifyEquality(ConcurrentBag<Point3DCollection> collection, ConcurrentBag<Point3DCollection> result)
        {
            var first = collection.OrderBy(c => c.Count);
            var second = collection.OrderBy(c => c.Count);
            first.SequenceEqual(second, new CollectionComparer());
        }


    }
}

2
使用Google的protobuf-net。protobuf-net是Google协议缓冲二进制序列化格式的开源.net实现,可用作BinaryFormatter序列化程序的替代品。它可能是最快速和最容易实现的解决方案。
这里是protobuf-net的主要谷歌Wiki链接。在左侧,您将找到所有最新二进制文件的下载链接。

https://code.google.com/p/protobuf-net/

这里有一篇很棒的文章,你可能想先看一下,以了解它是如何工作的。

http://wallaceturner.com/serialization-with-protobuf-net

这是一个关于你特定问题的Google维基讨论的链接。答案在页面底部。我从那里获取了以下代码,并根据你的帖子进行了替换。

https://code.google.com/p/protobuf-net/issues/detail?id=354

我自己没有用过它,但看起来它是解决你所述需求的一个非常好的方案。据我了解,你的代码最终可能会变成这样的某个变体。
[ProtoContract]
public class MyClass {
    public ConcurrentQueue<Point3DCollection> Points {get;set;}

    [ProtoMember(1)]
    private Point3DCollection[] Items
    {
        get { return Points.ToArray(); }
        set { Items = new ConcurrentBag<Point3DCollection>(value); }
    }
}

祝你好运,保重。

0
针对大量数据,为什么不考虑使用Sqlite或其他小型数据库系统等,可以将结构化数据存储在文件中。
我看到许多3D程序使用数据库来存储结构以及关系,这使得它们能够部分地插入/更新/删除数据。
Sqlite/数据库的好处是多线程串行化以提高速度,但是您需要在Sqlite上做一些工作以启用多线程Sqlite连接,否则您可以使用SQL Express的LocalDB甚至Sql Compact。
此外,一些加载数据的工作负载可以通过查询完成,这些查询将被数据库很好地索引。并且大多数事情都可以在后台工作人员上完成,而不会干扰用户界面。
Sqlite具有有限的多线程支持,可以在此处进行探索http://www.sqlite.org/threadsafe.html Sql Compact是线程安全的,并且需要安装,可以在没有管理员权限的情况下安装。您也可以使用Entity Framework。

你能添加一个链接来描述通过sqlite进行多线程处理所需的工作吗? - Rob Perkins

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接