Nodejs读取非常大的文件(~10GB),逐行处理然后写入其他文件

时间:2015-07-17 15:29:19

标签: node.js file-io large-files file-handling file-processing

我有一个特定格式的 10 GB 日志文件,我想逐行处理此文件,然后将输出写入其他文件< / strong>应用了一些转换之后。我正在使用节点进行此操作。

虽然这种方法很好,但这需要花费大量时间。我能够在JAVA的30-45分钟内完成这项工作,但在节点中,完成同样的工作需要160多分钟。以下是代码:

  

以下是从输入中读取每一行的启动代码。

var path = '../10GB_input_file.txt';
var output_file = '../output.txt';

function fileopsmain(){

    fs.exists(output_file, function(exists){
        if(exists) {
            fs.unlink(output_file, function (err) {
                if (err) throw err;
                console.log('successfully deleted ' + output_file);
            });
        }
    });

    new lazy(fs.createReadStream(path, {bufferSize: 128 * 4096}))
        .lines
        .forEach(function(line){
            var line_arr = line.toString().split(';');
            perform_line_ops(line_arr, line_arr[6], line_arr[7], line_arr[10]);
        }
    );

}
  

这是在该行上执行某些操作的方法   将输入传递给write方法,将其写入输出文件。

function perform_line_ops(line_arr, range_start, range_end, daynums){

    var _new_lines = '';
    for(var i=0; i<days; i++){
        //perform some operation to modify line pass it to print
    }

    write_line_ops(_new_lines);
}
  

以下方法用于将数据写入新文件。

function write_line_ops(line) {
    if(line != null && line != ''){
        fs.appendFileSync(output_file, line);
    }
}

我想把这段时间缩短到15-20分钟。是否可以这样做。

另外,对于记录,我在英特尔 i7处理器上尝试使用 8 GB 的RAM。

4 个答案:

答案 0 :(得分:2)

无需模块即可轻松完成此操作。例如:

var fs = require('fs');
var inspect = require('util').inspect;

var buffer = '';
var rs = fs.createReadStream('foo.log');
rs.on('data', function(chunk) {
  var lines = (buffer + chunk).split(/\r?\n/g);
  buffer = lines.pop();
  for (var i = 0; i < lines.length; ++i) {
    // do something with `lines[i]`
    console.log('found line: ' + inspect(lines[i]));
  }
});
rs.on('end', function() {
  // optionally process `buffer` here if you want to treat leftover data without
  // a newline as a "line"
  console.log('ended on non-empty buffer: ' + inspect(buffer));
});

答案 1 :(得分:0)

我无法猜测代码中可能出现的瓶颈在哪里。

  • 您可以添加lazy功能的库或源代码吗?
  • 您的perform_line_ops执行了多少次操作? (if / else,switch / case,function calls)

我已根据您给定的代码创建了一个示例,我知道这不会回答您的问题但可能有助于您了解节点如何处理此类情况。

const fs = require('fs')
const path = require('path')

const inputFile = path.resolve(__dirname, '../input_file.txt')
const outputFile = path.resolve(__dirname, '../output_file.txt')

function bootstrap() {
    // fs.exists is deprecated
    // check if output file exists
    // https://nodejs.org/api/fs.html#fs_fs_exists_path_callback
    fs.exists(outputFile, (exists) => {
        if (exists) {
            // output file exists, delete it
            // https://nodejs.org/api/fs.html#fs_fs_unlink_path_callback
            fs.unlink(outputFile, (err) => {
                if (err) {
                    throw err
                }

                console.info(`successfully deleted: ${outputFile}`)
                checkInputFile()
            })
        } else {
            // output file doesn't exist, move on
            checkInputFile()
        }
    })
}

function checkInputFile() {
    // check if input file can be read
    // https://nodejs.org/api/fs.html#fs_fs_access_path_mode_callback
    fs.access(inputFile, fs.constants.R_OK, (err) => {
        if (err) {
            // file can't be read, throw error
            throw err
        }

        // file can be read, move on
        loadInputFile()
    })
}

function saveToOutput() {
    // create write stream
    // https://nodejs.org/api/fs.html#fs_fs_createwritestream_path_options
    const stream = fs.createWriteStream(outputFile, {
        flags: 'w'
    })

    // return wrapper function which simply writes data into the stream
    return (data) => {
        // check if the stream is writable
        if (stream.writable) {
            if (data === null) {
                stream.end()
            } else if (data instanceof Array) {
                stream.write(data.join('\n'))
            } else {
                stream.write(data)
            }
        }
    }
}

function parseLine(line, respond) {
    respond([line])
}

function loadInputFile() {
    // create write stream
    const saveOutput = saveToOutput()
    // create read stream
    // https://nodejs.org/api/fs.html#fs_fs_createreadstream_path_options
    const stream = fs.createReadStream(inputFile, {
        autoClose: true,
        encoding: 'utf8',
        flags: 'r'
    })

    let buffer = null

    stream.on('data', (chunk) => {
        // append the buffer to the current chunk
        const lines = (buffer !== null)
            ? (buffer + chunk).split('\n')
            : chunk.split('\n')

        const lineLength = lines.length
        let lineIndex = -1

        // save last line for later (last line can be incomplete)
        buffer = lines[lineLength - 1]

        // loop trough all lines
        // but don't include the last line
        while (++lineIndex < lineLength - 1) {
            parseLine(lines[lineIndex], saveOutput)
        }
    })

    stream.on('end', () => {
        if (buffer !== null && buffer.length > 0) {
            // parse the last line
            parseLine(buffer, saveOutput)
        }

        // Passing null signals the end of the stream (EOF)
        saveOutput(null)
    })
}

// kick off the parsing process
bootstrap()

答案 2 :(得分:0)

我知道这已经老了但是......

猜测 appendFileSync() _write()_ s到文件系统并等待响应。很多小写通常都很昂贵,假设你在Java中使用 BufferedWriter ,你可以通过跳过一些write()来获得更快的结果。

使用其中一个异步写入并查看节点是否合理缓冲,或者将行写入大节点缓冲区直到它已满并始终写入一个完整(或接近完整)的缓冲区。通过调整缓冲区大小,您可以验证写入次数是否影响perf。我怀疑它会。

答案 3 :(得分:0)

执行速度很慢,因为您没有使用节点的异步操作。从本质上讲,您可以像这样执行代码:

> read some lines
> transform
> write some lines
> repeat

虽然你可以一次做所有事情,或至少阅读和写作。这里答案中的一些例子可以做到这一点,但语法至少是复杂的。使用scramjet,你可以用几行简单的方法完成:

const {StringStream} = require('scramjet');

fs.createReadStream(path, {bufferSize: 128 * 4096})
    .pipe(new StringStream({maxParallel: 128})    // I assume this is an utf-8 file
    .split("\n")                                  // split per line
    .parse((line) => line.split(';'))             // parse line
    .map([line_arr, range_start, range_end, daynums] => {
        return simplyReturnYourResultForTheOtherFileHere(
            line_arr, range_start, range_end, daynums
        );                                         // run your code, return promise if you're doing some async work
    })
    .stringify((result) => result.toString())
    .pipe(fs.createWriteStream)
    .on("finish", () => console.log("done"))
    .on("error", (e) => console.log("error"))

这可能会跑得更快。