使用Json.Net反序列化大于内存容量的流数据
本文关键字:容量 数据 内存 大于 Json Net 反序列化 使用 | 更新日期: 2023-09-27 18:06:44
我使用以下代码来解压缩包含HttpClient检索的压缩Json提要的本地ZIP文件。
ProgressStream progressStream = null;
API_Json_Special_Feeds.RootObject root = null;
private void import_File(string file)
{
isImporting = true;
Console.WriteLine("Importing " + Path.GetFileName(file));
using (FileStream read = File.OpenRead(file))
{
progressStream = new ProgressStream(read);
using (GZipStream zip = new GZipStream(progressStream, CompressionMode.Decompress))
{
UTF8Encoding temp = new UTF8Encoding(true);
var serializer = new JsonSerializer();
StreamReader sr = new StreamReader(zip);
using (var jsonTextReader = new JsonTextReader(sr))
{
root = serializer.Deserialize<API_Json_Special_Feeds.RootObject>(jsonTextReader);
//I'd like to manipulate root between these lines
foreach (API_Json_Special_Feeds.Item item in root.items)
{
Special_Feed_Data.special_Feed_Items.Add(item);
}
}
progressStream.Dispose();
}
}
}
文件相当大,压缩后为300-600MB,未压缩时为9-11GB。如您所见,我插入了一个中间流,以便检查吞吐量。这在我64GB的机器上运行得很好,但客户端只有8GB的可用空间。试图在一台有8G内存的机器上解压缩和序列化9-11G可不是件有趣的事。
我是Json的新手,所以我最初的想法是在数据被反序列化时对其进行某种过滤或分页,可能与我用来测量流吞吐量的方法相同:
private void timer()
{
bool isRunning = true;
while (isRunning)
{
if (progressStream != null)
{
kBytes_Read = ((double)progressStream.BytesRead / (double)1024);
mem_Used = get_Memory_Used();
if (root != null)
Console.WriteLine("Root contains " + root.items.Count.ToString() + " items");
//This doesn't work, because root is null until ALL of the data is deserialized
}
Thread.Sleep(450);
}
}
在我的脑海中,我看到Json.net一次对一条记录进行反序列化,并将其添加到根目录中的项列表中。这样做的问题是,在流完成之前,"根"的计算结果为null——在反序列化方法完成之前,我找不到访问反序列化数据的方法。
问题是否有任何方法可以访问已经序列化到Root的数据?项,而反序列化仍在进行中?如果不是,如何停止/分页/暂停大数据的反序列化,以避免耗尽内存?
必须避免将整个根对象反序列化到内存中。您可以使用相同的JsonTextReader
来完成此操作,因为它会逐个解析json令牌,但您必须进行一些小的手动解析。下面是一个例子:
static void Main(string[] args)
{
// our fake huge object
var json = @"{""root"":{""items"":[{""data"":""value""},{""data"":""value""}]}}";
using (var reader = new JsonTextReader(new StringReader(json))) {
bool insideItems = false;
while (reader.Read()) {
// reading tokens one by one
if (reader.TokenType == JsonToken.PropertyName) {
// remember, this is just an example, so it's quite crude
if ((string) reader.Value == "items") {
// we reached property named "items" of some object. We assume this is "items" of our root object
insideItems = true;
}
}
if (reader.TokenType == JsonToken.StartObject && insideItems) {
// if we reached start of some json object, and we have already reached "items" property before - we assume
// we are inside "items" array
// here, deserialize items one by one. This way you will consume almost no memory at any given time
var item = JsonSerializer.Create().Deserialize<DataItem>(reader);
Console.WriteLine(item.Data);
}
}
}
}
public class DataItem {
public string Data { get; set; }
}
}
记住,这只是个例子。在实际情况下,您需要进行更仔细的手动解析(检查"items"属性是否确实属于根对象,检查它是否是数组等等),但总体思路是相同的。