อ่านไฟล์ทีละหนึ่งไฟล์ใน node.js?


553

ฉันพยายามอ่านไฟล์ขนาดใหญ่ทีละบรรทัด ฉันพบคำถามเกี่ยวกับ Quoraที่เกี่ยวข้องกับเรื่องนี้ แต่ฉันขาดการเชื่อมต่อบางอย่างเพื่อทำให้สิ่งทั้งหมดเข้าด้วยกัน

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

บิตที่ฉันอยากรู้คือฉันจะอ่านทีละบรรทัดจากไฟล์แทน STDIN ได้อย่างไรในตัวอย่างนี้

ฉันเหนื่อย:

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

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

แต่มันไม่ทำงาน ฉันรู้ว่าในเวลาไม่นานฉันสามารถถอยกลับไปใช้บางอย่างเช่น PHP แต่ฉันอยากจะเข้าใจสิ่งนี้

ฉันไม่คิดว่าคำตอบอื่น ๆ จะทำงานได้เนื่องจากไฟล์มีขนาดใหญ่กว่าเซิร์ฟเวอร์ที่ฉันใช้งานอยู่มีหน่วยความจำสำหรับ


2
fs.readSync()ผลัดกันออกมานี้จะค่อนข้างยากโดยใช้เพียงในระดับต่ำ คุณสามารถอ่านเลขฐานแปดไบนารีลงในบัฟเฟอร์ได้ แต่ไม่มีวิธีง่ายๆในการจัดการกับอักขระ UTF-8 หรือ UTF-16 บางส่วนโดยไม่ตรวจสอบบัฟเฟอร์ก่อนแปลเป็นสตริง JavaScript และทำการสแกนหา EOL Buffer()ประเภทไม่ได้เป็นชุดที่อุดมไปด้วยฟังก์ชั่นการใช้งานบนอินสแตนซ์ที่เป็นสตริงพื้นเมือง แต่สตริงพื้นเมืองไม่สามารถมีข้อมูลไบนารี สำหรับฉันดูเหมือนว่าการขาดวิธีการอ่านข้อความในตัวจาก filehandles โดยพลการนั้นเป็นช่องว่างที่แท้จริงใน node.js
hippietrail

5
บรรทัดว่างที่อ่านโดยวิธีนี้จะถูกแปลงเป็นบรรทัดที่มี 0 เดียว (รหัสอักขระจริงสำหรับ 0) ในนั้น ฉันต้องแฮ็กบรรทัดนี้:if (line.length==1 && line[0] == 48) special(line);
Thabo

2
หนึ่งอาจใช้แพคเกจ 'ทีละบรรทัด' ซึ่งทำงานได้อย่างสมบูรณ์
Patrice

1
โปรดอัปเดตคำถามเพื่อบอกว่าวิธีแก้ปัญหาคือใช้สตรีมการแปลง
Gabriel Llamas

2
@DanDascalescu หากคุณต้องการคุณสามารถเพิ่มสิ่งนี้ลงในรายการ: ตัวอย่างของคุณมีการแก้ไขเล็กน้อยในnodeเอกสาร API ของgithub.com/nodejs/node/pull/4609
eljefedelrodeodeljefe

คำตอบ:


789

ตั้งแต่ Node.js v0.12 และ ณ Node.js v4.0.0 มีโมดูลหลักreadline ที่เสถียร นี่เป็นวิธีที่ง่ายที่สุดในการอ่านบรรทัดจากไฟล์โดยไม่มีโมดูลภายนอก:

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();

หรืออีกทางหนึ่ง:

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

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

บรรทัดสุดท้ายจะถูกอ่านได้อย่างถูกต้อง ( ณ โหนด v0.12 หรือหลังจากนั้น) \nแม้ว่าจะไม่มีครั้งสุดท้าย

UPDATE : ตัวอย่างนี้ได้รับการเพิ่มลงในเอกสารที่เป็นทางการของโหนด API


7
คุณต้องการเทอร์มินัล: false ในนิยาม
createInterface

64
วิธีการกำหนดบรรทัดสุดท้าย? โดยจับกิจกรรม "ปิด":rl.on('close', cb)
กรีน

27
Readline สำหรับวัตถุประสงค์ที่คล้ายกันเป็นGNU Readline , ไม่ได้สำหรับการอ่านบรรทัดไฟล์โดยสาย มีข้อควรระวังหลายประการในการใช้เพื่ออ่านไฟล์และนี่ไม่ใช่วิธีปฏิบัติที่ดีที่สุด
Nakedible

8
@Nakedible: น่าสนใจ คุณช่วยโพสต์คำตอบด้วยวิธีที่ดีกว่าได้ไหม?
Dan Dascalescu

6
ฉันพิจารณาgithub.com/jahewson/node-bylineเพื่อให้มีการใช้งานการอ่านแบบบรรทัดต่อบรรทัดได้ดีที่สุด แต่ความคิดเห็นอาจแตกต่างกันไป
Nakedible

164

สำหรับการดำเนินการอย่างง่ายไม่ควรมีการพึ่งพาโมดูลบุคคลที่สามใด ๆ ไปง่าย.

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
น่าเศร้าโซลูชันที่น่าดึงดูดใจนี้ทำงานไม่ถูกต้อง - lineเหตุการณ์เกิดขึ้นหลังจากการกดปุ่ม\nเท่านั้นนั่นคือตัวเลือกทั้งหมดถูกพลาด (ดูunicode.org/reports/tr18/#Line_Boundaries ) # 2 ข้อมูลหลังจากที่ผ่านมา\nจะถูกละเว้นอย่างเงียบ ๆ (ดูstackoverflow.com/questions/18450197/… ) ฉันเรียกโซลูชันนี้ว่าอันตรายเพราะทำงานได้ 99% ของไฟล์ทั้งหมดและ 99% ของข้อมูล แต่ไม่ทำงานอย่างเงียบ ๆในส่วนที่เหลือ เมื่อใดก็ตามที่คุณfs.writeFileSync( path, lines.join('\n'))เขียนไฟล์ที่จะอ่านได้เพียงบางส่วนโดยวิธีการแก้ปัญหาข้างต้น
ไหล

4
มีปัญหากับวิธีนี้ หากคุณใช้ your.js <lines.txt คุณจะไม่ได้รับบรรทัดสุดท้าย หากไม่มี '\ n' ในตอนท้ายของหลักสูตร
zag2art

readlineพฤติกรรมแพคเกจในรูปแบบที่แปลกประหลาดอย่างแท้จริงที่จะมีประสบการณ์โปรแกรมเมอร์ Unix / Linux
แหลม

11
rd.on("close", ..);สามารถใช้เป็นโทรกลับ (occurrs เมื่อทุกสายจะอ่าน)
Luca Steeb

6
ดูเหมือนว่าปัญหา "ข้อมูลหลังจาก \ n ล่าสุด" ในโหนดรุ่นของฉัน (0.12.7) ดังนั้นฉันชอบคำตอบนี้ซึ่งดูเหมือนง่ายที่สุดและสง่างามที่สุด
Myk Melez

63

คุณจะได้ไม่ต้องopenไฟล์ ReadStreamแต่คุณจะต้องสร้าง

fs.createReadStream

จากนั้นส่งกระแสข้อมูลนั้นไปที่ Lazy


2
มีกิจกรรมบางอย่างที่เหมือนจบกิจกรรมของ Lazy หรือไม่? เมื่ออ่านทุกบรรทัดแล้ว?
Max

1
@ Max, ลอง:new lazy(fs.createReadStream('...')).lines.forEach(function(l) { /* ... */ }).join(function() { /* Done */ })
Cecchi

6
@Cchichi และ @Max อย่าใช้การเข้าร่วมเพราะมันจะบัฟเฟอร์ไฟล์ทั้งหมดในหน่วยความจำ ให้ฟังเหตุการณ์ 'สิ้นสุด' แทน:new lazy(...).lines.forEach(...).on('end', function() {...})
คอริน

3
@Cecchi, @Corin และ @Max: สำหรับสิ่งที่มันคุ้มค่าผมขับรถตัวเองผูกมัดบ้า.on('end'... หลังจากที่ .forEach(...)เมื่อในความเป็นจริงทุกอย่างที่ประพฤติตามที่คาดไว้เมื่อฉันผูกพันเหตุการณ์แรก
crowjonah

52
ผลลัพธ์นี้มีสูงมากในผลการค้นหาดังนั้นจึงเป็นที่น่าสังเกตว่า Lazy นั้นถูกทอดทิ้ง มันใช้เวลา 7 เดือนโดยไม่มีการเปลี่ยนแปลงและมีข้อผิดพลาดบางอย่างที่น่ากลัว (บรรทัดสุดท้ายถูกข้ามหน่วยความจำขนาดใหญ่รั่วไหล ฯลฯ )
blu

38

มีโมดูลที่ดีมากสำหรับการอ่านไฟล์ทีละบรรทัดเรียกว่าline-reader

ด้วยคุณเพียงแค่เขียน:

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
  }
});

คุณสามารถทำซ้ำไฟล์ด้วยส่วนต่อประสาน "java-style" หากคุณต้องการการควบคุมเพิ่มเติม:

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

4
มันใช้งานได้ดี มันยังอ่านบรรทัดสุดท้าย (!) เป็นมูลค่าการกล่าวขวัญว่ามันเก็บ \ r หากมันเป็นไฟล์ข้อความสไตล์หน้าต่าง line.trim () ทำเคล็ดลับในการลบส่วนเสริม \ r
Pierre-Luc Bertrand

มันย่อยที่ดีที่สุดในการป้อนข้อมูลที่สามารถมาจากไฟล์ที่มีชื่อเท่านั้นและไม่ (สำหรับตัวอย่างที่ชัดเจนและมีความสำคัญอย่างยิ่งprocess/stdin) อย่างน้อยถ้าทำได้ก็ไม่ชัดเจนจากการอ่านรหัสและพยายาม
แหลม

2
ในขณะเดียวกันมีวิธีในตัวอ่านบรรทัดจากไฟล์โดยใช้โมดูลหลักreadline
Dan Dascalescu

นี้เก่า แต่ในกรณีที่คนสะดุดกับมันfunction(reader)และfunction(line)ควรจะ: และfunction(err,reader) function(err,line)
jallmer

1
สำหรับบันทึกline-readerอ่านไฟล์แบบอะซิงโครนัส ทางเลือกแบบซิงโครนัสคือline-reader-sync
Prajwal Dhatwalia

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

42
นี้จะอ่านไฟล์ทั้งหมดในหน่วยความจำแล้วแบ่งเป็นบรรทัด มันไม่ใช่คำถามที่ถาม จุดนี้จะสามารถอ่านไฟล์ขนาดใหญ่ตามลำดับได้ตามต้องการ
Dan Dascalescu

2
กรณีนี้เหมาะกับการใช้งานของฉันฉันกำลังมองหาวิธีง่ายๆในการแปลงอินพุตจากสคริปต์หนึ่งเป็นรูปแบบอื่น ขอบคุณ!
Callat

23

อัพเดทในปี 2562

ตัวอย่างที่ยอดเยี่ยมได้ถูกโพสต์ในเอกสารทาง Nodejs อย่างเป็นทางการแล้ว ที่นี่

สิ่งนี้ต้องการ Nodej ล่าสุดที่ติดตั้งบนเครื่องของคุณ > 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();

คำตอบนี้ดีกว่าทุกสิ่งที่กล่าวมาข้างต้นเนื่องจากพฤติกรรมตามสัญญาโดยระบุ EOF อย่างชัดเจน
phil294

ขอบคุณที่หวาน
Goran Stoyanov

3
บางทีนี่อาจจะเป็นที่เห็นได้ชัดสำหรับคนอื่น ๆ แต่มันใช้เวลาสักครู่ในการแก้ปัญหา: ถ้าคุณมีสายawaitระหว่างการcreateInterface()โทรและจุดเริ่มต้นของfor awaitลูปคุณจะเสียสายอย่างลึกลับจากจุดเริ่มต้นของไฟล์ createInterface()เริ่มต้นการเปล่งบรรทัดที่อยู่เบื้องหลังและ async iterator ที่สร้างขึ้นโดยปริยายโดยconst line of rlไม่สามารถเริ่มฟังบรรทัดเหล่านั้นได้จนกว่าจะถูกสร้างขึ้น
andrewdotn

19

หัวข้อเก่า แต่ใช้งานได้:

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
})

ง่าย ไม่จำเป็นต้องใช้โมดูลภายนอก


2
หากคุณได้รับreadline is not definedหรือfs is not definedเพิ่มvar readline = require('readline');และvar fs = require('fs');เพื่อให้ได้ผล รหัสมิฉะนั้นหวานหวาน ขอบคุณ
bergie3000

12
คำตอบนี้เป็นคำตอบที่ถูกต้องของคำตอบก่อนหน้านี้แต่ไม่มีคำเตือนความคิดเห็นแพ็คเกจ Readline ถูกทำเครื่องหมายว่าไม่เสถียร (ยังไม่เสถียร ณ เดือนเมษายน 2015) และในกลางปี ​​2013 มีปัญหาในการอ่านบรรทัดสุดท้ายของไฟล์โดยไม่ต้องลงท้ายด้วยบรรทัด ปัญหาบรรทัดสุดท้ายเกรียนครั้งที่ 1 ที่ฉันใช้ใน v0.10.35 แล้วก็หายไป /
argh

คุณไม่จำเป็นต้องระบุการส่งออกถ้าสิ่งที่คุณทำคือการอ่านจากสตรีมไฟล์
Dan Dascalescu

18

คุณสามารถม้วนตัวอ่านบรรทัดของคุณเองได้เสมอ ฉันยังไม่ได้ทำการทดสอบตัวอย่างข้อมูลนี้ แต่มันแยกกระแสข้อมูลที่เข้ามาของชิ้นข้อมูลออกเป็นเส้นอย่างถูกต้องโดยไม่ต้องตามหลัง '\ 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();

ฉันทำสิ่งนี้เมื่อทำงานกับสคริปต์การแยกวิเคราะห์อย่างรวดเร็วซึ่งจำเป็นต้องรวบรวมข้อมูลในระหว่างการแยกวิเคราะห์บันทึกและฉันรู้สึกว่ามันจะดีถ้าลองทำสิ่งนี้โดยใช้ js และโหนดแทนที่จะใช้ perl หรือ bash

อย่างไรก็ตามฉันรู้สึกว่าสคริปต์ nodejs ขนาดเล็กควรมีอยู่ในตัวเองและไม่ต้องพึ่งพาโมดูลของบุคคลที่สามดังนั้นหลังจากอ่านคำตอบทั้งหมดของคำถามนี้แต่ละคนใช้โมดูลที่หลากหลายเพื่อจัดการกับการแยกวิเคราะห์บรรทัด


ดูเหมือนจะไม่มีวิธีการเล็กน้อยที่จะขยายสิ่งนี้เพื่อทำงานกับไฟล์โดยพลการนอกเหนือจากเพียงstdin... เว้นแต่ฉันจะหายไปสักครู่
hippietrail

3
@hippietrail คุณสามารถสร้างReadStreamด้วยfs.createReadStream('./myBigFile.csv')และใช้มันแทนstdin
nolith

2
แต่ละอันรับประกันว่ามีเพียงเส้นที่สมบูรณ์หรือไม่? มีการรับประกันอักขระหลายไบต์ UTF-8 ที่จะไม่แยกตามขอบเขตของชิ้นส่วนหรือไม่?
hippietrail

1
@hippietrail ฉันไม่คิดว่าตัวละครหลายไบต์ได้รับการจัดการอย่างถูกต้องโดยการใช้งานนี้ ก่อนอื่นต้องแปลงบัฟเฟอร์ให้เป็นสตริงอย่างถูกต้องและติดตามตัวอักขระที่แบ่งระหว่างบัฟเฟอร์สองอัน ในการทำอย่างถูกต้องสามารถใช้ตัวสร้างในStringDecoder
Ernelli

ในขณะเดียวกันมีวิธีในตัวอ่านบรรทัดจากไฟล์โดยใช้โมดูลหลักreadline
Dan Dascalescu

12

ด้วยโมดูลผู้ให้บริการ :

var carrier = require('carrier');

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

ดี นอกจากนี้ยังใช้งานได้กับไฟล์อินพุตใด ๆ : var inStream = fs.createReadStream('input.txt', {flags:'r'}); แต่ไวยากรณ์ของคุณสะอาดกว่าวิธีการใช้เอกสาร. on ():carrier.carry(inStream).on('line', function(line) { ...
Brent Faust

ผู้ให้บริการดูเหมือนว่าจะจัดการ\r\nและ\nสิ้นสุดบรรทัดเท่านั้น หากคุณจำเป็นต้องจัดการกับไฟล์ทดสอบสไตล์ MacOS จากก่อน OS X พวกเขาใช้\rและผู้ให้บริการไม่จัดการ น่าแปลกที่มีไฟล์ดังกล่าวลอยอยู่ในป่า คุณอาจต้องจัดการ Unicode BOM (เครื่องหมายคำสั่งซื้อไบต์) อย่างชัดเจนซึ่งจะใช้ที่จุดเริ่มต้นของไฟล์ข้อความใน MS Windows Sphere อิทธิพล
hippietrail

ในขณะเดียวกันมีวิธีในตัวอ่านบรรทัดจากไฟล์โดยใช้โมดูลหลักreadline
Dan Dascalescu

9

ฉันลงเอยด้วยการรั่วไหลของหน่วยความจำขนาดใหญ่ขนาดใหญ่โดยใช้ Lazy เพื่ออ่านทีละบรรทัดเมื่อพยายามประมวลผลบรรทัดเหล่านั้นและเขียนลงในสตรีมอื่นเนื่องจากวิธีการระบาย / หยุดชั่วคราว / ทำงานต่อในโหนดทำงาน (ดู: http: // elegantcode) .com / 2011/04/06 / การทารก - ขั้นตอน - กับ - โหนด - js - สูบน้ำ - ข้อมูล - ระหว่าง - ลำธาร / (ฉันรักผู้ชายคนนี้ btw) ฉันไม่ได้ดู Lazy อย่างใกล้ชิดพอที่จะเข้าใจว่าทำไม แต่ฉันไม่สามารถหยุดสตรีมการอ่านของฉันชั่วคราวเพื่อให้ระบายได้โดยไม่ต้อง Lazy ออก

ฉันเขียนโค้ดเพื่อประมวลผลไฟล์ csv ขนาดใหญ่เป็นเอกสาร xml คุณสามารถดูรหัสได้ที่นี่: https://github.com/j03m/node-csv2xml

หากคุณเรียกใช้การแก้ไขก่อนหน้านี้ด้วยสาย Lazy การรั่วไหลจะเกิดขึ้น การแก้ไขล่าสุดไม่รั่วไหลเลยและคุณอาจใช้เป็นพื้นฐานสำหรับเครื่องอ่าน / โปรเซสเซอร์ แม้ว่าฉันมีบางสิ่งที่กำหนดเองในที่นั่น

แก้ไข: ฉันเดาว่าฉันควรทราบด้วยว่ารหัสของฉันกับ Lazy ทำงานได้ดีจนกระทั่งฉันพบว่าตัวเองเขียนชิ้นส่วน XML ขนาดใหญ่พอที่จะระบาย / หยุดชั่วคราว / กลับมาทำงานต่อได้เพราะมีความจำเป็น สำหรับชิ้นเล็ก ๆ มันก็ดี


ในขณะเดียวกันมีวิธีที่ง่ายมากในการอ่านบรรทัดจากไฟล์โดยใช้โมดูลหลักreadline
Dan Dascalescu

ได้. นั่นคือวิธีที่ถูกต้องตอนนี้ แต่นี่มาจากปี 2011 :)
j03m

8

แก้ไข:

ใช้แปลงกระแส


ด้วยBufferedReaderคุณสามารถอ่านบรรทัด

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
ในขณะเดียวกันมีวิธีที่ง่ายมากในการอ่านบรรทัดจากไฟล์โดยใช้โมดูลหลักreadline
Dan Dascalescu

7

ตั้งแต่โพสต์คำตอบดั้งเดิมของฉันฉันพบว่าการแบ่งเป็นโมดูลโหนดที่ใช้งานง่ายมากสำหรับการอ่านบรรทัดในไฟล์ ซึ่งยังยอมรับพารามิเตอร์ทางเลือก

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

ยังไม่ได้ทดสอบกับไฟล์ที่มีขนาดใหญ่มาก แจ้งให้เราทราบหากคุณทำ


6

ฉันรู้สึกหงุดหงิดจากการไม่มีวิธีแก้ปัญหาที่ครอบคลุมสำหรับเรื่องนี้ดังนั้นฉันจึงรวบรวมความพยายามของตัวเอง ( git / npm ) รายการคุณสมบัติการคัดลอกวาง:

  • การประมวลผลสายอินเตอร์แอคทีฟ (ตามการติดต่อกลับไม่มีการโหลดไฟล์ทั้งหมดลงใน RAM)
  • อีกทางเลือกหนึ่งคือส่งคืนบรรทัดทั้งหมดในอาร์เรย์ (รายละเอียดหรือโหมดดิบ)
  • ขัดจังหวะการสตรีมแบบโต้ตอบหรือดำเนินการแผนที่ / ตัวกรองเช่นการประมวลผล
  • ตรวจจับการขึ้นบรรทัดใหม่ใด ๆ (PC / Mac / Linux)
  • การรักษาที่ถูกต้อง eof / บรรทัดสุดท้าย
  • การจัดการอักขระ UTF-8 แบบหลายไบต์อย่างถูกต้อง
  • ดึงข้อมูลออฟเซตไบต์และความยาวไบต์บนพื้นฐานต่อบรรทัด
  • การเข้าถึงแบบสุ่มโดยใช้การออฟเซ็ตแบบ line-based หรือ byte
  • แม็พข้อมูลออฟเซ็ตบรรทัดโดยอัตโนมัติเพื่อเร่งความเร็วการเข้าถึงแบบสุ่ม
  • การพึ่งพาเป็นศูนย์
  • การทดสอบ

NIH? คุณตัดสินใจ :-)


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("++++++++++++++++++++")
})

ฉันจะทดสอบสิ่งนี้ แต่คุณสามารถบอกฉันได้หรือไม่มันรับประกันได้ว่าจะไม่ทำลายอักขระหลายไบต์หรือไม่? (UTF-8 / UTF-16)
hippietrail

2
@hippietrail: คำตอบนั้นไม่ได้สำหรับ UTF-8 แม้ว่ามันจะทำงานบนสตรีมไบต์แทนการสตรีมตัวละคร มันขึ้นบรรทัดใหม่ (0x0a) ใน UTF-8 ไบต์ทั้งหมดของอักขระหลายไบต์มีการตั้งค่าบิตการเรียงลำดับขั้นสูง ดังนั้นไม่มีอักขระหลายไบต์สามารถรวมขึ้นบรรทัดใหม่หรืออักขระ ASCII ทั่วไปอื่น ๆ อย่างไรก็ตาม UTF-16 และ UTF-32 เป็นอีกเรื่องหนึ่ง
จอร์จ

@ George: ฉันคิดว่าเราเข้าใจผิดกัน เนื่องจาก CR และ LF อยู่ในช่วง ASCII และ UTF-8 จะรักษาอักขระ 128 ASCII ไว้โดยที่ไม่เปลี่ยนแปลง CR หรือ LF จึงไม่สามารถเป็นส่วนหนึ่งของอักขระ UTF-8 หลายไบต์ สิ่งที่ฉันถามคือการที่จะdataโทรหาstream.on("data")อาจเริ่มต้นหรือสิ้นสุดด้วยอักขระ UTF-8 หลายไบต์เพียงอย่างเดียวเช่นซึ่งU+10D0ประกอบด้วยสามไบต์e1 83 90
hippietrail

1
นี่ยังคงโหลดเนื้อหาไฟล์ทั้งหมดในหน่วยความจำก่อนที่จะทำให้มันเป็น "บรรทัดใหม่" สิ่งนี้ไม่ได้อ่านทีละหนึ่งบรรทัด แต่จะใช้ทุกบรรทัดแทนแล้วแยกตามความยาวบัฟเฟอร์ "บรรทัดใหม่" วิธีนี้เอาชนะวัตถุประสงค์ในการสร้างกระแส
Justin

ในขณะเดียวกันมีวิธีที่ง่ายมากในการอ่านบรรทัดจากไฟล์โดยใช้โมดูลหลักreadline
Dan Dascalescu

5

ฉันต้องการที่จะแก้ไขปัญหาเดียวกันนี้โดยทั่วไปสิ่งที่ใน Perl จะเป็น:

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

กรณีการใช้งานของฉันเป็นเพียงสคริปต์แบบสแตนด์อโลนไม่ใช่เซิร์ฟเวอร์ดังนั้นการซิงโครนัสก็ใช้ได้ นี่เป็นเกณฑ์ของฉัน:

  • รหัสซิงโครนัสที่น้อยที่สุดที่สามารถใช้ซ้ำในหลายโครงการ
  • ไม่ จำกัด ขนาดไฟล์หรือจำนวนบรรทัด
  • ไม่จำกัดความยาวของเส้น
  • สามารถจัดการ Unicode แบบเต็มใน UTF-8 รวมถึงตัวละครที่เกิน BMP
  • สามารถจัดการปลายสาย * nix และ Windows (ไม่ต้องใช้ Mac แบบเก่าสำหรับฉัน)
  • อักขระที่ลงท้ายด้วยบรรทัดที่จะรวมในบรรทัด
  • สามารถจัดการบรรทัดสุดท้ายโดยมีหรือไม่มีอักขระสิ้นสุดบรรทัด
  • ไม่ใช้ไลบรารีภายนอกใด ๆ ที่ไม่รวมอยู่ในการแจกจ่าย node.js

นี่เป็นโครงการสำหรับฉันที่จะรู้สึกถึงโค้ดประเภทสคริปต์ระดับต่ำใน node.js และตัดสินใจว่ามันทำงานได้ดีเพียงใดแทนภาษาสคริปต์อื่น ๆ เช่น Perl

หลังจากความพยายามอย่างน่าประหลาดใจและการเริ่มต้นผิดพลาดสองครั้งนี่คือรหัสที่ฉันใช้ มันค่อนข้างเร็ว แต่ไม่สำคัญกว่าที่ฉันคาดไว้: (แยกไว้ที่ 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;
    }
  }
}

มันอาจจะถูกล้างออกไปอีกมันเป็นผลมาจากการลองผิดลองถูก


5

ในกรณีส่วนใหญ่ควรจะเพียงพอ:

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

เครื่องอ่านบรรทัดของตัวสร้าง: 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

หากคุณต้องการอ่านไฟล์ทีละบรรทัดและเขียนในอื่น:

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');
   });
};

ความแตกต่างระหว่างคำตอบของคุณกับโคฟราซาคืออะไร?
บัฟฟาโล่

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();
}

ฉันมีปัญหาเดียวกันและมาพร้อมกับวิธีการแก้ปัญหาข้างต้นดูคล้ายกับคนอื่น ๆ แต่เป็น aSync และสามารถอ่านไฟล์ขนาดใหญ่ได้อย่างรวดเร็ว

ความหวังนี้ช่วยได้


1

ฉันมีโมดูลเล็ก ๆ น้อย ๆ ซึ่งทำได้ดีและถูกใช้โดยโครงการอื่น ๆ ไม่กี่npm readlineหมายเหตุ thay ในโหนด v10 มีโมดูล readline ดั้งเดิมดังนั้นฉันจึงเผยแพร่โมดูลของฉันเป็น linebyline https://www.npmjs.com/package/ linebyline

ถ้าคุณไม่ต้องการใช้โมดูลฟังก์ชั่นนั้นง่ายมาก:

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

ทางออกก็คือการใช้ตรรกะทางผู้จัดการลำดับnsynjs มันอ่านไฟล์แบบทีละบรรทัดโดยใช้โมดูล readline ของโหนดและไม่ใช้สัญญาหรือเรียกซ้ำดังนั้นจึงไม่ล้มเหลวในไฟล์ขนาดใหญ่ นี่คือลักษณะของรหัสที่จะปรากฏ:

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');
});

รหัสข้างต้นอ้างอิงจาก exampe นี้: https://github.com/amaksr/nsynjs/blob/master/examples/node-readline/index.js


1

คำถามสองข้อที่เราต้องถามตัวเองในขณะที่ทำการปฏิบัติการเช่น:

  1. จำนวนหน่วยความจำที่ใช้ในการดำเนินการคือเท่าใด
  2. ปริมาณการใช้หน่วยความจำเพิ่มขึ้นอย่างมากกับขนาดไฟล์หรือไม่?

วิธีแก้ปัญหาเช่นrequire('fs').readFileSync()โหลดไฟล์ทั้งหมดลงในหน่วยความจำ นั่นหมายความว่าปริมาณหน่วยความจำที่ต้องใช้ในการทำงานจะใกล้เคียงกับขนาดไฟล์ เราควรหลีกเลี่ยงสิ่งเหล่านี้เพื่อสิ่งที่ใหญ่กว่า50mbs

เราสามารถติดตามจำนวนหน่วยความจำที่ใช้โดยฟังก์ชั่นได้อย่างง่ายดายโดยการวางโค้ดบรรทัดเหล่านี้หลังจากการเรียกใช้ฟังก์ชัน

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

ตอนนี้วิธีที่ดีที่สุดที่จะอ่านบรรทัดโดยเฉพาะอย่างยิ่งจากไฟล์ที่มีขนาดใหญ่จะใช้โหนดReadLine เอกสารที่มีที่น่าตื่นตาตื่นใจตัวอย่าง

แม้ว่าเราไม่ต้องการโมดูลบุคคลที่สามที่จะทำ แต่ถ้าคุณกำลังเขียนรหัสองค์กรคุณต้องจัดการกับกรณีขอบจำนวนมาก ฉันต้องเขียนโมดูลที่มีน้ำหนักเบามากที่เรียกว่า Apick File Storageเพื่อจัดการกับกล่องขอบทั้งหมด

โมดูลจัดเก็บไฟล์ Apick: https://www.npmjs.com/package/apickfs เอกสารประกอบ: https://github.com/apickjs/apickFS#readme

ไฟล์ตัวอย่าง: https://1drv.ms/t/s!AtkMCsWInsSZiGptXYAFjalXOpUx

ตัวอย่าง: ติดตั้งโมดูล

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);
  });

วิธีนี้ได้รับการทดสอบสำเร็จด้วยไฟล์หนาแน่นสูงสุด 4 GB

big.text เป็นไฟล์ข้อความหนาแน่นที่มี 163,845 บรรทัดและเป็น 124 Mb สคริปต์ในการอ่าน 10 บรรทัดที่แตกต่างจากไฟล์นี้ใช้หน่วยความจำเพียงประมาณ 4.63 MB เท่านั้น และแยกวิเคราะห์ JSON ที่ถูกต้องกับวัตถุหรืออาร์เรย์ได้ฟรี 🥳สุดยอด !!

เราสามารถอ่านไฟล์เดียวหรือหลายร้อยบรรทัดของไฟล์ที่มีการใช้หน่วยความจำน้อยมาก


0

ฉันใช้สิ่งนี้:

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

ใช้ฟังก์ชั่นนี้ในสตรีมและฟังรายการกิจกรรมที่จะส่งเสียง

gr-


0

ในขณะที่คุณควรใช้readlineโมดูลเป็นคำตอบที่ดีที่สุดreadlineดูเหมือนว่าจะมุ่งเน้นไปที่อินเตอร์เฟสบรรทัดคำสั่งแทนที่จะอ่านบรรทัด นอกจากนี้ยังมีความทึบมากขึ้นเล็กน้อยเกี่ยวกับการบัฟเฟอร์ (ใครก็ตามที่ต้องการตัวอ่านการวางสายแบบสตรีมอาจต้องการปรับแต่งขนาดบัฟเฟอร์) โมดูล readline คือ ~ 1,000 บรรทัดในขณะนี้ซึ่งมีสถิติและการทดสอบคือ 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));

นี่เป็นรุ่นที่สั้นกว่าโดยไม่มีสถิติที่ 19 บรรทัด:

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>';


});

0

ฉันห่อตรรกะทั้งหมดของการประมวลผลรายการรายวันเป็นโมดูล npm: line-kit https://www.npmjs.com/package/line-kit

// example
var count = 0
require('line-kit')(require('fs').createReadStream('/etc/issue'),
                    (line) => { count++; },
                    () => {console.log(`seen ${count} lines`)})


-1

ฉันใช้โค้ดด้านล่างเพื่ออ่านบรรทัดหลังจากตรวจสอบว่าไม่ใช่ไดเรกทอรีและไม่รวมอยู่ในรายการไฟล์ไม่จำเป็นต้องตรวจสอบ

(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

ฉันดูคำตอบข้างต้นทั้งหมดแล้วพวกเขาใช้ห้องสมุดบุคคลที่สามเพื่อแก้ปัญหา มันมีทางออกที่ง่ายใน API ของโหนด เช่น

const fs= require('fs')

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

stream.on('data', chunk => {
    let row = chunk.toString('ascii')
}))
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.