Как сериализовать / десериализовать большой список элементов с помощью protobuf-net - PullRequest
8 голосов
/ 25 ноября 2011

У меня есть список около 500 миллионов предметов. Я могу сериализовать это в файл с файлом protobuf-net, если я сериализую отдельные элементы, а не список - я не могу собрать элементы в Список цен, а затем сериализовать, потому что у меня заканчивается память. Итак, я должен сериализовать одну запись за раз:

using (var input = File.OpenText("..."))
using (var output = new FileStream("...", FileMode.Create, FileAccess.Write))
{
    string line = "";
    while ((line = input.ReadLine()) != null)
    {
        Price price = new Price();
        (code that parses input into a Price record)

        Serializer.Serialize(output, price);
    }
}

Мой вопрос касается части десериализации. Похоже, что метод десериализации не перемещает позицию потока к следующей записи. Я попробовал:

using (var input = new FileStream("...", FileMode.Open, FileAccess.Read))
{
    Price price = null;
    while ((price = Serializer.Deserialize<Price>(input)) != null)
    {
    }
}

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

Как правильно десериализовать поток, содержащий список объектов, которые не сериализованы в виде списка?

Ответы [ 3 ]

4 голосов
/ 25 ноября 2011

Хорошие новости! API protobuf-net настроен именно для этого сценария. Вы должны увидеть пару методов SerializeItems и DeserializeItems, которые работают с IEnumerable<T>, позволяя осуществлять потоковую передачу как внутрь, так и наружу. Самый простой способ сделать это перечислением через «блок итератора» над исходными данными.

Если по какой-либо причине это не удобно, это на 100% идентично использованию SerializeWithLengthPrefix и DeserializeWithLengthPrefix для каждого элемента с указанием (в качестве параметров) поля: 1 и стиля префикса: base-128. Вы даже можете использовать SerializeWithLengthPrefix для записи и DeserializeItems для чтения (если вы используете поле 1 и base-128).

В качестве примера - идентификатор должен видеть, что в полностью воспроизводимом сценарии нужно комментировать; на самом деле, то, что я ожидал бы , это то, что вы получаете только один объект обратно, содержащий объединенные значения из каждого объекта - потому что без префикса длины спецификация protobuf предполагает, что вы просто объединяете значения в один объект. Два упомянутых выше подхода позволяют избежать этой проблемы.

2 голосов
/ 08 мая 2013

Может быть, я опоздал на это ... но просто добавлю к тому, что уже сказал Марк.

При использовании Serializer.Serialize(output, price); protobuf обрабатывает последовательные сообщения как часть (одного и того же) единого объекта.Поэтому, когда вы используете десериализацию, используя

while ((price = Serializer.Deserialize<Price>(input)) != null)

, вы получите все записи обратно.Следовательно, вы увидите только последнюю ценовую запись.

Чтобы сделать то, что вы хотите сделать, измените код сериализации на:

Serializer.SerializeWithLengthPrefix(output, price, PrefixStyle.Base128, 1);

и

while ((price = Serializer.DeserializeWithLengthPrefix<Price>(input, PrefixStyle.Base128, 1)) != null)
0 голосов
/ 20 февраля 2017

API практически не изменился с момента ответа Марка.
Кажется, больше нет метода SerializeItems.

Вот еще немного актуальной информации, которая должна помочь:

ProtoBuf.Serializer.Serialize(stream, items);

может принимать IEnumerable, как показано выше, и он выполняет свою работу, когда дело доходит до сериализации.
Однако есть метод DeserializeItems (...), а дьявол кроется в деталях:)
Если вы сериализуете IEnumerable, как описано выше, вам нужно вызвать DeserializeItems, передавая PrefixStyle.Base128 и 1, так как fieldNumber вызывает по умолчанию эти значения по умолчанию.
Вот пример:

ProtoBuf.Serializer.DeserializeItems<T>(stream, ProtoBuf.PrefixStyle.Base128, 1));

Также, как отметили Марк и Вик, вы можете сериализовать / десериализовать для каждого элемента, например, так (используя пользовательские значения для PrefixStyle и fieldNumber):

ProtoBuf.Serializer.SerializeWithLengthPrefix(stream, item, ProtoBuf.PrefixStyle.Base128, fieldNumber: 1);

и

T item;
while ((item = ProtoBuf.Serializer.DeserializeWithLengthPrefix<T>(stream, ProtoBuf.PrefixStyle.Base128, fieldNumber: 1)) != null)
{
    // do stuff here
}
...