在 Deno 中读取大型 JSON 文件
Reading large JSON file in Deno
我经常发现自己读取一个大 JSON 文件(通常是一个对象数组),然后操作每个对象并写回一个新文件。
为了在 Node 中实现这一点(至少是读取数据部分),我通常使用 stream-json 模块来做类似的事情。
const fs = require('fs');
const StreamArray = require('stream-json/streamers/StreamArray');
const pipeline = fs.createReadStream('sample.json')
.pipe(StreamArray.withParser());
pipeline.on('data', data => {
//do something with each object in file
});
我最近发现了 Deno,并希望能够使用 Deno 完成此工作流程。
看起来标准库中的 readJSON 方法将文件的全部内容读入内存,所以我不知道它是否适合处理大文件。
有没有一种方法可以通过使用 Deno 中内置的一些较低级别的方法从文件中流式传输数据来完成此操作?
我认为像 stream-json
这样的包在 Deno 上和在 NodeJs 上一样有用,所以一种方法肯定是获取该包的源代码并使其在 Deno 上运行. (而且这个答案很快就会过时,因为有很多人在做这样的事情,而且很快就会有人——也许是你——做出他们的结果 public 并且可以导入任何 Deno 脚本。)
或者,虽然这不能直接回答您的问题,但处理 Json 数据的大型数据集的常见模式是让包含 Json 对象的文件以换行符分隔。 (每行一个 Json 个对象。)例如,Hadoop 和 Spark、AWS S3 select 以及许多其他可能使用这种格式。如果您可以获得那种格式的输入数据,那可能会帮助您使用更多的工具。然后,您还可以使用 Deno 标准库中的 readString('\n')
方法流式传输数据:https://github.com/denoland/deno_std/blob/master/io/bufio.ts
具有减少对第三方包的依赖的额外优势。示例代码:
import { BufReader } from "https://deno.land/std/io/bufio.ts";
async function stream_file(filename: string) {
const file = await Deno.open(filename);
const bufReader = new BufReader(file);
console.log('Reading data...');
let line: string;
let lineCount: number = 0;
while ((line = await bufReader.readString('\n')) != Deno.EOF) {
lineCount++;
// do something with `line`.
}
file.close();
console.log(`${lineCount} lines read.`)
}
这是我用于包含 13,147,089 行文本的文件的代码。
请注意,它与 Roberts 的代码相同,但使用的是 readLine() 而不是 readString('\n')。
readLine()
是一个低级的行读取原语。大多数呼叫者应改用 readString('\n')
或使用扫描器。`
import { BufReader } from "https://deno.land/std/io/bufio.ts";
export async function stream_file(filename: string) {
const file = await Deno.open(filename);
const bufReader = new BufReader(file);
console.log("Reading data...");
let line: string | any;
let lineCount: number = 0;
while ((line = await bufReader.readLine()) != Deno.EOF) {
lineCount++;
// do something with `line`.
}
file.close();
console.log(`${lineCount} lines read.`);
}
现在 Deno 1.0 已经出来了,以防其他人有兴趣做这样的事情。我能够拼凑出一个适用于我的用例的小 class。它不像 stream-json
包那样健壮,但它可以很好地处理大型 JSON 数组。
import { EventEmitter } from "https://deno.land/std/node/events.ts";
export class JSONStream extends EventEmitter {
private openBraceCount = 0;
private tempUint8Array: number[] = [];
private decoder = new TextDecoder();
constructor (private filepath: string) {
super();
this.stream();
}
async stream() {
console.time("Run Time");
let file = await Deno.open(this.filepath);
//creates iterator from reader, default buffer size is 32kb
for await (const buffer of Deno.iter(file)) {
for (let i = 0, len = buffer.length; i < len; i++) {
const uint8 = buffer[ i ];
//remove whitespace
if (uint8 === 10 || uint8 === 13 || uint8 === 32) continue;
//open brace
if (uint8 === 123) {
if (this.openBraceCount === 0) this.tempUint8Array = [];
this.openBraceCount++;
};
this.tempUint8Array.push(uint8);
//close brace
if (uint8 === 125) {
this.openBraceCount--;
if (this.openBraceCount === 0) {
const uint8Ary = new Uint8Array(this.tempUint8Array);
const jsonString = this.decoder.decode(uint8Ary);
const object = JSON.parse(jsonString);
this.emit('object', object);
}
};
};
}
file.close();
console.timeEnd("Run Time");
}
}
用法示例
const stream = new JSONStream('test.json');
stream.on('object', (object: any) => {
// do something with each object
});
正在处理一个约 4.8 MB json 文件,其中包含约 20,000 个小对象
[
{
"id": 1,
"title": "in voluptate sit officia non nesciunt quis",
"urls": {
"main": "https://www.placeholder.com/600/1b9d08",
"thumbnail": "https://www.placeholder.com/150/1b9d08"
}
},
{
"id": 2,
"title": "error quasi sunt cupiditate voluptate ea odit beatae",
"urls": {
"main": "https://www.placeholder.com/600/1b9d08",
"thumbnail": "https://www.placeholder.com/150/1b9d08"
}
}
...
]
用了 127 毫秒。
❯ deno run -A parser.ts
Run Time: 127ms
2021 年 7 月更新:我有同样的需求,但找不到可行的解决方案,所以我写了一个库来为 Deno 解决这个问题:https://github.com/xtao-org/jsonhilo
可以像典型的基于 SAX 的解析器一样使用:
import {JsonHigh} from 'https://deno.land/x/jsonhilo@v0.1.0/mod.js'
const stream = JsonHigh({
openArray: () => console.log('<array>'),
openObject: () => console.log('<object>'),
closeArray: () => console.log('</array>'),
closeObject: () => console.log('</object>'),
key: (key) => console.log(`<key>${key}</key>`),
value: (value) => console.log(`<value type="${typeof value}">${value}</value>`),
})
stream.push('{"tuple": [null, true, false, 1.2e-3, "[demo]"]}')
/* OUTPUT:
<object>
<key>tuple</key>
<array>
<value type="object">null</value>
<value type="boolean">true</value>
<value type="boolean">false</value>
<value type="number">0.0012</value>
<value type="string">[demo]</value>
</array>
</object>
*/
还有一个独特的低级接口,可以实现非常快速(此处的基准:https://github.com/xtao-org/jsonhilo-benchmarks)无损解析。
麻省理工下发布,尽情享受吧!我希望它能解决你的问题。 :)
我经常发现自己读取一个大 JSON 文件(通常是一个对象数组),然后操作每个对象并写回一个新文件。
为了在 Node 中实现这一点(至少是读取数据部分),我通常使用 stream-json 模块来做类似的事情。
const fs = require('fs');
const StreamArray = require('stream-json/streamers/StreamArray');
const pipeline = fs.createReadStream('sample.json')
.pipe(StreamArray.withParser());
pipeline.on('data', data => {
//do something with each object in file
});
我最近发现了 Deno,并希望能够使用 Deno 完成此工作流程。
看起来标准库中的 readJSON 方法将文件的全部内容读入内存,所以我不知道它是否适合处理大文件。
有没有一种方法可以通过使用 Deno 中内置的一些较低级别的方法从文件中流式传输数据来完成此操作?
我认为像 stream-json
这样的包在 Deno 上和在 NodeJs 上一样有用,所以一种方法肯定是获取该包的源代码并使其在 Deno 上运行. (而且这个答案很快就会过时,因为有很多人在做这样的事情,而且很快就会有人——也许是你——做出他们的结果 public 并且可以导入任何 Deno 脚本。)
或者,虽然这不能直接回答您的问题,但处理 Json 数据的大型数据集的常见模式是让包含 Json 对象的文件以换行符分隔。 (每行一个 Json 个对象。)例如,Hadoop 和 Spark、AWS S3 select 以及许多其他可能使用这种格式。如果您可以获得那种格式的输入数据,那可能会帮助您使用更多的工具。然后,您还可以使用 Deno 标准库中的 readString('\n')
方法流式传输数据:https://github.com/denoland/deno_std/blob/master/io/bufio.ts
具有减少对第三方包的依赖的额外优势。示例代码:
import { BufReader } from "https://deno.land/std/io/bufio.ts";
async function stream_file(filename: string) {
const file = await Deno.open(filename);
const bufReader = new BufReader(file);
console.log('Reading data...');
let line: string;
let lineCount: number = 0;
while ((line = await bufReader.readString('\n')) != Deno.EOF) {
lineCount++;
// do something with `line`.
}
file.close();
console.log(`${lineCount} lines read.`)
}
这是我用于包含 13,147,089 行文本的文件的代码。
请注意,它与 Roberts 的代码相同,但使用的是 readLine() 而不是 readString('\n')。
readLine()
是一个低级的行读取原语。大多数呼叫者应改用 readString('\n')
或使用扫描器。`
import { BufReader } from "https://deno.land/std/io/bufio.ts";
export async function stream_file(filename: string) {
const file = await Deno.open(filename);
const bufReader = new BufReader(file);
console.log("Reading data...");
let line: string | any;
let lineCount: number = 0;
while ((line = await bufReader.readLine()) != Deno.EOF) {
lineCount++;
// do something with `line`.
}
file.close();
console.log(`${lineCount} lines read.`);
}
现在 Deno 1.0 已经出来了,以防其他人有兴趣做这样的事情。我能够拼凑出一个适用于我的用例的小 class。它不像 stream-json
包那样健壮,但它可以很好地处理大型 JSON 数组。
import { EventEmitter } from "https://deno.land/std/node/events.ts";
export class JSONStream extends EventEmitter {
private openBraceCount = 0;
private tempUint8Array: number[] = [];
private decoder = new TextDecoder();
constructor (private filepath: string) {
super();
this.stream();
}
async stream() {
console.time("Run Time");
let file = await Deno.open(this.filepath);
//creates iterator from reader, default buffer size is 32kb
for await (const buffer of Deno.iter(file)) {
for (let i = 0, len = buffer.length; i < len; i++) {
const uint8 = buffer[ i ];
//remove whitespace
if (uint8 === 10 || uint8 === 13 || uint8 === 32) continue;
//open brace
if (uint8 === 123) {
if (this.openBraceCount === 0) this.tempUint8Array = [];
this.openBraceCount++;
};
this.tempUint8Array.push(uint8);
//close brace
if (uint8 === 125) {
this.openBraceCount--;
if (this.openBraceCount === 0) {
const uint8Ary = new Uint8Array(this.tempUint8Array);
const jsonString = this.decoder.decode(uint8Ary);
const object = JSON.parse(jsonString);
this.emit('object', object);
}
};
};
}
file.close();
console.timeEnd("Run Time");
}
}
用法示例
const stream = new JSONStream('test.json');
stream.on('object', (object: any) => {
// do something with each object
});
正在处理一个约 4.8 MB json 文件,其中包含约 20,000 个小对象
[
{
"id": 1,
"title": "in voluptate sit officia non nesciunt quis",
"urls": {
"main": "https://www.placeholder.com/600/1b9d08",
"thumbnail": "https://www.placeholder.com/150/1b9d08"
}
},
{
"id": 2,
"title": "error quasi sunt cupiditate voluptate ea odit beatae",
"urls": {
"main": "https://www.placeholder.com/600/1b9d08",
"thumbnail": "https://www.placeholder.com/150/1b9d08"
}
}
...
]
用了 127 毫秒。
❯ deno run -A parser.ts
Run Time: 127ms
2021 年 7 月更新:我有同样的需求,但找不到可行的解决方案,所以我写了一个库来为 Deno 解决这个问题:https://github.com/xtao-org/jsonhilo
可以像典型的基于 SAX 的解析器一样使用:
import {JsonHigh} from 'https://deno.land/x/jsonhilo@v0.1.0/mod.js'
const stream = JsonHigh({
openArray: () => console.log('<array>'),
openObject: () => console.log('<object>'),
closeArray: () => console.log('</array>'),
closeObject: () => console.log('</object>'),
key: (key) => console.log(`<key>${key}</key>`),
value: (value) => console.log(`<value type="${typeof value}">${value}</value>`),
})
stream.push('{"tuple": [null, true, false, 1.2e-3, "[demo]"]}')
/* OUTPUT:
<object>
<key>tuple</key>
<array>
<value type="object">null</value>
<value type="boolean">true</value>
<value type="boolean">false</value>
<value type="number">0.0012</value>
<value type="string">[demo]</value>
</array>
</object>
*/
还有一个独特的低级接口,可以实现非常快速(此处的基准:https://github.com/xtao-org/jsonhilo-benchmarks)无损解析。
麻省理工下发布,尽情享受吧!我希望它能解决你的问题。 :)