วิธีที่เร็วที่สุดในการอ่านไฟล์ข้อความแบบบรรทัดต่อบรรทัดคืออะไร


319

ฉันต้องการอ่านไฟล์ข้อความทีละบรรทัด ฉันต้องการทราบว่าฉันทำอย่างมีประสิทธิภาพมากที่สุดเท่าที่จะทำได้ภายในขอบเขต. NET C # ของสิ่งต่าง ๆ

นี่คือสิ่งที่ฉันพยายาม:

var filestream = new System.IO.FileStream(textFilePath,
                                          System.IO.FileMode.Open,
                                          System.IO.FileAccess.Read,
                                          System.IO.FileShare.ReadWrite);
var file = new System.IO.StreamReader(filestream, System.Text.Encoding.UTF8, true, 128);

while ((lineOfText = file.ReadLine()) != null)
{
    //Do something with the lineOfText
}

7
โดยFastestคุณหมายถึงจากประสิทธิภาพหรือมุมมองการพัฒนา
sll

1
นี่จะล็อคไฟล์ในช่วงเวลาของวิธีการ คุณสามารถใช้ File.ReadAllLines เป็นอาร์เรย์จากนั้นประมวลผลอาร์เรย์
Kell

17
BTW ใส่filestream = new FileStreamในusing()คำสั่งเพื่อหลีกเลี่ยงปัญหาที่น่ารำคาญเป็นไปได้ที่มีการจัดการไฟล์ล็อก
SLL

เกี่ยวกับการปิดล้อม FileStream ใช้คำสั่ง () ดู StackOverflow เกี่ยวกับวิธีการที่แนะนำ: StackOverflow โดยใช้คำสั่งสตรีมไฟล์ filestream
deegee

ฉันคิดว่า ReadToEnd () เร็วขึ้น
Dan Gifford

คำตอบ:


315

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

ใช้ StreamReader.ReadLine

นี่เป็นวิธีการของคุณ ด้วยเหตุผลบางอย่างคุณตั้งค่าขนาดบัฟเฟอร์เป็นค่าที่น้อยที่สุด (128) การเพิ่มสิ่งนี้จะเป็นการเพิ่มประสิทธิภาพโดยทั่วไป ขนาดเริ่มต้นคือ 1,024 และตัวเลือกที่ดีอื่น ๆ คือ 512 (ขนาดเซกเตอร์ใน Windows) หรือ 4,096 (ขนาดคลัสเตอร์ใน NTFS) คุณจะต้องเรียกใช้เกณฑ์มาตรฐานเพื่อกำหนดขนาดบัฟเฟอร์ที่เหมาะสมที่สุด บัฟเฟอร์ที่ใหญ่กว่าคือ - ถ้าไม่เร็วกว่า - อย่างน้อยไม่ช้ากว่าบัฟเฟอร์ที่เล็กกว่า

const Int32 BufferSize = 128;
using (var fileStream = File.OpenRead(fileName))
  using (var streamReader = new StreamReader(fileStream, Encoding.UTF8, true, BufferSize)) {
    String line;
    while ((line = streamReader.ReadLine()) != null)
      // Process line
  }

FileStreamคอนสตรัคช่วยให้คุณระบุFileOptions FileOptions.SequentialScanตัวอย่างเช่นถ้าคุณกำลังอ่านไฟล์ขนาดใหญ่ตามลำดับตั้งแต่ต้นจนจบคุณอาจได้รับประโยชน์จาก การเปรียบเทียบเป็นสิ่งที่ดีที่สุดที่คุณสามารถทำได้

ใช้ไฟล์. ReadLines

นี่เป็นเหมือนโซลูชันของคุณเองยกเว้นว่าจะมีการใช้งานโดยใช้StreamReaderขนาดบัฟเฟอร์คงที่ 1,024 ในคอมพิวเตอร์ของฉันผลลัพธ์นี้มีประสิทธิภาพที่ดีขึ้นเล็กน้อยเมื่อเทียบกับรหัสของคุณด้วยขนาดบัฟเฟอร์ 128 อย่างไรก็ตามคุณสามารถเพิ่มประสิทธิภาพการทำงานเดียวกันโดยใช้ขนาดบัฟเฟอร์ที่ใหญ่ขึ้น วิธีนี้ถูกนำมาใช้โดยใช้บล็อกตัววนซ้ำและไม่ใช้หน่วยความจำสำหรับทุกบรรทัด

var lines = File.ReadLines(fileName);
foreach (var line in lines)
  // Process line

ใช้ File.ReadAllLines

นี่เป็นวิธีที่เหมือนกับวิธีก่อนหน้านี้ยกเว้นว่าวิธีนี้จะเพิ่มรายการของสตริงที่ใช้สร้างอาร์เรย์ที่ส่งคืนของบรรทัดดังนั้นความต้องการหน่วยความจำจึงสูงขึ้น อย่างไรก็ตามมันจะส่งคืนString[]และไม่ใช่การIEnumerable<String>อนุญาตให้คุณเข้าถึงบรรทัดแบบสุ่ม

var lines = File.ReadAllLines(fileName);
for (var i = 0; i < lines.Length; i += 1) {
  var line = lines[i];
  // Process line
}

ใช้ String.Split

วิธีนี้ช้ากว่าปกติมากอย่างน้อยในไฟล์ขนาดใหญ่ (ทดสอบในไฟล์ 511 KB) อาจเป็นเพราะวิธีString.Splitการใช้งาน นอกจากนี้ยังจัดสรรอาร์เรย์สำหรับทุกบรรทัดที่เพิ่มหน่วยความจำที่ต้องการเปรียบเทียบกับโซลูชันของคุณ

using (var streamReader = File.OpenText(fileName)) {
  var lines = streamReader.ReadToEnd().Split("\r\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
  foreach (var line in lines)
    // Process line
}

ข้อเสนอแนะของฉันคือการใช้File.ReadLinesเพราะมันสะอาดและมีประสิทธิภาพ หากคุณต้องการตัวเลือกการแชร์พิเศษ (ตัวอย่างเช่นคุณใช้FileShare.ReadWrite) คุณสามารถใช้รหัสของคุณเอง แต่คุณควรเพิ่มขนาดบัฟเฟอร์


1
ขอบคุณสำหรับสิ่งนี้ - การรวมพารามิเตอร์ขนาดบัฟเฟอร์ของคุณใน Constructor ของ StreamReader นั้นมีประโยชน์จริงๆ ฉันสตรีมจาก S3 API ของ Amazon และการใช้ขนาดบัฟเฟอร์ที่ตรงกันจะช่วยเพิ่มความเร็วในการใช้งานร่วมกับ ReadLine ()
Richard K.

ฉันไม่เข้าใจ ตามทฤษฎีแล้วเวลาส่วนใหญ่ที่ใช้ในการอ่านไฟล์จะเป็นเวลาค้นหาบนดิสก์และค่าโสหุ้ยของการจัดการกระแสข้อมูลเช่นเดียวกับสิ่งที่คุณทำกับไฟล์ ReadLines ในทางกลับกันไฟล์ควรอ่านทุกอย่างของไฟล์ลงในหน่วยความจำในครั้งเดียว ประสิทธิภาพการทำงานแย่ลงได้อย่างไร?
h9uest

2
ฉันไม่สามารถพูดเกี่ยวกับประสิทธิภาพความเร็วได้ แต่สิ่งหนึ่งที่แน่นอนคือมันแย่กว่าการใช้หน่วยความจำ หากคุณต้องจัดการกับไฟล์ที่มีขนาดใหญ่มาก (เช่น GB) สิ่งนี้สำคัญมาก มากขึ้นถ้ามันหมายความว่ามันต้องสลับหน่วยความจำ ในด้านความเร็วคุณสามารถเพิ่มที่ ReadAllLine จำเป็นต้องอ่านทุกบรรทัดก่อนส่งคืนการประมวลผลการหน่วงเวลาผลลัพธ์ ในบางสถานการณ์ความประทับใจในเรื่องความเร็วนั้นสำคัญกว่าความเร็วที่แท้จริง
bkqc

หากคุณอ่านสตรีมเป็นอาร์เรย์ไบต์มันจะอ่านไฟล์ได้เร็วขึ้น 20% ~ 80% (จากการทดสอบที่ฉันทำ) สิ่งที่คุณต้องการคือรับอาร์เรย์ไบต์และแปลงเป็นสตริง นั่นเป็นวิธีที่ฉันทำ: สำหรับการอ่านใช้สตรีมอ่าน() คุณสามารถสร้างวงเพื่อให้อ่านเป็นชิ้น ๆ หลังจากผนวกเนื้อหาทั้งหมดลงในอาร์เรย์ไบต์ (ใช้System.Buffer.BlockCopy ) คุณจะต้องแปลงไบต์เป็นสตริง: Encoding.Default.GetString (byteContent, 0, byteContent.Length - 1) แยก (สตริงใหม่ [ ] {"\ r \ n", "\ r", "\ n"}, StringSplitOptions.None);
Kim Lage

200

หากคุณใช้. NET 4 เพียงใช้File.ReadLinesสิ่งที่ทำเพื่อคุณ ฉันสงสัยว่ามันเป็นมากเช่นเดียวกับคุณ แต่มันยังอาจจะใช้FileOptions.SequentialScanและกันชนขนาดใหญ่ (128 ดูเหมือนว่ามีขนาดเล็กมาก)


ข้อดีอีกอย่างของReadLines()มันคือขี้เกียจทำงานได้ดีกับ LINQ
stt106

35

ในขณะที่File.ReadAllLines()เป็นหนึ่งในวิธีที่ง่ายที่สุดในการอ่านไฟล์ แต่ก็เป็นหนึ่งในวิธีที่ช้าที่สุด

หากคุณเพียงแค่ต้องการอ่านบรรทัดในไฟล์โดยไม่ต้องทำอะไรมากตามมาตรฐานเหล่านี้วิธีที่เร็วที่สุดในการอ่านไฟล์คือวิธีการแบบเก่าของ:

using (StreamReader sr = File.OpenText(fileName))
{
        string s = String.Empty;
        while ((s = sr.ReadLine()) != null)
        {
               //do minimal amount of work here
        }
}

อย่างไรก็ตามหากคุณต้องทำมากกับแต่ละบรรทัดบทความนี้สรุปว่าวิธีที่ดีที่สุดคือต่อไปนี้ (และมันจะเร็วกว่าในการจัดสรรสตริงล่วงหน้า [] ถ้าคุณรู้ว่าคุณจะอ่านกี่บรรทัด):

AllLines = new string[MAX]; //only allocate memory here

using (StreamReader sr = File.OpenText(fileName))
{
        int x = 0;
        while (!sr.EndOfStream)
        {
               AllLines[x] = sr.ReadLine();
               x += 1;
        }
} //Finished. Close the file

//Now parallel process each line in the file
Parallel.For(0, AllLines.Length, x =>
{
    DoYourStuff(AllLines[x]); //do your work here
});

13

ใช้รหัสต่อไปนี้:

foreach (string line in File.ReadAllLines(fileName))

นี่คือความแตกต่างอย่างมากในประสิทธิภาพการอ่าน

มันมาพร้อมกับต้นทุนการใช้หน่วยความจำ แต่คุ้มค่าโดยสิ้นเชิง


ฉันต้องการFile.ReadLines (คลิกฉัน)มากกว่าFile.ReadAllLines
newbieguy

5

มีหัวข้อที่ดีเกี่ยวกับเรื่องนี้ในคำถาม Stack Overflow คำถามคือ 'การคืนผลตอบแทน' ช้ากว่าการ "กลับไปโรงเรียนเก่า" หรือไม่ .

มันบอกว่า:

ReadAllLines โหลดบรรทัดทั้งหมดลงในหน่วยความจำและส่งคืนสตริง [] ทุกอย่างดีและดีถ้าไฟล์มีขนาดเล็ก หากไฟล์มีขนาดใหญ่กว่าจะพอดีกับหน่วยความจำคุณจะมีหน่วยความจำไม่เพียงพอ

ReadLines ในทางกลับกันใช้การคืนผลตอบแทนเพื่อกลับหนึ่งบรรทัดในแต่ละครั้ง ด้วยคุณสามารถอ่านไฟล์ขนาดใดก็ได้ มันไม่ได้โหลดไฟล์ทั้งหมดลงในหน่วยความจำ

สมมติว่าคุณต้องการค้นหาบรรทัดแรกที่มีคำว่า "foo" แล้วออก การใช้ ReadAllLines คุณจะต้องอ่านไฟล์ทั้งหมดในหน่วยความจำแม้ว่า "foo" จะเกิดขึ้นในบรรทัดแรก ด้วย ReadLines คุณจะอ่านเพียงหนึ่งบรรทัด อันไหนจะเร็วกว่ากัน?


4

หากขนาดไฟล์ไม่ใหญ่แสดงว่าเป็นการอ่านไฟล์ทั้งหมดที่เร็วกว่าและแยกไฟล์ในภายหลัง

var filestreams = sr.ReadToEnd().Split(Environment.NewLine, 
                              StringSplitOptions.RemoveEmptyEntries);

6
File.ReadAllLines()
jgauffin

@ jgauffin ฉันไม่ทราบว่าจะใช้งานไฟล์ได้อย่างไร ReadAlllines () แต่ฉันคิดว่ามันมีบัฟเฟอร์ จำกัด และ fileReadtoEnd บัฟเฟอร์ควรมากขึ้นดังนั้นจำนวนการเข้าถึงไฟล์จะลดลงด้วยวิธีนี้และทำสตริงแยกจาก ขนาดไฟล์กรณีไม่ใหญ่เร็วกว่าการเข้าถึงไฟล์หลายไฟล์
Saeed Amiri

ฉันสงสัยว่าFile.ReadAllLinesมีขนาดบัฟเฟอร์คงที่เนื่องจากรู้จักขนาดไฟล์
jgauffin

1
@jgauffin: ใน. NET 4.0 File.ReadAllLinesจะสร้างรายการและเพิ่มลงในรายการนี้ในการวนซ้ำโดยใช้StreamReader.ReadLine(ด้วยการจัดสรรที่เป็นไปได้ของอาร์เรย์ที่ซ่อนอยู่) วิธีนี้ใช้ขนาดบัฟเฟอร์เริ่มต้นที่ 1024 StreamReader.ReadToEndหลีกเลี่ยงการแยกบรรทัดส่วนและขนาดบัฟเฟอร์สามารถตั้งค่าในตัวสร้างหากต้องการ
Martin Liversage

มันจะมีประโยชน์ในการกำหนด "ใหญ่" เกี่ยวกับขนาดไฟล์
พอล

2

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


1
File.ReadAllLinesน่าจะเป็นทางเลือกที่ดีกว่าแล้ว
jgauffin

2

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

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