Позиция FileStream отключена после вызова ReadLine () из C # - PullRequest
3 голосов
/ 28 мая 2010

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

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

streamReader.ReadLine();

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

Например, повторный вызов 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)))
{...}

Есть предложения?

Ответы [ 4 ]

4 голосов
/ 28 мая 2010

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

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

4 голосов
/ 28 мая 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.

уже встроен механизм буферизации .
2 голосов
/ 28 мая 2010

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

1 голос
/ 06 ноября 2015

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

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

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

...