Позиция FileStream отключена после вызова ReadLine() из С#

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

Проблема в том, что после первого вызова

streamReader.ReadLine();

свойство streamReader.BaseStream.Position устанавливается в конец файла! Теперь я предполагаю, что за кулисами выполняется некоторое кэширование, но я ожидал, что это свойство будет отражать количество байтов, которые я использовал из этого файла. И да, в файле больше одной строки :-)

Например, повторный вызов ReadLine() (естественно) вернет следующую строку в файле, которая не начинается с позиции, ранее указанной streamReader.BaseStream.Position.

Как мне найти фактическое место, где заканчивается 1-я строка, чтобы я мог вернуться туда позже?

Я могу думать только о ручном учете, добавляя длины строк, возвращаемых ReadLine(), но даже здесь есть пара предостережений:

  • ReadLine() удаляет символы новой строки, которые могут иметь переменную длину (это '\n'? Это "\r\n"? И т. д.)
  • Я не уверен, что это будет работать нормально с символами переменной длины.

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

Если это поможет, я открываю свой файл следующим образом:

using (var reader = new StreamReader(
        new FileStream(
                       m_path, 
                       FileMode.Open, 
                       FileAccess.Read, 
                       FileShare.ReadWrite)))
{...}

Какие-либо предложения?


person Cristian Diaconescu    schedule 28.05.2010    source источник


Ответы (4)


Если вам нужно прочитать строки и вернуться к предыдущим фрагментам, почему бы не сохранить прочитанные строки в списке? Это должно быть достаточно легко.

Вы не должны зависеть от вычисления длины в байтах на основе длины строки - по причинам, которые вы упоминаете сами: многобайтовые символы, символы новой строки и т.д.

person driis    schedule 28.05.2010

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

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

Бухгалтерия путем подсчета количества байтов, прочитанных из каждого вызова ReadLine(), будет работать для большинства текстовых файлов. Однако я встречал случаи, когда в текстовый файл вмешивался управляющий символ, непечатаемый. Вычисленное количество байтов неверно, из-за чего моя программа не смогла найти правильное местоположение после этого.

Мое окончательное решение состояло в том, чтобы реализовать линейный ридер самостоятельно. До сих пор это работало хорошо. Это должно дать некоторое представление о том, как это выглядит:

using (FileStream fs = new FileStream(filePath, FileMode.Open))
{
    int ch;
    int currentLine = 1, offset = 0;

    while ((ch = fs.ReadByte()) >= 0)
    {
        offset++;

        // This covers all cases: \r\n and only \n (for UNIX files)
        if (ch == 10)
        {
            currentLine++;

            // ... do sth such as log current offset with line number
        }
    }
}

И чтобы вернуться к зарегистрированному смещению:

using (FileStream fs = new FileStream(filePath, FileMode.Open))
{
    fs.Seek(yourOffset, SeekOrigin.Begin);
    TextReader tr = new StreamReader(fs);

    string line = tr.ReadLine();
}

Также обратите внимание, что механизм буферизации уже встроен в FileStream.

person Gant    schedule 28.05.2010
comment
Есть проблемы. Работа с BOM — это большая проблема. - person Hans Passant; 28.05.2010

StreamReader не предназначен для такого использования, поэтому, если это то, что вам нужно, я подозреваю, что вам придется написать свою собственную оболочку для FileStream.

person JSBձոգչ    schedule 28.05.2010

Проблема с принятым ответом заключается в том, что если ReadLine() сталкивается с исключением, скажем, из-за того, что структура ведения журнала временно блокирует файл прямо при использовании ReadLine(), тогда эта строка не будет «сохранена» в списке, потому что она никогда не возвращалась линия. Если вы поймаете это исключение, вы не сможете повторить ReadLine() во второй раз, потому что внутреннее состояние и буфер StreamReaders испорчены по сравнению с последним ReadLine(), и вы получите только часть возвращаемой строки, и вы не можете игнорировать эту сломанную строку и искать вернуться к началу этого, как узнал OP.

Если вы хотите добраться до истинного местоположения, доступного для поиска, вам нужно использовать отражение, чтобы добраться до частных переменных StreamReaders, которые позволяют вам вычислить его позицию внутри собственного буфера. Решение Грейнджера, показанное здесь: StreamReader и поиск, должно работать. Или сделайте то, что сделали другие ответы на другие связанные вопросы: создайте свой собственный StreamReader, который показывает истинное местоположение для поиска (этот ответ в этой ссылке: Отслеживание положения строки streamreader). Это единственные два варианта, с которыми я столкнулся при работе с StreamReader и поиском, который по какой-то причине решил полностью исключить возможность поиска практически в любой ситуации.

edit: я использовал решение Грейнджер, и оно работает. Просто убедитесь, что вы идете в этом порядке: GetActualPosition(), затем установите BaseStream.Position в эту позицию, затем убедитесь, что вы вызываете DiscardBufferedData(), и, наконец, вы можете вызвать ReadLine(), и вы получите полную строку, начиная с позиции дано в методе.

person Quantic    schedule 06.11.2015