c# - 从流中读取(或编辑)大 JSON 的方式
问题描述
(尚未回答 - 至少有 3 个解决方案留在那里,而不是原来的问题。)
我一直在尝试解析和拆分大 JSON,但不想修改内容。
浮点转换改变了数字,直到 FloatParseHandling 改变。
与使用普通 Stream.ReadToEnd -> 耗尽或耗尽空闲 RAM -> 崩溃或“停止”方法的 30s/5-7GB 相比,类似的循环可以仅使用 14MB 的 RAM 在 40 秒内拆分我的机器上的 1/4GB JSON。
当时还想通过二进制比较来验证结果,但是很多数字都改变了。
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;
}
}
解决方案
翻译成 C# 的Gason可能是现在 C# 语言中最快的解析器,速度类似于 C++ 版本(Debug Build,Release 慢 2 倍),内存消耗大 2 倍: https ://github.com/eltomjan/gason
(免责声明:我隶属于 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 是一个快速而简单的选项 - 在使用 <950MB RAM 的快速机器(调试构建)上,主数组中的整个 1/4GB、<18Mio 行在 <5.3s 内,Newtonsoft.Json 消耗 >30s/5.36国标。如果只解析前 100 行 <330ms,>250MB RAM。
在 Release Build 中,Newton 花费了 >29.3s(>10.8x 更好的性能),甚至更好 <3.2s。
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"
}
]
}
推荐阅读
- linux - 嵌入式 Linux:当标准输出定向到文件或 FIFO 时没有输出
- python - Prefect:代理人和执行人的关系?
- reactjs - 将 split.js 与 React 组件一起使用
- php - PHP MySQL检测是否同一个用户已经登录
- jquery - Angular 找不到模块?
- aws-api-gateway - AWS API Gateway 开发人员门户显示单个阶段的两个链接
- tinymce-5 - 在 TinyMCE 编辑器中处理格式化命令
- python - Assigning Values Depending on a Condition
- extjs - ExtJs 3.4 在 xtype 中显示错误文本字段:'textfield' (MODx)
- ios - 如何在 sprite kit 中引用多个同名的 sprite?