Как сериализовать / десериализовать большой список элементов с помощью 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
}