Подтвердить что ты не робот

Действительно ли StreamReader.Readline() - самый быстрый способ подсчета строк в файле?

Присматриваясь некоторое время, я нашел довольно много дискуссий о том, как определить количество строк в файле.

Например, эти три:
С# как подсчитывать строки в текстовом файле
Определить количество строк в текстовом файле
Как быстро считать строки?

Итак, я пошел вперед и в итоге использовал то, что кажется самым эффективным (по крайней мере, с точки зрения памяти?) методом, который я мог найти:

private static int countFileLines(string filePath)
{
    using (StreamReader r = new StreamReader(filePath))
    {
        int i = 0;
        while (r.ReadLine() != null) 
        { 
            i++; 
        }
        return i;
    }
}

Но это берет навсегда, когда сами строки из файла очень длинны. Действительно ли это не более быстрое решение?

Я пытаюсь использовать StreamReader.Read() или StreamReader.Peek(), но я не могу (или не знаю, как сделать), чтобы любой из них перешел к следующей строке, как только "stuff" ( chars? text?).

Любые идеи, пожалуйста?


ЗАКЛЮЧЕНИЕ/РЕЗУЛЬТАТЫ (после выполнения некоторых тестов на основе предоставленных ответов):

Я тестировал 5 методов ниже на двух разных файлах, и я получил согласованные результаты, которые, как представляется, указывают на то, что простой старый StreamReader.ReadLine() по-прежнему остается одним из самых быстрых способов... Честно говоря, я озадачен после всех комментариев и обсуждение в ответах.

Файл # 1:
Размер: 3,631 КБ
Линии: 56,870

Результаты в секундах для файла №1:
0.02 → Метод ReadLine.
0,04 → Метод чтения.
0.29 → Метод ReadByte.
0.25 → Метод Readlines.Count.
0.04 → Метод ReadWithBufferSize.

Файл # 2:
Размер: 14,499 КБ
Линии: 213 424

Результаты в секундах для файла №1:
0.08 → Метод ReadLine.
0.19 → Метод чтения.
1.15 → Метод ReadByte.
1.02 → Метод Readlines.Count.
0.08 → Метод ReadWithBufferSize.

Вот 5 методов, которые я тестировал на основе всех полученных мной отзывов:

private static int countWithReadLine(string filePath)
{
    using (StreamReader r = new StreamReader(filePath))
    {
    int i = 0;
    while (r.ReadLine() != null)
    {
        i++;
    }
    return i;
    }
}

private static int countWithRead(string filePath)
{
    using (StreamReader _reader = new StreamReader(filePath))
    {
    int c = 0, count = 0;
    while ((c = _reader.Read()) != -1)
    {
        if (c == 10)
        {
        count++;
        }
    }
    return count;
    }            
}

private static int countWithReadByte(string filePath)
{
    using (Stream s = new FileStream(filePath, FileMode.Open))
    {
    int i = 0;
    int b;

    b = s.ReadByte();
    while (b >= 0)
    {
        if (b == 10)
        {
        i++;
        }
        b = s.ReadByte();
    }
    return i;
    }
}

private static int countWithReadLinesCount(string filePath)
{
    return File.ReadLines(filePath).Count();
}

private static int countWithReadAndBufferSize(string filePath)
{
    int bufferSize = 512;

    using (Stream s = new FileStream(filePath, FileMode.Open))
    {
    int i = 0;
    byte[] b = new byte[bufferSize];
    int n = 0;

    n = s.Read(b, 0, bufferSize);
    while (n > 0)
    {
        i += countByteLines(b, n);
        n = s.Read(b, 0, bufferSize);
    }
    return i;
    }
}

private static int countByteLines(byte[] b, int n)
{
    int i = 0;
    for (int j = 0; j < n; j++)
    {
    if (b[j] == 10)
    {
        i++;
    }
    }

    return i;
}
4b9b3361

Ответ 1

Нет, это не так. Точка - это материализует строки, которые не нужны.

В COUNT вам гораздо лучше проигнорировать "строку" и перейти к "строке".

a LINE - это строки байтов, заканчивающиеся на \r\n (13, 10 - CR LF) или другой маркер.

Просто запустите байты в буферизованном потоке, подсчитав количество появлений маркера конца строки.

Ответ 2

Лучший способ узнать, как это сделать быстро, - подумать о самом быстром способе сделать это, не используя C/С++.

В сборке есть операция уровня ЦП, которая сканирует память для символа, поэтому в сборке вы сделаете следующее

  • Прочитайте большую часть (или все) файла в памяти
  • Выполнить команду SCASB
  • Повторите при необходимости

Итак, в С# вы хотите, чтобы компилятор максимально приблизился к этому.

Ответ 3

Я попробовал несколько методов и протестировал их производительность:

Тот, который читает один байт, примерно на 50% медленнее других. Остальные методы возвращаются примерно столько же времени. Вы можете попробовать создать потоки и сделать это асинхронно, поэтому пока вы ждете чтения, вы можете начать обработку предыдущего чтения. Это звучит как головная боль для меня.

Я бы пошел с одним лайнером: File.ReadLines(filePath).Count(); он выполняет так же, как и другие методы, которые я тестировал.

        private static int countFileLines(string filePath)
        {
            using (StreamReader r = new StreamReader(filePath))
            {
                int i = 0;
                while (r.ReadLine() != null)
                {
                    i++;
                }
                return i;
            }
        }

        private static int countFileLines2(string filePath)
        {
            using (Stream s = new FileStream(filePath, FileMode.Open))
            {
                int i = 0;
                int b;

                b = s.ReadByte();
                while (b >= 0)
                {
                    if (b == 10)
                    {
                        i++;
                    }
                    b = s.ReadByte();
                }
                return i + 1;
            }
        }

        private static int countFileLines3(string filePath)
        {
            using (Stream s = new FileStream(filePath, FileMode.Open))
            {
                int i = 0;
                byte[] b = new byte[bufferSize];
                int n = 0;

                n = s.Read(b, 0, bufferSize);
                while (n > 0)
                {
                    i += countByteLines(b, n);
                    n = s.Read(b, 0, bufferSize);
                }
                return i + 1;
            }
        }

        private static int countByteLines(byte[] b, int n)
        {
            int i = 0;
            for (int j = 0; j < n; j++)
            {
                if (b[j] == 10)
                {
                    i++;
                }
            }

            return i;
        }

        private static int countFileLines4(string filePath)
        {
            return File.ReadLines(filePath).Count();
        }

Ответ 4

public static int CountLines(Stream stm)
{
    StreamReader _reader = new StreamReader(stm);
    int c = 0, count = 0;
    while ((c = _reader.Read()) != -1)
    {
        if (c == '\n')
        {
            count++;
        }
    }
    return count;
}

Ответ 5

Да, чтение таких строк является самым быстрым и простым способом в любом практическом смысле.

Здесь нет ярлыков. Файлы не основаны на линиях, поэтому вам нужно прочитать каждый байт из файла, чтобы определить, сколько строк существует.

Как указывал TomTom, создание строк строго не нужно для подсчета строк, но большая часть времени будет ждать, пока данные будут считаны с диска. Написание гораздо более сложного алгоритма, возможно, сбережет процент времени выполнения, и это значительно увеличит время написания и тестирования кода.

Ответ 6

Существует множество способов чтения файла. Обычно самый быстрый способ - самый простой:

using (StreamReader sr = File.OpenText(fileName))
{
        string s = String.Empty;
        while ((s = sr.ReadLine()) != null)
        {
               //do what you gotta do here
        }
}

Эта страница отлично сравнивает производительность между несколькими различными методами, включая использование BufferedReaders, чтение в объекты StringBuilder и весь массив.

Ответ 7

StreamReader - это не самый быстрый способ чтения файлов в целом из-за небольших накладных расходов от кодирования байтов до символов, поэтому чтение файла в байтовом массиве происходит быстрее.
Результаты, которые я получаю, немного различаются каждый раз из-за кеширования и других процессов, но вот один из результатов, которые я получил (в миллисекундах) с файлом 16 МБ:

75 ReadLines 
82 ReadLine 
22 ReadAllBytes 
23 Read 32K 
21 Read 64K 
27 Read 128K 

Обычно File.ReadLines должен быть немного медленнее, чем цикл StreamReader.ReadLine. File.ReadAllBytes работает медленнее с большими файлами и выкинет из памяти исключение с огромными файлами. Размер буфера по умолчанию для FileStream составляет 4K, но на моей машине 64K казался самым быстрым.

    private static int countWithReadLines(string filePath)
    {
        int count = 0;
        var lines = File.ReadLines(filePath);

        foreach (var line in lines) count++;
        return count;
    }

    private static int countWithReadLine(string filePath)
    {
        int count = 0;
        using (var sr = new StreamReader(filePath))      
            while (sr.ReadLine() != null)
                count++;
        return count;
    }

    private static int countWithFileStream(string filePath, int bufferSize = 1024 * 4)
    {
        using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
        {
            int count = 0;
            byte[] array = new byte[bufferSize];

            while (true)
            {
                int length = fs.Read(array, 0, bufferSize);

                for (int i = 0; i < length; i++)
                    if(array[i] == 10)
                        count++;

                if (length < bufferSize) return count;
            }
        } // end of using
    }

и протестирован с помощью:

var path = "1234567890.txt"; Stopwatch sw; string s = "";
File.WriteAllLines(path, Enumerable.Repeat("1234567890abcd", 1024 * 1024 )); // 16MB (16 bytes per line)

sw = Stopwatch.StartNew(); countWithReadLines(path)   ; sw.Stop(); s += sw.ElapsedMilliseconds + " ReadLines \n";
sw = Stopwatch.StartNew(); countWithReadLine(path)    ; sw.Stop(); s += sw.ElapsedMilliseconds + " ReadLine \n";
sw = Stopwatch.StartNew(); countWithReadAllBytes(path); sw.Stop(); s += sw.ElapsedMilliseconds + " ReadAllBytes \n";

sw = Stopwatch.StartNew(); countWithFileStream(path, 1024 * 32); sw.Stop(); s += sw.ElapsedMilliseconds + " Read 32K \n";
sw = Stopwatch.StartNew(); countWithFileStream(path, 1024 * 64); sw.Stop(); s += sw.ElapsedMilliseconds + " Read 64K \n";
sw = Stopwatch.StartNew(); countWithFileStream(path, 1024 *128); sw.Stop(); s += sw.ElapsedMilliseconds + " Read 128K \n";

MessageBox.Show(s);