使用Json.Net反序列化大于内存容量的流数据



我使用以下代码来解压缩包含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"属性是否确实属于根对象,检查它是否是数组等等),但总体思路是相同的。

最新更新