代码之家  ›  专栏  ›  技术社区  ›  Gordon Thompson

C序列化数据

  •  3
  • Gordon Thompson  · 技术社区  · 16 年前

    我一直在使用BinaryFormatter将数据序列化到磁盘,但它似乎不具有很强的可伸缩性。我已经创建了一个200MB的数据文件,但无法将其重新读取(在解析完成之前遇到流的末尾)。它尝试反序列化大约30分钟,然后放弃。这是在一个相当不错的8GB内存的四CPU机箱上。

    我正在对一个相当大的复杂结构进行序列化。

    htcacheitems是cacheitems的哈希表。每个cacheitem都有几个简单的成员(字符串+int等),还包含一个哈希表和一个链表的自定义实现。子哈希表指向cacheitemValue结构,该结构当前是一个包含键和值的简单DTO。链接列表项也同样简单。

    失败的数据文件包含大约400000个cacheitemvalues。

    较小的数据集可以很好地工作(尽管比我预期的反序列化和使用大量内存的时间要长)。

        public virtual bool Save(String sBinaryFile)
        {
            bool bSuccess = false;
            FileStream fs = new FileStream(sBinaryFile, FileMode.Create);
    
            try
            {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(fs, htCacheItems);
                bSuccess = true;
            }
            catch (Exception e)
            {
                bSuccess = false;
            }
            finally
            {
                fs.Close();
            }
            return bSuccess;
        }
    
        public virtual bool Load(String sBinaryFile)
        {
            bool bSuccess = false;
    
            FileStream fs = null;
            GZipStream gzfs = null;
    
            try
            {
                fs = new FileStream(sBinaryFile, FileMode.OpenOrCreate);
    
                if (sBinaryFile.EndsWith("gz"))
                {
                    gzfs = new GZipStream(fs, CompressionMode.Decompress);
                }
    
                //add the event handler
                ResolveEventHandler resolveEventHandler = new ResolveEventHandler(AssemblyResolveEventHandler);
                AppDomain.CurrentDomain.AssemblyResolve += resolveEventHandler;
    
                BinaryFormatter formatter = new BinaryFormatter();
                htCacheItems = (Hashtable)formatter.Deserialize(gzfs != null ? (Stream)gzfs : (Stream)fs);
    
                //remove the event handler
                AppDomain.CurrentDomain.AssemblyResolve -= resolveEventHandler;
    
                bSuccess = true;
            }
            catch (Exception e)
            {
                Logger.Write(new ExceptionLogEntry("Failed to populate cache from file " + sBinaryFile + ". Message is " + e.Message));
                bSuccess = false;
            }
            finally
            {
                if (fs != null)
                {
                    fs.Close();
                }
                if (gzfs != null)
                {
                    gzfs.Close();
                }
            }
            return bSuccess;
        }
    

    ResolveEventHandler只是一个解决方案,因为我正在将一个应用程序中的数据序列化,并将其加载到另一个应用程序中。( http://social.msdn.microsoft.com/Forums/en-US/netfxbcl/thread/e5f0c371-b900-41d8-9a5b-1052739f2521 )

    问题是,我该如何改进这一点?数据序列化是否总是效率低下,我最好还是写自己的例程?

    3 回复  |  直到 16 年前
        1
  •  2
  •   Marc Gravell    16 年前

    我个人会尽量避免需要大会的决心,这有一定的味道。如果你 必须 使用 BinaryFormatter 然后,我只需将DTO放入一个单独的库(DLL),可以在两个应用程序中使用。

    如果你不想共享DLL,那么我认为你不应该使用 二进制格式 -您应该使用基于契约的序列化程序,例如 XmlSerializer DataContractSerializer 或者“协议缓冲区”实现之一(重复jon的免责声明:我写了 one of the others )

    200兆看起来确实相当大,但我没想到会失败。这里一个可能的原因是对象跟踪,它是为引用而做的;但即使这样,这也让我吃惊。

    我希望看到一个简化的对象模型,看看它是否适合上述任何一种情况。


    下面是一个使用protobuf-net从描述中镜像设置的示例。奇怪的是,在处理链接列表时似乎出现了一个问题, which I'll investigate ;但其他的似乎有效:

    using System;
    using System.Collections.Generic;
    using System.IO;
    using ProtoBuf;
    [ProtoContract]
    class CacheItem
    {
        [ProtoMember(1)]
        public int Id { get; set; }
        [ProtoMember(2)]
        public int AnotherNumber { get; set; }
        private readonly Dictionary<string, CacheItemValue> data
            = new Dictionary<string,CacheItemValue>();
        [ProtoMember(3)]
        public Dictionary<string, CacheItemValue> Data { get { return data; } }
    
        //[ProtoMember(4)] // commented out while I investigate...
        public ListNode Nodes { get; set; }
    }
    [ProtoContract]
    class ListNode // I'd probably expose this as a simple list, though
    {
        [ProtoMember(1)]
        public double Head { get; set; }
        [ProtoMember(2)]
        public ListNode Tail { get; set; }
    }
    [ProtoContract]
    class CacheItemValue
    {
        [ProtoMember(1)]
        public string Key { get; set; }
        [ProtoMember(2)]
        public float Value { get; set; }
    }
    static class Program
    {
        static void Main()
        {
            // invent 400k CacheItemValue records
            Dictionary<string, CacheItem> htCacheItems = new Dictionary<string, CacheItem>();
            Random rand = new Random(123456);
            for (int i = 0; i < 400; i++)
            {
                string key;
                CacheItem ci = new CacheItem {
                    Id = rand.Next(10000),
                    AnotherNumber = rand.Next(10000)
                };
                while (htCacheItems.ContainsKey(key = rand.NextString())) {}
                htCacheItems.Add(key, ci);
                for (int j = 0; j < 1000; j++)
                {
                    while (ci.Data.ContainsKey(key = rand.NextString())) { }
                    ci.Data.Add(key,
                        new CacheItemValue {
                            Key = key,
                            Value = (float)rand.NextDouble()
                        });
                    int tail = rand.Next(1, 50);
                    ListNode node = null;
                    while (tail-- > 0)
                    {
                        node = new ListNode
                        {
                            Tail = node,
                            Head = rand.NextDouble()
                        };
                    }
                    ci.Nodes = node;
                }
            }
            Console.WriteLine(GetChecksum(htCacheItems));
            using (Stream outfile = File.Create("raw.bin"))
            {
                Serializer.Serialize(outfile, htCacheItems);
            }
            htCacheItems = null;
            using (Stream inFile = File.OpenRead("raw.bin"))
            {
                htCacheItems = Serializer.Deserialize<Dictionary<string, CacheItem>>(inFile);
            }
            Console.WriteLine(GetChecksum(htCacheItems));
        }
        static int GetChecksum(Dictionary<string, CacheItem> data)
        {
            int chk = data.Count;
            foreach (var item in data)
            {
                chk += item.Key.GetHashCode()
                    + item.Value.AnotherNumber + item.Value.Id;
                foreach (var subItem in item.Value.Data.Values)
                {
                    chk += subItem.Key.GetHashCode()
                        + subItem.Value.GetHashCode();
                }
            }
            return chk;
        }
        static string NextString(this Random random)
        {
            const string alphabet = "abcdefghijklmnopqrstuvwxyz0123456789 ";
            int len = random.Next(4, 10);
            char[] buffer = new char[len];
            for (int i = 0; i < len; i++)
            {
                buffer[i] = alphabet[random.Next(0, alphabet.Length)];
            }
            return new string(buffer);
        }
    }
    
        2
  •  2
  •   Jon Skeet    16 年前

    序列化是很棘手的,特别是当您希望在版本控制方面具有一定的灵活性时。

    通常在可移植性和可序列化的灵活性之间有一个权衡。例如,您可能希望使用 Protocol Buffers (免责声明:我写了 one of the C# ports )作为一个非常有效的解决方案,它具有良好的可移植性和版本控制能力,但是您需要将您的自然数据结构转换为协议缓冲区支持的内容。

    说了这句话,我很惊讶二进制序列化在这里失败了——至少在那个特定的方式上失败了。你能用一个大文件和一段非常简单的序列化代码让它失败吗?(无分辨率处理程序、无压缩等)

        3
  •  1
  •   Clement Herreman    16 年前

    可以帮助的是级联序列化。

    您调用mainHashtable.serialize(),例如,它返回一个XML字符串。此方法调用everyiteminyourhashtable.serialize()等。

    对每个类中的静态方法(称为“unserialize(string xml)”)也可以这样做,它取消对象的序列化并返回对象或对象列表。 你明白了吗?

    当然,您需要在希望序列化的每个类中实现此方法。

    看一看 ISerializable interface 这正是我所描述的。在我看来,这个界面太“微软”(不使用dom等),所以我创建了我的,但原理是一样的:层叠。