Який найшвидший спосіб прочитати текстовий файл рядок?


318

Я хочу прочитати текстовий файл за рядком. Мені хотілося знати, чи роблю це максимально ефективно в межах речей .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 в масив, а потім обробити масив.
Келл

17
BTW, додайте filestream = new FileStreamдо using()заяви, щоб уникнути можливих прикрих проблем із замкненою файловою ручкою
sll

Що стосується того, що FileStream використовує оператор (), див. StackOverflow щодо рекомендованого методу: StackOverflow за допомогою оператора filere streamreader
deegee

Я думаю, що ReadToEnd () швидше.
Ден Гіффорд

Відповіді:


315

Щоб знайти найшвидший спосіб прочитати файл за рядком, вам доведеться зробити порівняльний аналіз. Я зробив кілька невеликих тестів на своєму комп’ютері, але ви не можете очікувати, що мої результати стосуються вашого середовища.

Використання StreamReader.ReadLine

Це в основному ваш метод. Чомусь ви встановлюєте розмір буфера на найменше можливе значення (128). Підвищення цього в цілому збільшить ефективність роботи. За замовчуванням розмір 1,024, а інші хороші варіанти - 512 (розмір сектору в Windows) або 4 066 (розмір кластера в 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. Знову ж таки, тестування - це найкраще, що ви можете зробити.

Використання File.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 КБ файл), ймовірно, завдяки тому, як 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
Дякую за це - ваше включення параметра розміру буфера в конструктор StreamReader було дуже корисним. Я передаю потоки з API S3 Amazon, і використання відповідного розміру буфера значно прискорює роботу спільно з ReadLine ().
Річард К.

Я не розумію. Теоретично переважна більшість часу, витраченого на читання файлу, - це час пошуку на диску та накладні витрати на обробку потоків, як те, що ви робите з File.ReadLines. File.ReadLines, з іншого боку, повинен прочитати весь файл у пам'ять за один раз. Як це може бути гіршим у продуктивності?
h9uest

2
Я не можу сказати про швидкість роботи, але одне одно певне: це значно гірше за витратою пам’яті. Якщо вам доведеться обробляти дуже великі файли (наприклад, ГБ), це дуже важливо. Навіть більше, якщо це означає, що він повинен міняти пам'ять. З боку швидкості ви можете додати, що ReadAllLine повинен прочитати ВСІ рядки перед тим, як повернути результат затримки обробки. У деяких сценаріях ВПЕЧЕННЯ швидкості важливіше, ніж швидкість в режимі сировини.
bkqc

Якщо ви читаєте потік у вигляді байтових масивів, він зчитує файл на 20% ~ 80% швидше (з тестів, які я робив). Вам потрібно отримати байтовий масив і перетворити його в рядок. Ось так я і зробив: для читання використовуйте stream.Read () Ви можете зробити цикл, щоб він читав шматки. Після додавання всього вмісту до масиву байтів (використовуйте System.Buffer.BlockCopy ) вам потрібно буде перетворити байти в рядок: Encoding.Default.GetString (byteContent, 0, byteContent.Length - 1) .Split (новий рядок [ ] {"\ r \ n", "\ r", "\ n"}, StringSplitOptions.None);
Кім Лаге

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

Про це є гарна тема у питанні переповнення стека. Чи повернення доходу повільніше, ніж повернення "старої школи"? .

Він говорить:

ReadAllLines завантажує всі рядки в пам'ять і повертає рядок []. Все добре і добре, якщо файл невеликий. Якщо файл більше, ніж вміститься в пам'яті, у вас не вистачить пам’яті.

ReadLines, з іншого боку, використовує віддачу прибутковості для повернення по одному рядку. З його допомогою ви можете прочитати файл будь-якого розміру. Він не завантажує весь файл у пам'ять.

Скажіть, що ви хочете знайти перший рядок, який містить слово "foo", а потім вийти. Використовуючи ReadAllLines, вам доведеться прочитати весь файл у пам'яті, навіть якщо в першому рядку виникає "foo". З ReadLines ви читаєте лише один рядок. Який би був швидший?


4

Якщо розмір файлу не великий, то швидше прочитати весь файл і розділити його згодом

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

6
File.ReadAllLines()
jgauffin

@jgauffin Я не знаю за реалізацією file.ReadAlllines (), але я думаю, що він має обмежений буфер, а буфер fileReadtoEnd повинен бути більшим, тому кількість доступу до файлу зменшиться таким чином, і робити string.Split розмір файлу справи не великий, швидше, ніж багаторазовий доступ до файлу.
Saeed Amiri

Я сумніваюся, що вони File.ReadAllLinesмають фіксований розмір буфера, оскільки розмір файлу відомий.
jgauffin

1
@jgauffin: In .NET 4.0 File.ReadAllLinesстворює список і додає до цього списку в циклі, використовуючи StreamReader.ReadLine(з можливим перерозподілом базового масиву). Цей метод використовує розмір буфера за замовчуванням 1024. StreamReader.ReadToEndУникає розбору частини розбору рядків, а розмір буфера може бути встановлений у конструкторі за бажанням.
Мартін Ліверсайз

Було б корисно визначити "BIG" щодо розміру файлу.
Пол

2

Якщо у вас є достатня кількість пам’яті, я знайшов певні показники підвищення продуктивності, прочитавши весь файл у потоці пам’яті , а потім відкрив зчитувач потоку на ньому, щоб прочитати рядки. Поки ви дійсно плануєте прочитати весь файл у будь-якому випадку, це може принести певні вдосконалення.


1
File.ReadAllLinesтоді, здається, кращий вибір.
jgauffin

2

Ви не можете отримати швидше, якщо ви хочете використовувати існуючий API для читання рядків. Але читання великих фрагментів і вручну знайти кожен новий рядок у буфері для читання, можливо, буде швидше.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.