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

у меня есть список около 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);
    }
}

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

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

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

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

3 ответов


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

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

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


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

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

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

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

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

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

и

while ((price = Serializer.DeserializeWithLengthPrefix<Price>(input, PrefixStyle.Base128, 1)) != null)

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

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

ProtoBuf.Serializer.Serialize(stream, items);

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

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
}