c # сериализованные данные - PullRequest
3 голосов
/ 17 июля 2009

Я использую BinaryFormatter для сериализации данных на диск, но это не кажется очень масштабируемым. Я создал файл данных объемом 200 МБ, но не могу прочитать его обратно (конец потока обнаружен до завершения анализа). Примерно 30 минут он пытается десериализоваться, а затем сдается. Это довольно приличный процессор с четырьмя процессорами и 8 ГБ ОЗУ.

Я сериализую довольно большую сложную структуру.

htCacheItems - это хеш-таблица CacheItems. Каждый CacheItem имеет несколько простых членов (строки + целые и т. Д.), А также содержит Hashtable и пользовательскую реализацию связанного списка. Дополнительная хеш-таблица указывает на структуры CacheItemValue, которая в настоящее время является простым DTO, который содержит ключ и значение. Элементы связанного списка также одинаково просты.

Файл данных, в котором происходит сбой, содержит около 400 000 значений 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;
    }

resolEventHandler - это просто обходной путь, потому что я сериализую данные в одном приложении и загружаю их в другое (http://social.msdn.microsoft.com/Forums/en-US/netfxbcl/thread/e5f0c371-b900-41d8-9a5b-1052739f2521)

Вопрос в том, как я могу улучшить это? Всегда ли сериализация данных будет неэффективной, лучше ли мне писать собственные процедуры?

Ответы [ 3 ]

2 голосов
/ 17 июля 2009

Сериализация сложна, особенно когда вы хотите иметь некоторую степень гибкости, когда дело доходит до управления версиями.

Обычно существует компромисс между переносимостью и гибкостью того, что вы можете сериализовать. Например, вы можете использовать буфер протокола (заявление об отказе: я написал один из портов C # ) как довольно эффективное решение с хорошей переносимостью и управлением версиями - но тогда вам понадобится чтобы перевести любую вашу естественную структуру данных в нечто, поддерживаемое буферами протокола.

Сказав это, я удивлен, что двоичная сериализация здесь терпит неудачу - по крайней мере, именно таким образом. Можете ли вы заставить его терпеть неудачу с большим файлом с очень, очень простым фрагментом кода сериализации? (Нет обработчиков разрешения, нет сжатия и т. Д.)

2 голосов
/ 17 июля 2009

Я бы лично постарался избежать необходимости сборки-разрешения; это имеет определенный запах об этом. Если вы должны использовать BinaryFormatter, то я бы просто поместил DTO в отдельную библиотеку (dll), которую можно использовать в обоих приложениях.

Если вы не хотите делиться dll, то IMO вам не следует использовать BinaryFormatter - вам следует использовать контрактный сериализатор, такой как XmlSerializer или DataContractSerializer, или один из реализации "буферов протокола" (и, повторяю, отказ от ответственности Джона: я написал один из других ).

200 МБ кажется довольно большим, но я бы не ожидал, что он потерпит неудачу. Одной из возможных причин здесь является отслеживание объекта для ссылок; но даже тогда это удивляет меня.

Я бы хотел увидеть упрощенную объектную модель, чтобы увидеть, подходит ли она для чего-либо из вышеперечисленного.


Вот пример, который пытается отразить ваши настройки из описания, используя protobuf-net. Как ни странно, похоже, есть сбой в работе со связанным списком, , который я исследую ; но остальное вроде работает:

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);
    }
}
1 голос
/ 17 июля 2009

Может помочь каскадная сериализация.

Вы вызываете mainHashtable.serialize (), который, например, возвращает строку XML. Этот метод вызывает everyItemInYourHashtable.serialize () и т. Д.

Вы делаете то же самое со статическим методом в каждом классе, который называется unserialize (String xml), который десериализует ваши объекты и возвращает объект или список объектов. Вы получаете точку?

Конечно, вам нужно реализовать этот метод в каждом классе, который вы хотите, чтобы он был сериализуемым.

Взгляните на ISerializable интерфейс , который отражает точность того, что я описываю. IMO, этот интерфейс выглядит слишком «Microsoft» (без использования DOM и т. Д.), Поэтому я создал мой, но принцип тот же: каскад.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...