Đọc một dòng một tệp tại một thời điểm trong node.js?


553

Tôi cố gắng để đọc một tập tin một dòng lớn tại một thời điểm. Tôi đã tìm thấy một câu hỏi trên Quora liên quan đến chủ đề này nhưng tôi thiếu một số kết nối để làm cho toàn bộ mọi thứ khớp với nhau.

 var Lazy=require("lazy");
 new Lazy(process.stdin)
     .lines
     .forEach(
          function(line) { 
              console.log(line.toString()); 
          }
 );
 process.stdin.resume();

Bit mà tôi muốn tìm hiểu là làm thế nào tôi có thể đọc từng dòng một từ tệp thay vì STDIN như trong mẫu này.

Tôi đã thử:

 fs.open('./VeryBigFile.csv', 'r', '0666', Process);

 function Process(err, fd) {
    if (err) throw err;
    // DO lazy read 
 }

nhưng nó không hoạt động. Tôi biết rằng trong một tình huống khó khăn tôi có thể quay lại sử dụng một cái gì đó như PHP, nhưng tôi muốn tìm hiểu điều này.

Tôi không nghĩ câu trả lời khác sẽ hoạt động vì tệp lớn hơn nhiều so với máy chủ tôi đang chạy có bộ nhớ.


2
Điều này hóa ra khá khó khăn khi chỉ sử dụng cấp độ thấp fs.readSync(). Bạn có thể đọc các octet nhị phân vào bộ đệm nhưng không có cách nào dễ dàng để xử lý một phần ký tự UTF-8 hoặc UTF-16 mà không cần kiểm tra bộ đệm trước khi dịch nó sang chuỗi JavaScript và quét EOL. Các Buffer()loại không phải là tập hợp phong phú các chức năng để hoạt động trên trường hợp của nó như là chuỗi gốc, nhưng chuỗi nguồn gốc không thể chứa dữ liệu nhị phân. Dường như với tôi rằng việc thiếu một cách tích hợp để đọc các dòng văn bản từ các tập tin tùy ý là một khoảng cách thực sự trong node.js.
hà mã

5
Các dòng trống được đọc bằng phương thức này được chuyển đổi thành một dòng có một 0 (mã ký tự thực tế cho 0) trong đó. Tôi đã phải hack dòng này trong đó:if (line.length==1 && line[0] == 48) special(line);
Thabo

2
Người ta cũng có thể sử dụng gói 'từng dòng một để thực hiện công việc một cách hoàn hảo.
Patrice

1
Vui lòng cập nhật câu hỏi để nói rằng giải pháp là sử dụng luồng biến đổi
Gabriel Llamas

2
@DanDascalescu nếu bạn thích, bạn có thể thêm danh sách này vào danh sách: ví dụ của bạn đã được sửa đổi đôi chút trong nodetài liệu API của github.com/nodejs/node/pull/4609
eljefedelrodeodeljefe

Câu trả lời:


790

Kể từ Node.js v0.12 và tính Node.js v4.0.0, có một ổn định readline module lõi. Đây là cách dễ nhất để đọc các dòng từ một tệp mà không cần bất kỳ mô-đun bên ngoài nào:

const fs = require('fs');
const readline = require('readline');

async function processLineByLine() {
  const fileStream = fs.createReadStream('input.txt');

  const rl = readline.createInterface({
    input: fileStream,
    crlfDelay: Infinity
  });
  // Note: we use the crlfDelay option to recognize all instances of CR LF
  // ('\r\n') in input.txt as a single line break.

  for await (const line of rl) {
    // Each line in input.txt will be successively available here as `line`.
    console.log(`Line from file: ${line}`);
  }
}

processLineByLine();

Hay cách khác:

var lineReader = require('readline').createInterface({
  input: require('fs').createReadStream('file.in')
});

lineReader.on('line', function (line) {
  console.log('Line from file:', line);
});

Dòng cuối cùng được đọc chính xác (kể từ Node v0.12 trở lên), ngay cả khi không có cuối cùng \n.

CẬP NHẬT : ví dụ này đã được thêm vào tài liệu chính thức API của Node .


7
bạn cần một thiết bị đầu cuối: false trong định nghĩa createInterface
glasspill

64
Làm thế nào để xác định dòng cuối cùng? Bằng cách bắt một sự kiện "gần":rl.on('close', cb)
Green

27
Readline dành cho mục đích tương tự như GNU Readline , không phải để đọc các tệp theo từng dòng. Có một số lưu ý trong việc sử dụng nó để đọc tệp và đây không phải là cách thực hành tốt nhất.
Khỏa thân

8
@Nakenible: thú vị. Bạn có thể gửi một câu trả lời với một phương pháp tốt hơn?
Dan Dascalescu

6
Tôi coi github.com/jahewson/node-byline là cách triển khai tốt nhất cho việc đọc từng dòng, nhưng ý kiến ​​có thể khác nhau.
Khỏa thân

164

Đối với một hoạt động đơn giản như vậy, không nên có bất kỳ sự phụ thuộc nào vào các mô-đun của bên thứ ba. Nhẹ nhàng thôi.

var fs = require('fs'),
    readline = require('readline');

var rd = readline.createInterface({
    input: fs.createReadStream('/path/to/file'),
    output: process.stdout,
    console: false
});

rd.on('line', function(line) {
    console.log(line);
});

33
thật đáng buồn, giải pháp hấp dẫn này không hoạt động chính xác linecác sự kiện chỉ đến sau khi nhấn \n, tức là tất cả các lựa chọn thay thế đều bị bỏ qua (xem unicode.org/reports/tr18/#Line_Boundaries ). # 2, dữ liệu sau lần cuối \nbị âm thầm bỏ qua (xem stackoverflow.com/questions/18450197/ mẹo ). Tôi sẽ gọi giải pháp này là nguy hiểm vì nó hoạt động với 99% tất cả các tệp và 99% dữ liệu nhưng không thành công trong phần còn lại. Bất cứ khi nào bạn fs.writeFileSync( path, lines.join('\n'))đã viết một tập tin sẽ chỉ được đọc một phần bởi giải pháp trên.
chảy vào

4
Có một vấn đề với giải pháp này. Nếu bạn sử dụng your.js <lines.txt, bạn sẽ không nhận được dòng cuối cùng. Nếu nó không có '\ n' vào cuối khóa học.
zag2art

Các readlinecư xử gói theo những cách thực sự kỳ lạ đến một kinh nghiệm lập trình Unix / Linux.
Mũi nhọn

11
rd.on("close", ..);có thể được sử dụng như một cuộc gọi lại (xảy ra khi tất cả các dòng được đọc)
Luca Steeb 16/2/2015

6
Vấn đề "dữ liệu sau sự cố \ n" cuối cùng dường như được giải quyết trong phiên bản nút của tôi (0.12.7). Vì vậy, tôi thích câu trả lời này, có vẻ đơn giản và thanh lịch nhất.
Myk Melez

63

Bạn không cần phải opentập tin, nhưng thay vào đó, bạn phải tạo một ReadStream.

fs.createReadStream

Sau đó truyền luồng đó đến Lazy


2
Có một cái gì đó giống như một sự kiện kết thúc cho Lazy? Khi tất cả các dòng đã được đọc trong?
Tối đa

1
@Max, Hãy thử:new lazy(fs.createReadStream('...')).lines.forEach(function(l) { /* ... */ }).join(function() { /* Done */ })
Cecchi

6
@Cecchi và @Max, không sử dụng phép nối vì nó sẽ đệm toàn bộ tệp trong bộ nhớ. Thay vào đó, chỉ cần lắng nghe sự kiện 'kết thúc':new lazy(...).lines.forEach(...).on('end', function() {...})
Corin

3
@Cecchi, @Corin và @Max: Vì những gì đáng giá, tôi đã tự mình xiềng xích điên cuồng .on('end'... sau đó .forEach(...) , trong khi thực tế mọi thứ diễn ra như mong đợi khi tôi kết thúc sự kiện trước .
crowjonah

52
Kết quả này rất cao trên kết quả tìm kiếm, vì vậy điều đáng chú ý là Lazy có vẻ bị bỏ rơi. Đã 7 tháng không có bất kỳ thay đổi nào và có một số lỗi khủng khiếp (dòng cuối cùng bị bỏ qua, rò rỉ bộ nhớ lớn, v.v.).
blu

38

có một mô-đun rất đẹp để đọc từng dòng tệp, nó được gọi là đầu đọc dòng

với nó bạn chỉ cần viết:

var lineReader = require('line-reader');

lineReader.eachLine('file.txt', function(line, last) {
  console.log(line);
  // do whatever you want with line...
  if(last){
    // or check if it's the last one
  }
});

thậm chí bạn có thể lặp lại tệp với giao diện "kiểu java", nếu bạn cần kiểm soát nhiều hơn:

lineReader.open('file.txt', function(reader) {
  if (reader.hasNextLine()) {
    reader.nextLine(function(line) {
      console.log(line);
    });
  }
});

4
Điều này hoạt động tốt. Nó thậm chí còn đọc dòng cuối cùng (!). Điều đáng nói là nó sẽ giữ \ r nếu đó là tệp văn bản kiểu windows. line.trim () thực hiện thủ thuật xóa thêm \ r.
Pierre-Luc Bertrand

Đó là tối ưu phụ trong đầu vào đó chỉ có thể từ một tệp được đặt tên chứ không phải (đối với một ví dụ rõ ràng và cực kỳ quan trọng process/stdin). Ít nhất, nếu có thể, chắc chắn không thể đọc mã và thử mã.
Mũi nhọn

2
Trong khi đó, có một cách tích hợp để đọc các dòng từ một tệp, sử dụng readlinemô-đun lõi .
Dan Dascalescu

Điều này đã cũ, nhưng trong trường hợp bất kỳ ai cũng vấp phải nó: function(reader)function(line)nên là: function(err,reader)function(err,line).
jallmer

1
Chỉ cần cho bản ghi, line-readerđọc tệp không đồng bộ. Sự thay thế đồng bộ cho nó làline-reader-sync
Prajwal Dhatwalia 25/03/19

30
require('fs').readFileSync('file.txt', 'utf-8').split(/\r?\n/).forEach(function(line){
  console.log(line);
})

42
Điều này sẽ đọc toàn bộ tập tin trong bộ nhớ, sau đó chia nó thành các dòng. Đó không phải là những gì các câu hỏi yêu cầu. Vấn đề là có thể đọc các tệp lớn theo tuần tự, theo yêu cầu.
Dan Dascalescu

2
Điều này phù hợp với trường hợp sử dụng của tôi, tôi đang tìm kiếm một cách đơn giản để chuyển đổi đầu vào từ một tập lệnh sang định dạng khác. Cảm ơn!
Callat

23

Cập nhật năm 2019

Một ví dụ tuyệt vời đã được đăng trên tài liệu chính thức của Nodejs. đây

Điều này đòi hỏi Nodejs mới nhất được cài đặt trên máy của bạn. > 11,4

const fs = require('fs');
const readline = require('readline');

async function processLineByLine() {
  const fileStream = fs.createReadStream('input.txt');

  const rl = readline.createInterface({
    input: fileStream,
    crlfDelay: Infinity
  });
  // Note: we use the crlfDelay option to recognize all instances of CR LF
  // ('\r\n') in input.txt as a single line break.

  for await (const line of rl) {
    // Each line in input.txt will be successively available here as `line`.
    console.log(`Line from file: ${line}`);
  }
}

processLineByLine();

câu trả lời này tốt hơn nhiều so với bất cứ điều gì ở trên nhờ vào hành vi dựa trên lời hứa của nó, đặc biệt chỉ ra EOF.
phil294

Cảm ơn, thật ngọt ngào.
Goran Stoyanov

3
Có thể điều này là hiển nhiên đối với những người khác, nhưng tôi phải mất một thời gian để gỡ lỗi: nếu bạn có bất kỳ awaits nào giữa createInterface()cuộc gọi và bắt đầu for awaitvòng lặp, bạn sẽ mất các dòng một cách bí ẩn từ đầu tệp. createInterface()ngay lập tức bắt đầu phát ra các dòng phía sau hậu trường và trình lặp async được tạo hoàn toàn const line of rlkhông thể bắt đầu nghe các dòng đó cho đến khi nó được tạo.
andrewdotn

19

Chủ đề cũ, nhưng điều này hoạt động:

var rl = readline.createInterface({
      input : fs.createReadStream('/path/file.txt'),
      output: process.stdout,
      terminal: false
})
rl.on('line',function(line){
     console.log(line) //or parse line
})

Đơn giản. Không cần một mô-đun bên ngoài.


2
Nếu bạn nhận được readline is not definedhoặc fs is not defined, thêm var readline = require('readline');var fs = require('fs');để làm việc này. Nếu không thì ngọt, mật ngọt. Cảm ơn.
bergie3000

12
Câu trả lời này là một bản sao chính xác của câu trả lời trước đó , nhưng không có ý kiến ​​cảnh báo , gói đọc được đánh dấu không ổn định (vẫn không ổn định kể từ tháng 4 năm 2015) và vào giữa năm 2013, đã gặp sự cố khi đọc các dòng cuối cùng của tệp mà không có kết thúc dòng . Vấn đề dòng cuối cùng đã cắt lên lần đầu tiên tôi sử dụng nó trong v0.10,35, và sau đó biến mất. / argh
ruffin

Bạn không cần chỉ định đầu ra nếu tất cả những gì bạn làm được đọc từ một luồng tệp .
Dan Dascalescu

18

Bạn luôn có thể cuộn đầu đọc dòng của riêng bạn. Tôi đã không điểm chuẩn đoạn trích này, nhưng nó phân chia chính xác luồng chunk đến thành các dòng mà không có dấu '\ n'

var last = "";

process.stdin.on('data', function(chunk) {
    var lines, i;

    lines = (last+chunk).split("\n");
    for(i = 0; i < lines.length - 1; i++) {
        console.log("line: " + lines[i]);
    }
    last = lines[i];
});

process.stdin.on('end', function() {
    console.log("line: " + last);
});

process.stdin.resume();

Tôi đã nghĩ ra điều này khi làm việc với một kịch bản phân tích cú pháp nhật ký nhanh cần tích lũy dữ liệu trong quá trình phân tích cú pháp nhật ký và tôi cảm thấy thật tuyệt khi thử làm điều này bằng cách sử dụng js và nút thay vì sử dụng perl hoặc bash.

Dù sao, tôi cảm thấy rằng các tập lệnh nodejs nhỏ nên được khép kín và không dựa vào các mô-đun của bên thứ ba nên sau khi đọc tất cả các câu trả lời cho câu hỏi này, mỗi mô-đun sử dụng các mô-đun khác nhau để xử lý phân tích cú pháp, một giải pháp nodejs 13 SLOC có thể được quan tâm.


Dường như không có bất kỳ cách tầm thường nào để mở rộng điều này để hoạt động với các tệp tùy ý ngoài việc stdin... trừ khi tôi bị thiếu đôi khi.
hà mã

3
@hippietrail bạn có thể tạo một ReadStreamvới fs.createReadStream('./myBigFile.csv')và sử dụng nó thay vìstdin
nolith

2
Là mỗi đoạn được đảm bảo chỉ chứa các dòng hoàn chỉnh? Các ký tự UTF-8 nhiều byte có được đảm bảo không bị phân tách ở các ranh giới không?
hà mã

1
@hippietrail Tôi không nghĩ rằng các ký tự đa nhân được xử lý chính xác bằng cách triển khai này. Vì vậy, trước tiên người ta phải chuyển đổi chính xác bộ đệm thành chuỗi và theo dõi các ký tự được phân chia giữa hai bộ đệm. Để làm điều đó một cách chính xác, người ta có thể sử dụng StringDecoder tích hợp
Ernelli 5/12/13

Trong khi đó, có một cách tích hợp để đọc các dòng từ một tệp, sử dụng readlinemô-đun lõi .
Dan Dascalescu

12

Với mô đun sóng mang :

var carrier = require('carrier');

process.stdin.resume();
carrier.carry(process.stdin, function(line) {
    console.log('got one line: ' + line);
});

Đẹp. Điều này cũng hoạt động đối với bất kỳ tệp đầu vào nào: var inStream = fs.createReadStream('input.txt', {flags:'r'}); Nhưng cú pháp của bạn sạch hơn phương pháp sử dụng tài liệu .on ():carrier.carry(inStream).on('line', function(line) { ...
Brent Faust

tàu sân bay dường như chỉ xử lý \r\n\nkết thúc dòng. Nếu bạn cần xử lý các tệp thử nghiệm kiểu MacOS từ trước OS X, họ đã sử dụng \rvà nhà mạng không xử lý việc này. Đáng ngạc nhiên, vẫn có những tập tin như vậy trôi nổi trong tự nhiên. Bạn cũng có thể cần xử lý rõ ràng BOM Unicode (dấu thứ tự byte), điều này được sử dụng ở phần đầu của tệp văn bản trong phạm vi ảnh hưởng của MS Windows.
hà mã

Trong khi đó, có một cách tích hợp để đọc các dòng từ một tệp, sử dụng readlinemô-đun lõi .
Dan Dascalescu

9

Tôi đã kết thúc với một rò rỉ bộ nhớ lớn, lớn bằng cách sử dụng Lazy để đọc từng dòng khi cố gắng xử lý các dòng đó và ghi chúng vào luồng khác do cách thoát / tạm dừng / tiếp tục trong nút hoạt động (xem: http: // Elegantcode .com / 2011/04/06 / Take-baby-step-with-node-js-pump-data-between-stream / (tôi yêu anh chàng này btw)). Tôi chưa nhìn kỹ đủ vào Lazy để hiểu chính xác lý do tại sao, nhưng tôi không thể tạm dừng luồng đọc của mình để cho phép thoát mà không có Lazy thoát ra.

Tôi đã viết mã để xử lý các tệp csv lớn vào tài liệu xml, bạn có thể xem mã ở đây: https://github.com/j03m/node-csv2xml

Nếu bạn chạy các phiên bản trước với dòng Lazy thì nó bị rò rỉ. Bản sửa đổi mới nhất hoàn toàn không bị rò rỉ và có lẽ bạn có thể sử dụng nó làm cơ sở cho trình đọc / bộ xử lý. Mặc dù tôi có một số công cụ tùy chỉnh trong đó.

Chỉnh sửa: Tôi đoán tôi cũng nên lưu ý rằng mã của tôi với Lazy hoạt động tốt cho đến khi tôi thấy mình viết các đoạn xml đủ lớn để thoát / tạm dừng / tiếp tục vì cần thiết. Đối với khối nhỏ hơn nó là tốt.


Trong khi đó, có một cách đơn giản hơn nhiều để đọc các dòng từ một tệp, sử dụng readlinemô-đun lõi .
Dan Dascalescu

vâng Đó là cách chính xác bây giờ. Nhưng đây là từ năm 2011. :)
j03m

8

Biên tập:

Sử dụng một luồng biến đổi .


Với BufferedReader, bạn có thể đọc các dòng.

new BufferedReader ("lorem ipsum", { encoding: "utf8" })
    .on ("error", function (error){
        console.log ("error: " + error);
    })
    .on ("line", function (line){
        console.log ("line: " + line);
    })
    .on ("end", function (){
        console.log ("EOF");
    })
    .read ();

1
Trong khi đó, có một cách đơn giản hơn nhiều để đọc các dòng từ một tệp, sử dụng readlinemô-đun lõi .
Dan Dascalescu

7

Kể từ khi đăng câu trả lời ban đầu của tôi, tôi thấy rằng split là một mô-đun nút rất dễ sử dụng để đọc dòng trong một tệp; Mà cũng chấp nhận các tham số tùy chọn.

var split = require('split');
fs.createReadStream(file)
    .pipe(split())
    .on('data', function (line) {
      //each chunk now is a seperate line! 
    });

Không được thử nghiệm trên các tệp rất lớn. Hãy cho chúng tôi biết nếu bạn làm.


6

Tôi đã thất vọng vì thiếu một giải pháp toàn diện cho việc này, vì vậy tôi đã cùng nhau cố gắng ( git / npm ). Danh sách các tính năng được sao chép:

  • Xử lý dòng tương tác (dựa trên cuộc gọi lại, không tải toàn bộ tệp vào RAM)
  • Tùy chọn, trả về tất cả các dòng trong một mảng (chế độ chi tiết hoặc thô)
  • Tương tác gián đoạn truyền phát hoặc thực hiện bản đồ / bộ lọc như xử lý
  • Phát hiện bất kỳ quy ước mới nào (PC / Mac / Linux)
  • Đúng eof / điều trị dòng cuối cùng
  • Xử lý đúng các ký tự UTF-8 nhiều byte
  • Truy xuất thông tin bù byte và độ dài byte trên cơ sở từng dòng
  • Truy cập ngẫu nhiên, sử dụng offset dựa trên dòng hoặc dựa trên byte
  • Tự động ánh xạ thông tin bù dòng, để tăng tốc truy cập ngẫu nhiên
  • Không phụ thuộc
  • Xét nghiệm

NIH? Bạn quyết định :-)


5
function createLineReader(fileName){
    var EM = require("events").EventEmitter
    var ev = new EM()
    var stream = require("fs").createReadStream(fileName)
    var remainder = null;
    stream.on("data",function(data){
        if(remainder != null){//append newly received data chunk
            var tmp = new Buffer(remainder.length+data.length)
            remainder.copy(tmp)
            data.copy(tmp,remainder.length)
            data = tmp;
        }
        var start = 0;
        for(var i=0; i<data.length; i++){
            if(data[i] == 10){ //\n new line
                var line = data.slice(start,i)
                ev.emit("line", line)
                start = i+1;
            }
        }
        if(start<data.length){
            remainder = data.slice(start);
        }else{
            remainder = null;
        }
    })

    stream.on("end",function(){
        if(null!=remainder) ev.emit("line",remainder)
    })

    return ev
}


//---------main---------------
fileName = process.argv[2]

lineReader = createLineReader(fileName)
lineReader.on("line",function(line){
    console.log(line.toString())
    //console.log("++++++++++++++++++++")
})

Tôi sẽ kiểm tra điều này, nhưng bạn có thể cho tôi biết, nó có được đảm bảo không bao giờ phá vỡ các nhân vật đa nhân không? (UTF-8 / UTF-16)
hà mã

2
@hippietrail: Câu trả lời là không dành cho UTF-8, mặc dù nó đang hoạt động trên luồng byte chứ không phải luồng ký tự. Nó phá vỡ trên dòng mới (0x0a). Trong UTF-8, tất cả các byte của một ký tự đa bào có tập bit thứ tự cao. Do đó, không có ký tự đa dòng nào có thể bao gồm một dòng mới được nhúng hoặc ký tự ASCII phổ biến khác. UTF-16 và UTF-32 là một vấn đề khác, tuy nhiên.
George

@George: Tôi nghĩ chúng ta hiểu lầm nhau. Vì CR và LF đều nằm trong phạm vi ASCII và UTF-8 giữ nguyên 128 ký tự ASCII, cả CR và LF đều không thể là một phần của ký tự UTF-8 đa nhân. Điều tôi đã hỏi là liệu datacuộc gọi đến stream.on("data")có thể bắt đầu hay kết thúc chỉ với một phần của một ký tự UTF-8 đa nhân, như được U+10D0tạo thành từ ba bytee1 83 90
hippietrail

1
Điều này vẫn tải toàn bộ nội dung tập tin vào bộ nhớ trước khi biến nó thành một "dòng mới". Điều này không đọc một dòng tại một thời điểm, thay vào đó nó sẽ TẤT CẢ các dòng và sau đó chia chúng theo độ dài bộ đệm "dòng mới". Phương pháp này đánh bại mục đích tạo ra một luồng.
Justin

Trong khi đó, có một cách đơn giản hơn nhiều để đọc các dòng từ một tệp, sử dụng readlinemô-đun lõi .
Dan Dascalescu

5

Tôi muốn giải quyết vấn đề tương tự, về cơ bản, những gì trong Perl sẽ là:

while (<>) {
    process_line($_);
}

Trường hợp sử dụng của tôi chỉ là một tập lệnh độc lập, không phải là một máy chủ, vì vậy đồng bộ là tốt. Đây là những tiêu chí của tôi:

  • Mã đồng bộ tối thiểu có thể sử dụng lại trong nhiều dự án.
  • Không có giới hạn về kích thước tập tin hoặc số lượng dòng.
  • Không có giới hạn về độ dài của dòng.
  • Có thể xử lý Unicode đầy đủ trong UTF-8, bao gồm các ký tự ngoài BMP.
  • Có thể xử lý các kết thúc dòng * nix và Windows (Mac kiểu cũ không cần thiết cho tôi).
  • Ký tự kết thúc dòng được bao gồm trong dòng.
  • Có thể xử lý dòng cuối cùng có hoặc không có ký tự cuối dòng.
  • Không sử dụng bất kỳ thư viện bên ngoài nào không có trong bản phân phối node.js.

Đây là một dự án để tôi cảm nhận về mã loại kịch bản cấp thấp trong node.js và quyết định mức độ khả thi của nó như là một thay thế cho các ngôn ngữ kịch bản lệnh khác như Perl.

Sau một số nỗ lực đáng ngạc nhiên và một vài khởi đầu sai, đây là mã tôi nghĩ ra. Nó khá nhanh nhưng ít tầm thường hơn tôi mong đợi: (chia nó trên GitHub)

var fs            = require('fs'),
    StringDecoder = require('string_decoder').StringDecoder,
    util          = require('util');

function lineByLine(fd) {
  var blob = '';
  var blobStart = 0;
  var blobEnd = 0;

  var decoder = new StringDecoder('utf8');

  var CHUNK_SIZE = 16384;
  var chunk = new Buffer(CHUNK_SIZE);

  var eolPos = -1;
  var lastChunk = false;

  var moreLines = true;
  var readMore = true;

  // each line
  while (moreLines) {

    readMore = true;
    // append more chunks from the file onto the end of our blob of text until we have an EOL or EOF
    while (readMore) {

      // do we have a whole line? (with LF)
      eolPos = blob.indexOf('\n', blobStart);

      if (eolPos !== -1) {
        blobEnd = eolPos;
        readMore = false;

      // do we have the last line? (no LF)
      } else if (lastChunk) {
        blobEnd = blob.length;
        readMore = false;

      // otherwise read more
      } else {
        var bytesRead = fs.readSync(fd, chunk, 0, CHUNK_SIZE, null);

        lastChunk = bytesRead !== CHUNK_SIZE;

        blob += decoder.write(chunk.slice(0, bytesRead));
      }
    }

    if (blobStart < blob.length) {
      processLine(blob.substring(blobStart, blobEnd + 1));

      blobStart = blobEnd + 1;

      if (blobStart >= CHUNK_SIZE) {
        // blobStart is in characters, CHUNK_SIZE is in octets
        var freeable = blobStart / CHUNK_SIZE;

        // keep blob from growing indefinitely, not as deterministic as I'd like
        blob = blob.substring(CHUNK_SIZE);
        blobStart -= CHUNK_SIZE;
        blobEnd -= CHUNK_SIZE;
      }
    } else {
      moreLines = false;
    }
  }
}

Nó có thể có thể được làm sạch hơn nữa, đó là kết quả của thử nghiệm và lỗi.


5

Trong hầu hết các trường hợp, điều này là đủ:

const fs = require("fs")

fs.readFile('./file', 'utf-8', (err, file) => {
  const lines = file.split('\n')

  for (let line of lines)
    console.log(line)
});

2

Trình đọc dòng dựa trên trình tạo: https://github.com/neurosnap/gen-readlines

var fs = require('fs');
var readlines = require('gen-readlines');

fs.open('./file.txt', 'r', function(err, fd) {
  if (err) throw err;
  fs.fstat(fd, function(err, stats) {
    if (err) throw err;

    for (var line of readlines(fd, stats.size)) {
      console.log(line.toString());
    }

  });
});

2

Nếu bạn muốn đọc một dòng tệp theo dòng và viết cái này trong một cái khác:

var fs = require('fs');
var readline = require('readline');
var Stream = require('stream');

function readFileLineByLine(inputFile, outputFile) {

   var instream = fs.createReadStream(inputFile);
   var outstream = new Stream();
   outstream.readable = true;
   outstream.writable = true;

   var rl = readline.createInterface({
      input: instream,
      output: outstream,
      terminal: false
   });

   rl.on('line', function (line) {
        fs.appendFileSync(outputFile, line + '\n');
   });
};

Sự khác biệt giữa câu trả lời của bạn và kofrasa là gì?
Trâu

2
var fs = require('fs');

function readfile(name,online,onend,encoding) {
    var bufsize = 1024;
    var buffer = new Buffer(bufsize);
    var bufread = 0;
    var fd = fs.openSync(name,'r');
    var position = 0;
    var eof = false;
    var data = "";
    var lines = 0;

    encoding = encoding || "utf8";

    function readbuf() {
        bufread = fs.readSync(fd,buffer,0,bufsize,position);
        position += bufread;
        eof = bufread ? false : true;
        data += buffer.toString(encoding,0,bufread);
    }

    function getLine() {
        var nl = data.indexOf("\r"), hasnl = nl !== -1;
        if (!hasnl && eof) return fs.closeSync(fd), online(data,++lines), onend(lines); 
        if (!hasnl && !eof) readbuf(), nl = data.indexOf("\r"), hasnl = nl !== -1;
        if (!hasnl) return process.nextTick(getLine);
        var line = data.substr(0,nl);
        data = data.substr(nl+1);
        if (data[0] === "\n") data = data.substr(1);
        online(line,++lines);
        process.nextTick(getLine);
    }
    getLine();
}

Tôi đã có cùng một vấn đề và đã đưa ra giải pháp trên có vẻ giống với người khác nhưng là aSync và có thể đọc các tệp lớn rất nhanh

Hy vọng điều này sẽ giúp


1

Tôi có một mô-đun nhỏ làm tốt điều này và được sử dụng bởi khá nhiều dự án khác npm readline Lưu ý trong nút v10 có một mô-đun đọc đường dẫn riêng vì vậy tôi đã xuất bản lại mô-đun của mình dưới dạng linebyline https://www.npmjs.com/package/ đường dây

Nếu bạn không muốn sử dụng mô-đun, chức năng rất đơn giản:

var fs = require('fs'),
EventEmitter = require('events').EventEmitter,
util = require('util'),
newlines = [
  13, // \r
  10  // \n
];
var readLine = module.exports = function(file, opts) {
if (!(this instanceof readLine)) return new readLine(file);

EventEmitter.call(this);
opts = opts || {};
var self = this,
  line = [],
  lineCount = 0,
  emit = function(line, count) {
    self.emit('line', new Buffer(line).toString(), count);
  };
  this.input = fs.createReadStream(file);
  this.input.on('open', function(fd) {
    self.emit('open', fd);
  })
  .on('data', function(data) {
   for (var i = 0; i < data.length; i++) {
    if (0 <= newlines.indexOf(data[i])) { // Newline char was found.
      lineCount++;
      if (line.length) emit(line, lineCount);
      line = []; // Empty buffer.
     } else {
      line.push(data[i]); // Buffer new line data.
     }
   }
 }).on('error', function(err) {
   self.emit('error', err);
 }).on('end', function() {
  // Emit last line if anything left over since EOF won't trigger it.
  if (line.length){
     lineCount++;
     emit(line, lineCount);
  }
  self.emit('end');
 }).on('close', function() {
   self.emit('close');
 });
};
util.inherits(readLine, EventEmitter);

1

Một giải pháp khác là chạy logic thông qua trình thực thi tuần tự nsynjs . Nó đọc từng dòng tệp bằng cách sử dụng mô đun đọc dòng nút và nó không sử dụng lời hứa hoặc đệ quy, do đó sẽ không thất bại trên các tệp lớn. Đây là cách mã sẽ như thế nào:

var nsynjs = require('nsynjs');
var textFile = require('./wrappers/nodeReadline').textFile; // this file is part of nsynjs

function process(textFile) {

    var fh = new textFile();
    fh.open('path/to/file');
    var s;
    while (typeof(s = fh.readLine(nsynjsCtx).data) != 'undefined')
        console.log(s);
    fh.close();
}

var ctx = nsynjs.run(process,{},textFile,function () {
    console.log('done');
});

Mã ở trên được dựa trên exampe này: https://github.com/amaksr/nsynjs/blob/master/examples/node-readline/index.js


1

Hai câu hỏi chúng ta phải tự hỏi mình khi thực hiện các hoạt động đó là:

  1. Dung lượng bộ nhớ được sử dụng để thực hiện nó là bao nhiêu?
  2. Là tiêu thụ bộ nhớ tăng mạnh với kích thước tập tin?

Các giải pháp như require('fs').readFileSync()tải toàn bộ tập tin vào bộ nhớ. Điều đó có nghĩa là dung lượng bộ nhớ cần thiết để thực hiện các thao tác sẽ gần như tương đương với kích thước tệp. Chúng ta nên tránh những thứ này cho bất cứ điều gì lớn hơn50mbs

Chúng ta có thể dễ dàng theo dõi lượng bộ nhớ được sử dụng bởi một hàm bằng cách đặt các dòng mã này sau khi gọi hàm:

    const used = process.memoryUsage().heapUsed / 1024 / 1024;
    console.log(
      `The script uses approximately ${Math.round(used * 100) / 100} MB`
    );

Ngay bây giờ là cách tốt nhất để đọc các dòng cụ thể từ một tập tin lớn đang sử dụng nút của readline . Các tài liệu có một ví dụ tuyệt vời .

Mặc dù chúng tôi không cần bất kỳ mô-đun của bên thứ ba nào để làm điều đó. Nhưng, nếu bạn đang viết mã doanh nghiệp, bạn phải xử lý nhiều trường hợp cạnh. Tôi đã phải viết một mô-đun rất nhẹ gọi là Apick File Storage để xử lý tất cả các trường hợp cạnh đó.

Mô-đun lưu trữ tệp Apick: https://www.npmjs.com/package/apickfs Tài liệu: https://github.com/apickjs/apickFS#readme

Tệp ví dụ: https://1drv.ms/t/s!AtkMCsWInsSZiGptXYAFjalXOpUx

Ví dụ: Cài đặt mô-đun

npm i apickfs
// import module
const apickFileStorage = require('apickfs');
//invoke readByLineNumbers() method
apickFileStorage
  .readByLineNumbers(path.join(__dirname), 'big.txt', [163845])
  .then(d => {
    console.log(d);
  })
  .catch(e => {
    console.log(e);
  });

Phương pháp này đã được thử nghiệm thành công với các tệp dày đặc lên tới 4 GB.

big.text là một tệp văn bản dày đặc với 163.845 dòng và là 124 Mb. Tập lệnh để đọc 10 dòng khác nhau từ tệp này chỉ sử dụng khoảng 4,63 MB Bộ nhớ. Và nó phân tích cú pháp JSON hợp lệ thành Đối tượng hoặc Mảng miễn phí. 🥳 Tuyệt vời !!

Chúng ta có thể đọc một dòng duy nhất của tệp hoặc hàng trăm dòng của tệp với mức tiêu thụ bộ nhớ rất ít.


0

tôi sử dụng cái này

function emitLines(stream, re){
    re = re && /\n/;
    var buffer = '';

    stream.on('data', stream_data);
    stream.on('end', stream_end);

    function stream_data(data){
        buffer += data;
        flush();
    }//stream_data

    function stream_end(){
        if(buffer) stream.emmit('line', buffer);
    }//stream_end


    function flush(){
        var re = /\n/;
        var match;
        while(match = re.exec(buffer)){
            var index = match.index + match[0].length;
            stream.emit('line', buffer.substring(0, index));
            buffer = buffer.substring(index);
            re.lastIndex = 0;
        }
    }//flush

}//emitLines

sử dụng chức năng này trên một luồng và lắng nghe các sự kiện dòng sẽ phát ra.

gr-


0

Mặc dù bạn có thể nên sử dụng readlinemô-đun như câu trả lời hàng đầu cho thấy, readlinedường như được định hướng theo giao diện dòng lệnh thay vì đọc dòng. Nó cũng hơi mờ hơn một chút về bộ đệm. (Bất cứ ai cần một trình đọc định hướng dòng phát trực tuyến có thể sẽ muốn điều chỉnh kích thước bộ đệm). Mô-đun readline là ~ 1000 dòng trong khi này, với các số liệu thống kê và kiểm tra, là 34.

const EventEmitter = require('events').EventEmitter;
class LineReader extends EventEmitter{
    constructor(f, delim='\n'){
        super();
        this.totalChars = 0;
        this.totalLines = 0;
        this.leftover = '';

        f.on('data', (chunk)=>{
            this.totalChars += chunk.length;
            let lines = chunk.split(delim);
            if (lines.length === 1){
                this.leftover += chunk;
                return;
            }
            lines[0] = this.leftover + lines[0];
            this.leftover = lines[lines.length-1];
            if (this.leftover) lines.pop();
            this.totalLines += lines.length;
            for (let l of lines) this.onLine(l);
        });
        // f.on('error', ()=>{});
        f.on('end', ()=>{console.log('chars', this.totalChars, 'lines', this.totalLines)});
    }
    onLine(l){
        this.emit('line', l);
    }
}
//Command line test
const f = require('fs').createReadStream(process.argv[2], 'utf8');
const delim = process.argv[3];
const lineReader = new LineReader(f, delim);
lineReader.on('line', (line)=> console.log(line));

Đây là một phiên bản thậm chí ngắn hơn, không có số liệu thống kê, ở 19 dòng:

class LineReader extends require('events').EventEmitter{
    constructor(f, delim='\n'){
        super();
        this.leftover = '';
        f.on('data', (chunk)=>{
            let lines = chunk.split(delim);
            if (lines.length === 1){
                this.leftover += chunk;
                return;
            }
            lines[0] = this.leftover + lines[0];
            this.leftover = lines[lines.length-1];
            if (this.leftover) 
                lines.pop();
            for (let l of lines)
                this.emit('line', l);
        });
    }
}

0
const fs = require("fs")

fs.readFile('./file', 'utf-8', (err, data) => {
var innerContent;
    console.log("Asynchronous read: " + data.toString());
    const lines = data.toString().split('\n')
    for (let line of lines)
        innerContent += line + '<br>';


});


-1

Tôi sử dụng mã bên dưới các dòng đọc sau khi xác minh rằng nó không phải là một thư mục và nó không được bao gồm trong danh sách các tập tin không cần phải kiểm tra.

(function () {
  var fs = require('fs');
  var glob = require('glob-fs')();
  var path = require('path');
  var result = 0;
  var exclude = ['LICENSE',
    path.join('e2e', 'util', 'db-ca', 'someother-file'),
    path.join('src', 'favicon.ico')];
  var files = [];
  files = glob.readdirSync('**');

  var allFiles = [];

  var patternString = [
    'trade',
    'order',
    'market',
    'securities'
  ];

  files.map((file) => {
    try {
      if (!fs.lstatSync(file).isDirectory() && exclude.indexOf(file) === -1) {
        fs.readFileSync(file).toString().split(/\r?\n/).forEach(function(line){
          patternString.map((pattern) => {
            if (line.indexOf(pattern) !== -1) {
              console.log(file + ' contain `' + pattern + '` in in line "' + line +'";');
              result = 1;
            }
          });
        });
      }
    } catch (e) {
      console.log('Error:', e.stack);
    }
  });
  process.exit(result);

})();

-1

Tôi đã xem qua tất cả các câu trả lời ở trên, tất cả chúng đều sử dụng thư viện của bên thứ ba để giải quyết nó. Đó là một giải pháp đơn giản trong API của Node. ví dụ

const fs= require('fs')

let stream = fs.createReadStream('<filename>', { autoClose: true })

stream.on('data', chunk => {
    let row = chunk.toString('ascii')
}))
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.