Какой самый быстрый способ прочитать текстовый файл построчно?

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 в массиве, а затем обрабатывать массив.
Келл
17
Кстати, приложите filestream = new FileStreamв using()заявлении, чтобы избежать возможных раздражающих проблем с заблокированным дескриптором файла
sll
Относительно того, что FileStream использует оператор (), см. StackOverflow относительно рекомендуемого метода: StackOverflow с использованием оператора потокового чтения потока операторов
deegee
Я думаю, что ReadToEnd () быстрее.
Дэн Гиффорд

Ответы:

315

Чтобы найти самый быстрый способ чтения файла построчно, вам нужно сделать несколько тестов. Я провел несколько небольших тестов на своем компьютере, но вы не можете ожидать, что мои результаты применимы к вашей среде.

Использование StreamReader.ReadLine

Это в основном ваш метод. По какой-то причине вы устанавливаете размер буфера наименьшее возможное значение (128). Увеличение этого в целом увеличит производительность. Размер по умолчанию - 1024, а другие хорошие варианты - 512 (размер сектора в Windows) или 4096 (размер кластера в 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с использованием фиксированного размера буфера 1024. На моем компьютере это приводит к несколько лучшей производительности по сравнению с вашим кодом с размером буфера 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 было действительно полезным. Я использую потоковый интерфейс Amazon S3 API, и использование подходящего размера буфера значительно ускоряет работу с 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
});
Бесплатный кодер 24
источник
13

Используйте следующий код:

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

Это была ОГРОМНАЯ разница в производительности чтения.

Это происходит за счет потребления памяти, но оно того стоит!

user2671536
источник
я бы предпочел File.ReadLines (нажмите меня), чемFile.ReadAllLines
newbieguy
5

Есть хорошая тема на этот счет в вопросе переполнения стека. Является ли «возврат доходности» более медленным, чем возврат «старой школы»? ,

Это говорит:

ReadAllLines загружает все строки в память и возвращает строку []. Все хорошо, если файл небольшой. Если файл больше, чем умещается в памяти, вам не хватит памяти.

ReadLines, с другой стороны, использует yield return для возврата по одной строке за раз. С его помощью вы можете прочитать файл любого размера. Он не загружает весь файл в память.

Скажем, вы хотели найти первую строку, содержащую слово «foo», а затем выйти. Используя ReadAllLines, вам нужно будет прочитать весь файл в память, даже если в первой строке указано «foo». С ReadLines вы читаете только одну строку. Какой из них будет быстрее?

Марсель Джеймс
источник
4

Если размер файла не велик, то быстрее прочитать весь файл и затем разделить его

var filestreams = sr.ReadToEnd().Split(Environment.NewLine, 
                              StringSplitOptions.RemoveEmptyEntries);
Саид Амири
источник
6
File.ReadAllLines()
jgauffin
@jgauffin Я не знаю за реализацией file.ReadAlllines (), но я думаю, что он имеет ограниченный буфер, и буфер fileReadtoEnd должен быть больше, так что количество доступа к файлу будет уменьшено таким образом, и выполнение string.Split в если размер файла не большой, то он быстрее, чем множественный доступ к файлу.
Саид Амири
Я сомневаюсь, что File.ReadAllLinesесть фиксированный размер буфера, так как размер файла известен.
JGauffin
1
@jgauffin: В .NET 4.0 File.ReadAllLinesсоздает список и добавляет его в этот цикл с использованием StreamReader.ReadLine(с возможным перераспределением базового массива). Этот метод использует размер буфера по умолчанию, равный 1024. Он StreamReader.ReadToEndпозволяет избежать синтаксического анализа строки, и размер буфера может быть установлен в конструкторе при желании.
Мартин Ливерсэйдж
Было бы полезно определить «БОЛЬШОЙ» в отношении размера файла.
Пол
2

Если у вас достаточно памяти, я нашел некоторый прирост производительности, прочитав весь файл в поток памяти , а затем открыв для этого читатель потока, чтобы прочитать строки. Если вы все равно планируете прочитать весь файл, это может привести к некоторым улучшениям.

Kibbee
источник
1
File.ReadAllLinesкажется, лучший выбор тогда.
jgauffin
2

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

jgauffin
источник