最近想把QQ的聊天记录导出来看一下,PC QQ导出来的是MHT格式的单文件网页,聊天记录一大根本无法正常打开浏览。
于是想找个工具解决,在Github上面找到了几个脚本,但是都需要额外安装我不需要的开发环境,于是决定自己写个脚本,把MHT中的图片资源从MHT提取出来,分离为HTML和静态资源文件。这样就能解决文件太大无法打开预览的问题。
用Java写感觉有点不方便,于是选择用Nodejs来读取文件和解析。
大体思路就是先匹配文件的分隔线,然后按分隔线分割每一块,并读取每一块的头部标识,按照头部标识类型的不同进行处理。是图片类型就保存为图片文件,是HTML则保存为HTML。
写完脚本后发现使用fs.readFileSync
或fse.readFileSync
读取大于512M的文本文件内容并处理时,都会抛出ERR_STRING_TOO_LONG
异常。
Error: Cannot create a string longer than 0x1fffffe8 characters
at Buffer.toString (node:buffer:830:17)
at file:///xxxxx.js:4:13
at ModuleJob.run (node:internal/modules/esm/module_job:195:25)
at async ModuleLoader.import (node:internal/modules/esm/loader:337:24)
at async loadESM (node:internal/process/esm_loader:34:7)
at async handleMainPromise (node:internal/modules/run_main:106:12) {
code: 'ERR_STRING_TOO_LONG'
}
原因是Nodejs使用的引擎的内部限制,字符串创建时不能超过一定长度。这个值由buffer.constants.MAX_STRING_LENGTH决定。
并且在不同引擎、不同位数的操作系统下是不同的,一般就两个值0x0ffffff0
(32位系统)和0x1fffffe8
(64位系统) 。我在64位系统
下的node v18.20.2
使用fs.readFileSync
读取超过512MB
大小的文件就会抛出异常。
这个值是没法通过配置参数修改的。
这种情况基本上出现在读取文件内容时,因此解决办法是:如果读取的文件可能比较大,使用流式读取(ReadStream)
逐渐读取并解析,而不是一次性全部读取。
不使用第三方库的情况下ReadStream:
const fs = require('fs')
// 默认缓冲块highWaterMark的大小进行读取,可以在配置参数修改
const readStream = fs.createReadStream(path, { encoding: 'utf-8' })
readStream.on('data', (chunk) => {
// 处理每次读取的数据块
console.log(chunk.toString())
})
readStream.on('end', () => {
console.log('文件读取完毕')
})
readStream.on('error', (err) => {
console.error('读取文件时出错:', err)
})
如果想要按行来读取,一次读取文件的一行,需要用readline
包装一下readStream
:
const fs = require('fs')
const readline = require('readline')
const readStream = fs.createReadStream(filePath, { encoding: 'utf8' })
const rl = readline.createInterface({
input: readStream,
crlfDelay: Infinity
})
// 注意 这里监听的事件是line 而不是on
readStream.on('line', (line) => {
// 处理每次读取一行
console.log(line)
})
// 这里的事件是close
readStream.on('close', () => {
console.log('文件流读取关闭')
})
readStream.on('error', (err) => {
console.error('读取文件时出错:', err)
})