2016-09-11 9 views
0

Ich verwende den folgenden Code, um eine lokale ZIP-Datei zu dekomprimieren, die einen komprimierten Json-Feed enthält, der von HttpClient abgerufen wurde.Deserialisieren von Streaming-Daten größer als die Speicherkapazität mit 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(); 
      } 
     } 
} 

Die Datei ist ziemlich groß bei 300-600MB komprimiert und 9-11GB unkomprimiert. Wie Sie sehen können, habe ich einen Zwischenstream eingefügt, damit ich den Durchsatz überprüfen kann. Alles funktioniert gut auf meinem 64GB-Computer, aber der Client hat nur 8 GB zu spielen. Der Versuch, 9-11G auf einer Maschine mit 8G RAM zu dekomprimieren und zu serialisieren, wird keinen Spaß machen.

Ich bin neu in Json, so mein ursprünglichen Gedanke war, auf den Daten eine Art Filter oder Paginierung zu setzen, wie es deserialisiert Wesen, vielleicht in der gleichen Methode, die ich verwende Stream-Durchsatz zu messen:

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); 
     } 
    } 

In meinem Kopf sehe ich, wie Json.net einen Datensatz nach dem anderen löscht und zur Liste der Elemente in root hinzufügt. Das Problem dabei ist, dass "root" zu null ausgewertet wird, bis der Stream vollständig ist. Ich kann keinen Weg finden, auf die deserialisierten Daten zuzugreifen, bis die Deserialisierung abgeschlossen ist.

Frage Gibt es eine Möglichkeit, auf die Daten zuzugreifen, die bereits in Root.Items serialisiert wurden, während die Deserialisierung noch in Bearbeitung ist? Wenn nicht, wie würde man die Deserialisierung von großen Daten anhalten/paginieren/pausieren, damit sie das Gedächtnis nicht ausreißt?

Ich danke Ihnen für Ihre Zeit und im Voraus für alle Gedanken oder Vorschläge, die Sie zur Verfügung stellen können.

Antwort

0

Sie müssen die Deserialisierung des gesamten Root-Objekts in den Speicher vermeiden. Sie können dies mit dem gleichen JsonTextReader tun, weil es JSON-Token einzeln analysiert, aber Sie müssen ein wenig manuelle Parsing tun. Hier ein Beispiel:

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; } 
    } 
} 

Denken Sie daran, dies ist nur ein Beispiel. Im wirklichen Leben müssen Sie sorgfältigeres manuelles Parsing durchführen (prüfen Sie, ob die "items" -Eigenschaft tatsächlich von Ihrem Root-Objekt ist, überprüfen Sie, ob es ein Array ist usw.), aber die allgemeine Idee ist die gleiche.

+0

Viel verpflichtet, Evk! In meinem Fall sind die Dateien (es gibt viele) komprimiert. Um dies zu implementieren, müsste ich dann auf das Streaming verzichten, jede Datei lokal dekomprimieren und dann mit dem Textreader lesen? Bitte haben Sie Geduld mit meiner Ignoranz. Ich bin mir sicher, du weißt wie es ist, wenn du zum ersten Mal deinen Fuß in eine neue Technologie steckst. –

+0

@ShannonHolsinger Sie können den JsonTextReader mit jedem TextReader verwenden, wie zum Beispiel dem [StreamReader] (https://msdn.microsoft.com/en-us/library/system.io.streamreader) – ESG

+0

Nein, nein, warten Sie nicht - ich denke ich Bekomme, was du sagst. Anstatt Deserialize zu verwenden, kann ich die String-Filterung wie vorgeschlagen implementieren. Genial. Lass mich versuchen zu implementieren. –

Verwandte Themen