Чтение N-й строки файла в NodeJS
Я пытаюсь извлечь одну строку файла, учитывая, что я знаю имя пути и номер строки, в идеале я хотел бы сделать это , не читая больше файла, чем это необходимо.
Для целей, которые я использую здесь, не имеет значения, является ли это асинхронным или синхронизируемым.
Моя текущая (плохая) реализация выглядит следующим образом:
function get_line(filename, line_no, callback) {
line_no = parseInt(line_no);
var data = fs.readFileSync(filename, 'utf8');
var lines = data.split("\n");
for (var l in lines) {
if (l == line_no - 1) {
callback(null, lines[l].trim());
return;
}
}
throw new Error('File end reached without finding line');
}
Я пытался что-то сделать с помощью createReadStream, но события данных никогда не срабатывали. Может ли кто-либо предоставить прямое решение этой проблемы или указать мне на какую-то документацию по взаимодействию с файловой системой NodeJS, которая немного более ориентирована на пример, чем стандартные документы API библиотеки?
Ответы
Ответ 1
С читаемым потоком
var fs = require('fs');
function get_line(filename, line_no, callback) {
var stream = fs.createReadStream(filename, {
flags: 'r',
encoding: 'utf-8',
fd: null,
mode: 0666,
bufferSize: 64 * 1024
});
var fileData = '';
stream.on('data', function(data){
fileData += data;
// The next lines should be improved
var lines = fileData.split("\n");
if(lines.length >= +line_no){
stream.destroy();
callback(null, lines[+line_no]);
}
});
stream.on('error', function(){
callback('Error', null);
});
stream.on('end', function(){
callback('File end reached without finding line', null);
});
}
get_line('./file.txt', 1, function(err, line){
console.log('The line: ' + line);
})
Прямое решение:
Вы должны использовать метод среза вместо цикла.
var fs = require('fs');
function get_line(filename, line_no, callback) {
var data = fs.readFileSync(filename, 'utf8');
var lines = data.split("\n");
if(+line_no > lines.length){
throw new Error('File end reached without finding line');
}
callback(null, lines[+line_no]);
}
get_line('./file.txt', 9, function(err, line){
console.log('The line: ' + line);
})
для (var l в строках) не является наиболее эффективным способом для цикла по массиву, вы должны сделать это вместо:
for(var i = 0, iMax = lines.length; i < iMax; i++){/* lines[i] */ }
Асинхронный путь:
var fs = require('fs');
function get_line(filename, line_no, callback) {
fs.readFile(filename, function (err, data) {
if (err) throw err;
// Data is a buffer that we need to convert to a string
// Improvement: loop over the buffer and stop when the line is reached
var lines = data.toString('utf-8').split("\n");
if(+line_no > lines.length){
return callback('File end reached without finding line', null);
}
callback(null, lines[+line_no]);
});
}
get_line('./file.txt', 9, function(err, line){
console.log('The line: ' + line);
})
![view?id=AAIAAAD-U-%0AYBJN9lpixm6nxrnAaHQY5DoZ0FcNQ&trk=nav_responsive_tab_profile]()
Ответ 2
не прочитав больше файла, чем необходимо
EDIT: модуль не поддерживается, я рекомендую использовать другие модули для чтения по строкам, например, используя потоки преобразования: http://strongloop.com/strongblog/practical-examples-of-the-new-node-js-streams-api/
С BufferedReader:
var n = 10;
var l = null;
//Internally it uses a buffer, default 16KB, but you can reduce it to, for example, 4KB doing:
//new BufferedReader ("file", { encoding: "utf8", bufferSize: 4*1024 })
new BufferedReader ("file", { encoding: "utf8" })
.on ("error", function (error){
//error
console.log (error);
})
.on ("line", function (line){
if (!--n){
l = line;
//With interrupt you can stop the reading
this.interrupt ();
}
})
.on ("end", function (){
//your Nth line!
console.log (l);
})
.read ();
Ответ 3
Вы можете улучшить производительность ответа FGRibreau, удалив предыдущие данные в переменной "fileData".
function(file, line_no, cb){
var stream = fs.createReadStream(file, {
flags: 'r',
encoding: 'utf-8',
fd: null,
mode: '0666',
bufferSize: 64 * 1024
});
var fileData = '';
stream.on('data', function(data){
fileData += data;
var lines = fileData.split('\n');
if(lines.length >= +line_no){
stream.destroy();
cb(null, lines[+line_no]);
}
// Add this else condition to remove all unnecesary data from the variable
else
fileData = Array(lines.length).join('\n');
});
stream.on('error', function(){
cb('Error', null);
});
stream.on('end', function(){
cb('File end reached without finding line', null);
});
};
Используя файл 70000 строк, чтобы отобразить строку № 50000, я получил следующие результаты:
real 0m3.504s
пользователь 0m0,000s
sys 0m0.015s
В том же примере с else я получил следующее:
real 0m0.540s
пользователь 0m0.015s
sys 0m0.031s
Это также подразумевает гораздо более низкое потребление памяти.