逐行读取 FileReader 对象而不将整个文件加载到 RAM 中
Read FileReader object line-by-line without loading the whole file into RAM
现在许多浏览器都支持使用 HTML5 的 FileReader 读取本地文件,这为网站打开了大门,这些网站可以超越 'database front-ends' 进入脚本,这些脚本可以对本地数据做一些有用的事情,而无需首先将其发送到服务器。
除了上传前预处理图像和视频,FileReader 的一个大型应用程序会从某种磁盘 table(CSV、TSV 等)加载数据到浏览器中进行操作 - 也许用于在 D3.js 中绘图或分析或在 WebGL 中创建景观。
问题是,Whosebug 和其他网站上的大多数示例都使用 FileReader 的 .readAsText() 属性,它会在返回结果之前将整个文件读入 RAM。
javascript: how to parse a FileReader object line by line
要在不将数据加载到 RAM 的情况下读取文件,需要使用 .readAsArrayBuffer(),而这个 SO post 是我能得到的最接近好的答案:
filereader api on big files
但是,它对于那个特定问题来说有点太具体了,老实说,我可以尝试几天来使解决方案更通用,但由于我不理解块的重要性,所以两手空空大小或使用 Uint8Array 的原因。解决更普遍的问题,即使用用户可定义的行分隔符逐行读取文件(最好使用 .split(),因为它也接受正则表达式),然后每行执行一些操作(例如将其打印到console.log) 会很理想。
我在下面的 Gist URL 做了一个 LineReader class。正如我在评论中提到的,使用 LF、CR/LF 和 CR 以外的其他行分隔符是不常见的。因此,我的代码仅将 LF 和 CR/LF 视为行分隔符。
https://gist.github.com/peteroupc/b79a42fffe07c2a87c28
示例:
new LineReader(file).readLines(function(line){
console.log(line);
});
这是 Peter O.
代码的改编版 TypeScript class 版本
export class BufferedFileLineReader {
bufferOffset = 0;
callback: (line: string) => void = () => undefined;
currentLine = '';
decodeOptions: TextDecodeOptions = { 'stream': true };
decoder = new TextDecoder('utf-8', { 'ignoreBOM': true });
endCallback: () => void = () => undefined;
lastBuffer: Uint8Array | undefined;
offset = 0;
omittedCR = false;
reader = new FileReader();
sawCR = false;
readonly _error = (event: Event): void => {
throw event;
};
readonly _readFromView = (dataArray: Uint8Array, offset: number): void => {
for (let i = offset; i < dataArray.length; i++) {
// Treats LF and CRLF as line breaks
if (dataArray[i] == 0x0A) {
// Line feed read
const lineEnd = (this.sawCR ? i - 1 : i);
if (lineEnd > 0) {
this.currentLine += this.decoder.decode(dataArray.slice(this.bufferOffset, lineEnd), this.decodeOptions);
}
this.callback(this.currentLine);
this.decoder.decode(new Uint8Array([]));
this.currentLine = '';
this.sawCR = false;
this.bufferOffset = i + 1;
this.lastBuffer = dataArray;
} else if (dataArray[i] == 0x0D) {
if (this.omittedCR) {
this.currentLine += '\r';
}
this.sawCR = true;
} else if (this.sawCR) {
if (this.omittedCR) {
this.currentLine += '\r';
}
this.sawCR = false;
}
this.omittedCR = false;
}
if (this.bufferOffset != dataArray.length) {
// Decode the end of the line if no current line was reached
const lineEnd = (this.sawCR ? dataArray.length - 1 : dataArray.length);
if (lineEnd > 0) {
this.currentLine += this.decoder.decode(dataArray.slice(this.bufferOffset, lineEnd), this.decodeOptions);
}
this.omittedCR = this.sawCR;
}
};
readonly _viewLoaded = (): void => {
if (!this.reader.result) {
this.endCallback();
}
const dataArray = new Uint8Array(this.reader.result as ArrayBuffer);
if (dataArray.length > 0) {
this.bufferOffset = 0;
this._readFromView(dataArray, 0);
this.offset += dataArray.length;
const s = this.file.slice(this.offset, this.offset + 256);
this.reader.readAsArrayBuffer(s);
} else {
if (this.currentLine.length > 0) {
this.callback(this.currentLine);
}
this.decoder.decode(new Uint8Array([]));
this.currentLine = '';
this.sawCR = false;
this.endCallback();
}
}
constructor(private file: File) {
this.reader.addEventListener('load', this._viewLoaded);
this.reader.addEventListener('error', this._error);
}
public readLines(callback: (line: string) => void, endCallback: () => void) {
this.callback = callback;
this.endCallback = endCallback;
const slice = this.file.slice(this.offset, this.offset + 8192);
this.reader.readAsArrayBuffer(slice);
}
}
再次感谢 Peter O 的精彩回答。
现在许多浏览器都支持使用 HTML5 的 FileReader 读取本地文件,这为网站打开了大门,这些网站可以超越 'database front-ends' 进入脚本,这些脚本可以对本地数据做一些有用的事情,而无需首先将其发送到服务器。
除了上传前预处理图像和视频,FileReader 的一个大型应用程序会从某种磁盘 table(CSV、TSV 等)加载数据到浏览器中进行操作 - 也许用于在 D3.js 中绘图或分析或在 WebGL 中创建景观。
问题是,Whosebug 和其他网站上的大多数示例都使用 FileReader 的 .readAsText() 属性,它会在返回结果之前将整个文件读入 RAM。
javascript: how to parse a FileReader object line by line
要在不将数据加载到 RAM 的情况下读取文件,需要使用 .readAsArrayBuffer(),而这个 SO post 是我能得到的最接近好的答案:
filereader api on big files
但是,它对于那个特定问题来说有点太具体了,老实说,我可以尝试几天来使解决方案更通用,但由于我不理解块的重要性,所以两手空空大小或使用 Uint8Array 的原因。解决更普遍的问题,即使用用户可定义的行分隔符逐行读取文件(最好使用 .split(),因为它也接受正则表达式),然后每行执行一些操作(例如将其打印到console.log) 会很理想。
我在下面的 Gist URL 做了一个 LineReader class。正如我在评论中提到的,使用 LF、CR/LF 和 CR 以外的其他行分隔符是不常见的。因此,我的代码仅将 LF 和 CR/LF 视为行分隔符。
https://gist.github.com/peteroupc/b79a42fffe07c2a87c28
示例:
new LineReader(file).readLines(function(line){
console.log(line);
});
这是 Peter O.
代码的改编版 TypeScript class 版本export class BufferedFileLineReader {
bufferOffset = 0;
callback: (line: string) => void = () => undefined;
currentLine = '';
decodeOptions: TextDecodeOptions = { 'stream': true };
decoder = new TextDecoder('utf-8', { 'ignoreBOM': true });
endCallback: () => void = () => undefined;
lastBuffer: Uint8Array | undefined;
offset = 0;
omittedCR = false;
reader = new FileReader();
sawCR = false;
readonly _error = (event: Event): void => {
throw event;
};
readonly _readFromView = (dataArray: Uint8Array, offset: number): void => {
for (let i = offset; i < dataArray.length; i++) {
// Treats LF and CRLF as line breaks
if (dataArray[i] == 0x0A) {
// Line feed read
const lineEnd = (this.sawCR ? i - 1 : i);
if (lineEnd > 0) {
this.currentLine += this.decoder.decode(dataArray.slice(this.bufferOffset, lineEnd), this.decodeOptions);
}
this.callback(this.currentLine);
this.decoder.decode(new Uint8Array([]));
this.currentLine = '';
this.sawCR = false;
this.bufferOffset = i + 1;
this.lastBuffer = dataArray;
} else if (dataArray[i] == 0x0D) {
if (this.omittedCR) {
this.currentLine += '\r';
}
this.sawCR = true;
} else if (this.sawCR) {
if (this.omittedCR) {
this.currentLine += '\r';
}
this.sawCR = false;
}
this.omittedCR = false;
}
if (this.bufferOffset != dataArray.length) {
// Decode the end of the line if no current line was reached
const lineEnd = (this.sawCR ? dataArray.length - 1 : dataArray.length);
if (lineEnd > 0) {
this.currentLine += this.decoder.decode(dataArray.slice(this.bufferOffset, lineEnd), this.decodeOptions);
}
this.omittedCR = this.sawCR;
}
};
readonly _viewLoaded = (): void => {
if (!this.reader.result) {
this.endCallback();
}
const dataArray = new Uint8Array(this.reader.result as ArrayBuffer);
if (dataArray.length > 0) {
this.bufferOffset = 0;
this._readFromView(dataArray, 0);
this.offset += dataArray.length;
const s = this.file.slice(this.offset, this.offset + 256);
this.reader.readAsArrayBuffer(s);
} else {
if (this.currentLine.length > 0) {
this.callback(this.currentLine);
}
this.decoder.decode(new Uint8Array([]));
this.currentLine = '';
this.sawCR = false;
this.endCallback();
}
}
constructor(private file: File) {
this.reader.addEventListener('load', this._viewLoaded);
this.reader.addEventListener('error', this._error);
}
public readLines(callback: (line: string) => void, endCallback: () => void) {
this.callback = callback;
this.endCallback = endCallback;
const slice = this.file.slice(this.offset, this.offset + 8192);
this.reader.readAsArrayBuffer(slice);
}
}
再次感谢 Peter O 的精彩回答。