วิธีคัดลอกไฟล์ข้อมูลขนาดใหญ่ทีละบรรทัด?


9

ฉันมีCSVไฟล์35GB ฉันต้องการอ่านแต่ละบรรทัดและเขียนบรรทัดออกเป็น CSV ใหม่หากตรงกับเงื่อนไข

try (BufferedWriter writer = Files.newBufferedWriter(Paths.get("source.csv"))) {
    try (BufferedReader br = Files.newBufferedReader(Paths.get("target.csv"))) {
        br.lines().parallel()
            .filter(line -> StringUtils.isNotBlank(line)) //bit more complex in real world
            .forEach(line -> {
                writer.write(line + "\n");
        });
    }
}

สิ่งนี้ใช้เวลาประมาณ 7 นาที เป็นไปได้หรือไม่ที่จะเพิ่มความเร็วของกระบวนการนั้นให้มากขึ้น?


1
ใช่คุณสามารถลองไม่ทำสิ่งนี้จาก Java แต่ทำได้โดยตรงจาก Linux / Windows / etc ระบบปฏิบัติการ. Java ถูกตีความและจะมีค่าใช้จ่ายในการใช้งานเสมอ นอกจากนี้ไม่ฉันไม่มีวิธีที่ชัดเจนในการเพิ่มความเร็วและ 7 นาทีสำหรับ 35GB นั้นสมเหตุสมผลสำหรับฉัน
Tim Biegeleisen

1
อาจจะลบparallelทำให้เร็วขึ้น? และนั่นไม่สับเปลี่ยนเส้นรอบ ๆ ?
Thilo

1
สร้างBufferedWriterตัวเองโดยใช้Constructorที่ให้คุณตั้งค่าขนาดบัฟเฟอร์ บางทีขนาดบัฟเฟอร์ที่ใหญ่กว่า (หรือเล็กกว่า) อาจสร้างความแตกต่างได้ ฉันจะพยายามจับคู่BufferedWriterขนาดบัฟเฟอร์กับขนาดบัฟเฟอร์ของระบบปฏิบัติการโฮสต์
Abra

5
@TimBiegeleisen: "Java ถูกตีความ" เป็นความเข้าใจผิดที่ดีที่สุดและเกือบจะผิดเสมอเช่นกัน ใช่สำหรับการเพิ่มประสิทธิภาพบางอย่างที่คุณอาจต้องออกจากโลก JVM แต่การทำเช่นนี้ได้เร็วขึ้นใน Java เป็นมั่นเหมาะ doable
Joachim Sauer

1
คุณควรสร้างโปรไฟล์แอปพลิเคชันเพื่อดูว่ามีฮอตสปอตใด ๆ ที่คุณสามารถทำอะไรได้บ้าง คุณจะไม่สามารถทำอะไรได้มากมายเกี่ยวกับ raw IO (บัฟเฟอร์ 8192 ไบต์เริ่มต้นนั้นไม่ดีนักเนื่องจากมีขนาดเซกเตอร์อื่น ๆ ที่เกี่ยวข้อง) แต่อาจมีสิ่งต่าง ๆ เกิดขึ้น (ภายใน) ที่คุณอาจจะสามารถ ทำงานกับ.
Kayaman

คำตอบ:


4

หากเป็นตัวเลือกคุณสามารถใช้ GZipInputStream / GZipOutputStream เพื่อลดขนาดดิสก์ I / O

Files.newBufferedReader / Writer ใช้ขนาดบัฟเฟอร์เริ่มต้น 8 KB ฉันเชื่อว่า คุณอาจลองบัฟเฟอร์ที่ใหญ่กว่านี้

การแปลงเป็นสตริง Unicode จะชะลอตัวลงเป็น (และใช้หน่วยความจำสองเท่า) UTF-8 ที่ใช้ไม่ง่ายเหมือน StandardCharsets ISOO_8859_1

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

ไฟล์ที่แมปหน่วยความจำอาจเหมาะสมที่สุด อาจใช้ Parallelism ในช่วงของไฟล์ทำให้ไฟล์แตกออกมา

try (FileChannel sourceChannel = new RandomAccessFile("source.csv","r").getChannel(); ...
MappedByteBuffer buf = sourceChannel.map(...);

สิ่งนี้จะกลายเป็นโค้ดจำนวนมากรับบรรทัดที่ถูกต้อง(byte)'\n'แต่ไม่ซับซ้อนเกินไป


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

ฉันเพิ่งทดสอบGZipInputStream + GZipOutputStreamหน่วยความจำอย่างเต็มรูปแบบบน ramdisk ผลการดำเนินงานเป็นที่เลวร้ายมาก ...
membersound

1
ใน Gzip: ไม่ใช่ดิสก์ที่ช้า ใช่ไบต์เป็นตัวเลือก: ขึ้นบรรทัดใหม่, คอมม่า, แท็บ, เครื่องหมายอัฒภาคทั้งหมดสามารถจัดการเป็นไบต์และจะเร็วกว่าสตริงมาก ไบต์เป็น UTF-8 ถึง UTF-16 ถ่านไปยัง String ถึง UTF-8 ถึงไบต์
Joop Eggen

1
เพียงแมปส่วนต่าง ๆ ของไฟล์เมื่อเวลาผ่านไป เมื่อคุณถึงขีด จำกัด เพียงแค่สร้างใหม่MappedByteBufferจากตำแหน่งที่รู้จักกันดีล่าสุด ( FileChannel.mapใช้เวลานาน)
Joachim Sauer

1
ใน 2019 new RandomAccessFile(…).getChannel()ไม่มีความจำเป็นต้องใช้ FileChannel.open(…)ใช้เพียงแค่
Holger

0

คุณสามารถลองสิ่งนี้:

try (BufferedWriter writer = new BufferedWriter(new FileWriter(targetFile), 1024 * 1024 * 64)) {
  try (BufferedReader br = new BufferedReader(new FileReader(sourceFile), 1024 * 1024 * 64)) {

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

มันจะเร็วขึ้นไหม ลองนี้:

final char[] cbuf = new char[1024 * 1024 * 128];

try (Writer writer = new FileWriter(targetFile)) {
  try (Reader br = new FileReader(sourceFile)) {
    int cnt = 0;
    while ((cnt = br.read(cbuf)) > 0) {
      // add your code to process/split the buffer into lines.
      writer.write(cbuf, 0, cnt);
    }
  }
}

วิธีนี้จะช่วยให้คุณประหยัดได้สามหรือสี่นาที

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


ในตัวอย่างสุดท้ายของคุณ: ฉันจะประเมินcbufเนื้อหาและเขียนเฉพาะบางส่วนได้อย่างไร และฉันต้องรีเซ็ตบัฟเฟอร์อีกครั้งหรือไม่ (ว่าฉันจะได้รู้ว่าบัฟเฟอร์เต็ม?)
membersound

0

ขอบคุณคำแนะนำทั้งหมดของคุณสิ่งที่ฉันได้รับเร็วที่สุดคือการแลกเปลี่ยนนักเขียนด้วยBufferedOutputStreamซึ่งทำให้ดีขึ้นประมาณ 25%:

   try (BufferedReader reader = Files.newBufferedReader(Paths.get("sample.csv"))) {
        try (BufferedOutputStream writer = new BufferedOutputStream(Files.newOutputStream(Paths.get("target.csv")), 1024 * 16)) {
            reader.lines().parallel()
                    .filter(line -> StringUtils.isNotBlank(line)) //bit more complex in real world
                    .forEach(line -> {
                        writer.write((line + "\n").getBytes());
                    });
        }
    }

ยังคงBufferedReaderทำงานได้ดีกว่าBufferedInputStreamในกรณีของฉัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.