2016-09-11 1 views
0

Я использую следующий код для распаковки локального ZIP-файла, содержащего сжатый поток Json, полученный HttpClient.Deserializing потоковые данные, большие, чем объем памяти с Json.Net

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-600 МБ и несжатом 9-11 ГБ. Как вы можете видеть, я вставил промежуточный поток, чтобы проверить пропускную способность. Все это отлично работает на моей 64-гигабайтной машине, но у клиента всего 8 ГБ. Попытка распаковать и сериализовать 9-11G на машине с 8 ГБ ОЗУ не будет интересной.

Я новичок в 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, десериализуя одну запись за раз и добавляя ее в список элементов в корне. Проблема заключается в том, что «root» оценивает значение null до тех пор, пока поток не будет завершен. Я не могу найти способ доступа к десериализованным данным до тех пор, пока метод десериализации не будет завершен.

Вопрос Есть ли способ получить доступ к данным, которые уже были сериализованы в Root.Items, пока десериализация продолжается? Если нет, как можно остановиться или приостановить или приостановить десериализацию больших данных, чтобы он не сдул память?

Благодарю вас за ваше время и заранее за любые мысли или предложения, которые вы можете предоставить.

ответ

0

Вы должны избегать десериализации всего корневого объекта в память. Вы можете сделать это, используя тот же самый 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» принадлежит вашему корневому объекту, проверьте, является ли он массивом и т. Д.), Но общая идея такая же.

+0

Очень важно, Evk! В моем случае файлы (их много) сжимают. Чтобы реализовать это, мне придется отказаться от потоковой передачи, распаковать каждый файл локально, а затем прочитать с помощью текстового редактора? Пожалуйста, проявляйте терпение своим невежеством. Я уверен, вы знаете, как это происходит, когда вы впервые вставляете ногу в новую технику. –

+0

@ShannonHolsinger вы можете использовать JsonTextReader с любым TextReader, таким как [StreamReader] (https://msdn.microsoft.com/en-us/library/system.io.streamreader) – ESG

+0

Нет, не ждите, никогда не подумайте - я думаю, что я получите то, что вы говорите. Вместо использования десериализации я могу реализовать фильтрацию строк, как вы предложили. Потрясающие. Позвольте мне попытаться реализовать. –