Способ чтения (или редактирования) большого JSON из / в поток - PullRequest
0 голосов
/ 27 марта 2019

(Ответы пока нет - вместо первоначального вопроса осталось не менее 3 решений.)
Я пытался проанализировать и разбить большой JSON, но не хотел изменять содержимое.
Преобразования с плавающей запятой меняли числа доFloatParseHandling change.

Подобный цикл может разбить 1/4 ГБ JSON на моей машине за 40 с использованием только 14 МБ ОЗУ по сравнению с 30 с / 5-7 ГБ с использованием обычного потока.или подход «стоп».

Хотел также проверить результаты путем двоичного сравнения, но множество чисел изменилось.

jsonReader .FloatParseHandling = FloatParseHandling.Decimal;

using Newtonsoft.Json; // intentionally ugly - complete working code

long batchSize = 500000, start = 0, end = 0, pos = 0;
bool neverEnd = true;
while (neverEnd) {
  end = start + batchSize - 1;
  var sr = new StreamReader(File.Open("bigOne.json", FileMode.Open, FileAccess.Read));
  var sw = new StreamWriter(new FileStream(@"PartNo" + start + ".json", FileMode.Create));
  using (JsonWriter writer = new JsonTextWriter(sw))
  using (var jsonR = new JsonTextReader(sr)) {
    jsonR.FloatParseHandling = FloatParseHandling.Decimal;
    while (neverEnd) {
      neverEnd &= jsonR.Read();
      if (jsonR.TokenType == JsonToken.StartObject
       && jsonR.Path.IndexOf("BigArrayPathStart") == 0) { // batters[0] ... batters[3]
        if (pos > end) break;
        if (pos++ < start) {
          do { jsonR.Read(); } while (jsonR.TokenType != JsonToken.EndObject);
          continue;
        }
      }

      if (jsonR.TokenType >= JsonToken.PropertyName){ writer.WriteToken(jsonR); }
      else if (jsonR.TokenType == JsonToken.StartObject) { writer.WriteStartObject(); }
      else if (jsonR.TokenType == JsonToken.StartArray) { writer.WriteStartArray(); }
      else if (jsonR.TokenType == JsonToken.StartConstructor) {
          writer.WriteStartConstructor(jsonR.Value.ToString());
      }
    }
    start = pos; pos = 0;
  }
}

1 Ответ

1 голос
/ 03 апреля 2019

Gason , переведенный на C #, вероятно, самый быстрый парсер на языке C #, скорость аналогична версии C ++ (Debug Build, в 2 раза медленнее в выпуске), потребление памяти в 2 раза больше: https://github.com/eltomjan/gason

(Отказ от ответственности: я связан с этим C # форком Гасона.)

У синтаксического анализатора есть экспериментальная функция - выход после анализа предварительно определенного числа строк в последнем массиве и продолжение в следующий раз после последнего элемента в следующем пакете:

using Gason;

int endPos = -1;
JsonValue jsn;
Byte[] raw;

String json = @"{""id"":""0001"",""type"":""donut"",""name"":""Cake"",""ppu"":0.55, 
  ""batters"": [ { ""id"": ""1001"", ""type"": ""Regular"" },
                 { ""id"": ""1002"", ""type"": ""Chocolate"" },
                 { ""id"": ""1003"", ""type"": ""Blueberry"" }, 
                 { ""id"": ""1004"", ""type"": ""Devil's Food"" } ]
  }"
raw = Encoding.UTF8.GetBytes(json);
ByteString[] keys = new ByteString[]
{
    new ByteString("batters"),
    null
};
Parser jsonParser = new Parser(true); // FloatAsDecimal (,JSON stack array size=32)
jsonParser.Parse(raw, ref endPos, out jsn, keys, 2, 0, 2); // batters / null path...
ValueWriter wr = new ValueWriter(); // read only 1st 2
using (StreamWriter sw = new StreamWriter(Console.OpenStandardOutput()))
{
    sw.AutoFlush = true;
    wr.DumpValueIterative(sw, jsn, raw);
}
Parser.Parse(raw, ref endPos, out jsn, keys, 2, endPos, 2); // and now following 2
using (StreamWriter sw = new StreamWriter(Console.OpenStandardOutput()))
{
    sw.AutoFlush = true;
    wr.DumpValueIterative(sw, jsn, raw);
}

Теперь это быстрый и простой способ разделения длинных JSON - целых 1/4 ГБ, <18 миллионов строк в основном массиве за <5,3 с на быстрой машине (Debug Build) с использованием <950 МБ ОЗУ, Newtonsoft.Json потребляется> 30s / 5.36GB. Если анализируется только первые 100 строк <330 мс,> 250 МБ ОЗУ.
В Release Build еще лучше <3,2 с, где Ньютон потратил> 29,3 с (> в 10,8 раза лучше).

1st Parse:
{
  "id": "0001",
  "type": "donut",
  "name": "Cake",
  "ppu": 0.55,
  "batters": [
    {
      "id": "1001",
      "type": "Regular"
    },
    {
      "id": "1002",
      "type": "Chocolate"
    }
  ]
}
2nd Parse:
{
  "id": "0001",
  "type": "donut",
  "name": "Cake",
  "ppu": 0.55,
  "batters": [
    {
      "id": "1003",
      "type": "Blueberry"
    },
    {
      "id": "1004",
      "type": "Devil's Food"
    }
  ]
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...