Поведение TextReader.Peek и обнаружение конца потока/читателя

Когда я использую средство чтения текста, как лучше всего определить, что я на самом деле нахожусь в конце своих данных? Обычный способ сделать это выглядит следующим образом, например:

    while(reader.Peek() != -1)
    {
       ///do stuff
    }

Однако в документации msdn здесь указано следующее

Целое число, представляющее следующий символ для чтения, или -1, если больше нет доступных символов или средство чтения не поддерживает поиск.

Итак, мой вопрос: как узнать, действительно ли вы находитесь в конце данных читателей или читатель/базовый поток просто не поддерживает поиск, поскольку возвращаемое значение здесь кажется неоднозначным? если, например, у меня есть следующее

    public void Parse(TextReader reader)
    {
         while(reader.Peek() != -1) //am I really at the end
         {
            //do stuff
         }
    }

    Parse(new StreamReader(new NetworkStream(....)));

поскольку сетевой поток не поддерживает поиск.

Или я что-то пропустил?

Изменить:

Просто чтобы уточнить, я могу легко реализовать это, используя более конкретный класс StreamReader, так как я могу проверить EoS. Однако, чтобы сделать вещи более общими, я хотел использовать TextReader, поэтому я не привязан только к StreamReader. Однако семантика Peek кажется немного странной, почему она просто не выбрасывает, если поиск не поддерживается, и с этой целью почему нет свойства EoF для TextReader?


person Colin Bull    schedule 30.11.2012    source источник
comment
Есть ли причина, по которой вам нужно использовать Peek вместо Read?   -  person Douglas    schedule 30.11.2012
comment
это часть конечного автомата, поэтому я могу или не хочу использовать этот байт в текущем состоянии. так что заглянуть - единственный вариант, который у меня есть здесь? без поддержки отдельного стека неиспользуемых байтов.   -  person Colin Bull    schedule 30.11.2012
comment
Как насчет того, чтобы проверить reader.BaseStream.CanSeek?   -  person Douglas    schedule 30.11.2012
comment
Могу я спросить: почему так много людей используют .Peek, а не .EndOfStream, чтобы проверить, достигнут ли конец потока? Есть ли преимущество в использовании .Peek?   -  person igrimpe    schedule 30.11.2012
comment
textreader не предоставляет базовый поток, поскольку он может не иметь базового потока (в случае чтения строк). Я думаю, это то, что действительно мотивирует этот вопрос.   -  person Colin Bull    schedule 30.11.2012
comment
@ColinBull: я имел в виду потоки, поддерживающие EOS. Я часто вижу код, в котором ppl читают из потока и используют .Peek, чтобы определить, достигли ли они конца. Это просто какой-то исторический C&P или за этим стоит идея?   -  person igrimpe    schedule 30.11.2012
comment
@igrimpe - я думаю, что один из сценариев будет в интерактивном консольном приложении, которое сканирует ввод из Console.In. Если вы обернете Console.In в StreamReader, это изменит поведение пользовательского интерфейса, и вы можете запутаться в кодировании, чтобы вернуться к поведению Console.In по умолчанию. В этом случае проще просто проверить на -1. Но, как вы сказали, в большинстве ситуаций следует отдавать предпочтение .EndOfStream. Я подозреваю, что у людей, которые работали с Unix, есть некоторые психологические шрамы.   -  person Paul Williams    schedule 30.07.2014
comment
Если вам интересно, для чего это использовалось, на самом деле это парсер HTML для (поставщика типа F#) [github.com/fsharp/FSharp.Data/blob/HtmlTypeProvider/src/Html/ В итоге я оставил проверку -1, но изменил способ поддержки состояние разбора.   -  person Colin Bull    schedule 01.08.2014


Ответы (4)


Это действительно зависит от того, что вы делаете в разборе.

Обычно я просто Read и смотрю, сколько прочитано. Я бы предложил не читать символ за раз:

char[] buffer = new char[1024 * 16];
int charsRead;
while ((charsRead = read.Read(buffer, 0, buffer.Length)) > 0)
{
    // Process buffer, only as far as charsRead
}
person Jon Skeet    schedule 01.12.2012
comment
Я не понимаю, в чем проблема с Read(), поскольку предоставленные реализации Textreader имеют внутреннюю буферизацию. По крайней мере, так говорит мне моя книга по C# 3.0. Меня раздражает, что я должен сначала выполнить tmp = Read(), чтобы я мог проверить -1, а затем выполнить c = (char) tmp, чтобы получить то, что я хочу, но это кажется безопаснее/чище, чем проверка c = = char.MaxValue() - person Jon Coombs; 30.09.2013
comment
Это может быть буферизовано, да. Хотя мне кажется, что чище читать буфер за раз... зачем полагаться на буферизацию, если она вам действительно не нужна? Если вы явно не включите буферизацию, вы можете легко получить неприятную проблему с производительностью из-за изменения деталей реализации. - person Jon Skeet; 30.09.2013
comment
Хм, написание всего этого дополнительного кода/логики только для повторной реализации того, что уже предоставляет фреймворк, казалось бы, противоречит цели использования фреймворка. (Похоже на возвращение к C++.) Внутри внешнего цикла вам понадобится еще один такой же цикл, верно? for (int i=0; i ‹ charsRead; i++) { char val = (char)buffer[i]; /* process val... */ } Я лучше сосредоточусь на написании логики программы; то есть val = (char) read.Read(); - person Jon Coombs; 02.10.2013
comment
@JCoombs: На самом деле обычно нет - обычно вы можете иметь дело с персонажами в большом количестве. Конечно, это зависит от того, что вы делаете, но мой опыт показывает, что часто бывает так же просто (а иногда и проще) использовать объем Read, как по одному символу за раз Read. (В качестве альтернативы, читайте строку за раз с помощью ReadLine - это часто проще, чем любое из них.) И я бы определенно не просто использовал Read, так как это удаляет информацию о том, действительно ли вы достигли конца потока. Сначала я бы сосредоточился на правильности. - person Jon Skeet; 02.10.2013
comment
Интересный. Обычно я обнаруживаю, что имею дело либо с одной строкой текста, либо с одним символом за раз. Преимущество наличия буфера заключается в том, что он может видеть вперед (например, несколько Peek()), но я не понимаю, как это будет работать, потому что он сломается, когда вы окажетесь ближе к концу буфера, верно? - person Jon Coombs; 02.10.2013
comment
@JCoombs: Ну, если вы читаете строки, просто используйте ReadLine. Часто, когда я не читаю строки, я копирую текст из одного места в другое, так что это вопрос чтения фрагмента, а затем записи его в TextWriter. - person Jon Skeet; 02.10.2013
comment
Правильно, я имею в виду именно ReadLine(). А, я вижу, как хорошо ваш подход сработал бы для копирования бинарных файлов по частям. Я склонен работать с текстовыми файлами. [РЕДАКТИРОВАТЬ: О, извините, вы сказали текст. - person Jon Coombs; 02.10.2013
comment
Я думаю, что Peek лучше, потому что Read продвигает символ вперед в читателе, а это непреднамеренно. - person trinalbadger587; 07.12.2016
comment
@trinalbadger587: Я хочу сказать, что вместо использования Peek и чтения затем вы просто продолжаете читать, пока не закончите - person Jon Skeet; 07.12.2016
comment
@JonSkeet А, хорошо. Я согласен. +1 - person trinalbadger587; 07.12.2016

если вы не ищете конкретное значение с помощью Peek(), почему бы не использовать .Read()

Например

string line;
System.IO.StreamReader file = new System.IO.StreamReader(strfn);
while((line = file.ReadLine()) != null)
{
  this.richTextBox1.AppendText(line+"\n");//you can replace this line to fit your UseCase
}

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

string tempFile = Path.GetTempFileName();
using(var sr = new StreamReader("file.txt"))
{
  using(var sw = new StreamWriter(tempFile))
  {
    string line;
    while((line = sr.ReadLine()) != null)
    {
         if(line != "BlaBlaBla")
             sw.WriteLine(line);
    }
  }
}

Вот еще вариант можно попробовать

Из Stream, если вы Read(buffer, offset, count), вы получите неположительный результат, а если вы Peek(), вы получите отрицательный результат.

При использовании BinaryReader документация предполагает, что PeekChar() должен возвращать отрицательный результат:

Возвращаемое значение

Тип: System.Int32 Следующий доступный символ или -1, если больше нет доступных символов или поток не поддерживает поиск.

Вы уверены, что это не поврежденный поток? т.е. остальные данные не могут образовать полную char из заданной кодировки?

person MethodMan    schedule 30.11.2012
comment
Хотя в качестве решения проблема заключается в том, что строка может быть сколь угодно длинной. Представьте себе файл размером 1 ТБ без возврата каретки и перевода строки, это попытается загрузить его в память. - person Colin Bull; 01.12.2012

Это должно быть reader.Read() == -1 больше не существует, иначе символ существует.

person iefpw    schedule 30.11.2012

Если вам просто нужно прочитать и обработать все данные до конца вашего потока, вам следует использовать Read напрямую, который возвращает -1, если больше нет доступных символов.

int nextByte;
while ((nextByte = reader.ReadByte()) != -1)
    // Process nextByte here.

Редактировать. Альтернативный способ проверить, поддерживает ли средство чтения поиск, — это проверить базовый поток:

bool canSeek = reader.BaseStream.CanSeek;

Если это возвращает true, то Peek должно возвращать -1 только при достижении конца потока.

person Douglas    schedule 30.11.2012
comment
Он специально спросил о TextReader, а не о StreamReader. - person trinalbadger587; 07.12.2016