StreamReader и поиск

Вы можете использовать streamreader для чтения нормального текстового файла, а затем в середине чтения закройте средство чтения потоков после сохранения текущей позиции, а затем снова запустите средство чтения каналов и начните чтение с этой позиции?

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

что-то вроде этого:

 var fs = File.Open(@"C:\testfile.txt", FileMode.Open, FileAccess.Read);
        var sr = new StreamReader(fs);
        Debug.WriteLine(sr.ReadLine());//Prints:firstline
        var pos = fs.Position;
        while (!sr.EndOfStream)
        {
            Debug.WriteLine(sr.ReadLine());
        }
        fs.Seek(pos, SeekOrigin.Begin);
        Debug.WriteLine(sr.ReadLine());//Prints Nothing, i expect it to print SecondLine.

@lasseespeholt

вот код, который я пробовал

            var position = -1;
        StreamReaderSE sr = new StreamReaderSE(@"c:\testfile.txt");
        Debug.WriteLine(sr.ReadLine());
        position = sr.BytesRead;
        Debug.WriteLine(sr.ReadLine());
        Debug.WriteLine(sr.ReadLine());
        Debug.WriteLine(sr.ReadLine());
        Debug.WriteLine(sr.ReadLine());
        Debug.WriteLine("Wait");
        sr.BaseStream.Seek(position, SeekOrigin.Begin);
        Debug.WriteLine(sr.ReadLine());

Ответ 1

Да, вы можете это увидеть:

var sr = new StreamReader("test.txt");
sr.BaseStream.Seek(2, SeekOrigin.Begin); // Check sr.BaseStream.CanSeek first

Обновление: Имейте в виду, что вы не можете использовать sr.BaseStream.Position для чего-нибудь полезного, потому что StreamReader использует буферы, чтобы он не отражал то, что вы действительно прочитали. Наверное, у тебя будут проблемы с поиском истинной позиции. Потому что вы не можете просто подсчитывать символы (разные кодировки и, следовательно, длины символов). Я думаю, что лучший способ - работать с FileStream.

Update: Используйте TGREER.myStreamReader здесь: http://www.daniweb.com/software-development/csharp/threads/35078 этот класс добавляет BytesRead и т.д. (работает с ReadLine(), но, по-видимому, не с другими методами чтения) и тогда вы можете сделать вот так:

File.WriteAllText("test.txt", "1234\n56789");

long position = -1;

using (var sr = new myStreamReader("test.txt"))
{
    Console.WriteLine(sr.ReadLine());

    position = sr.BytesRead;
}

Console.WriteLine("Wait");

using (var sr = new myStreamReader("test.txt"))
{
    sr.BaseStream.Seek(position, SeekOrigin.Begin);
    Console.WriteLine(sr.ReadToEnd());
}

Ответ 2

Я понимаю, что это действительно запоздало, но я просто наткнулся на этот невероятный недостаток в StreamReader сам; тот факт, что вы не можете надежно искать при использовании StreamReader. Лично моя особая потребность в том, чтобы иметь возможность читать символы, но затем "поддерживать", если выполняется определенное условие; это побочный эффект одного из форматов файлов, которые я обрабатываю.

Использование ReadLine() не является опцией, потому что оно полезно только для действительно тривиальных заданий синтаксического анализа. Я должен поддерживать настраиваемые последовательности разделителей строк и строк и поддерживать последовательности разделителей escape-последовательности. Кроме того, я не хочу реализовывать свой собственный буфер, поэтому я могу поддерживать "резервное копирование" и escape-последовательности; это должно быть задание StreamReader.

Этот метод вычисляет фактическую позицию в базовом потоке байтов по запросу. Он работает для UTF8, UTF-16LE, UTF-16BE, UTF-32LE, UTF-32BE и любого однобайтового кодирования (например, кодовых страниц 1252, 437, 28591 и т.д.), Независимо от наличия преамбулы/спецификации. Эта версия не будет работать для кодировок UTF-7, Shift-JIS или других переменных-байтов.

Когда мне нужно искать произвольную позицию в базовом потоке, я прямо устанавливаю BaseStream.Position, а затем вызываю DiscardBufferedData(), чтобы получить StreamReader назад в синхронизации для следующего вызова Read()/Peek().

И дружеское напоминание: не произвольно устанавливайте BaseStream.Position. Если вы разделите символ на две части, вы отмените следующий Read(), а для UTF-16/-32 вы также аннулируете результат этого метода.

public static long GetActualPosition(StreamReader reader)
{
    System.Reflection.BindingFlags flags = System.Reflection.BindingFlags.DeclaredOnly | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetField;

    // The current buffer of decoded characters
    char[] charBuffer = (char[])reader.GetType().InvokeMember("charBuffer", flags, null, reader, null);

    // The index of the next char to be read from charBuffer
    int charPos = (int)reader.GetType().InvokeMember("charPos", flags, null, reader, null);

    // The number of decoded chars presently used in charBuffer
    int charLen = (int)reader.GetType().InvokeMember("charLen", flags, null, reader, null);

    // The current buffer of read bytes (byteBuffer.Length = 1024; this is critical).
    byte[] byteBuffer = (byte[])reader.GetType().InvokeMember("byteBuffer", flags, null, reader, null);

    // The number of bytes read while advancing reader.BaseStream.Position to (re)fill charBuffer
    int byteLen = (int)reader.GetType().InvokeMember("byteLen", flags, null, reader, null);

    // The number of bytes the remaining chars use in the original encoding.
    int numBytesLeft = reader.CurrentEncoding.GetByteCount(charBuffer, charPos, charLen - charPos);

    // For variable-byte encodings, deal with partial chars at the end of the buffer
    int numFragments = 0;
    if (byteLen > 0 && !reader.CurrentEncoding.IsSingleByte)
    {
        if (reader.CurrentEncoding.CodePage == 65001) // UTF-8
        {
            byte byteCountMask = 0;
            while ((byteBuffer[byteLen - numFragments - 1] >> 6) == 2) // if the byte is "10xx xxxx", it a continuation-byte
                byteCountMask |= (byte)(1 << ++numFragments); // count bytes & build the "complete char" mask
            if ((byteBuffer[byteLen - numFragments - 1] >> 6) == 3) // if the byte is "11xx xxxx", it starts a multi-byte char.
                byteCountMask |= (byte)(1 << ++numFragments); // count bytes & build the "complete char" mask
            // see if we found as many bytes as the leading-byte says to expect
            if (numFragments > 1 && ((byteBuffer[byteLen - numFragments] >> 7 - numFragments) == byteCountMask))
                numFragments = 0; // no partial-char in the byte-buffer to account for
        }
        else if (reader.CurrentEncoding.CodePage == 1200) // UTF-16LE
        {
            if (byteBuffer[byteLen - 1] >= 0xd8) // high-surrogate
                numFragments = 2; // account for the partial character
        }
        else if (reader.CurrentEncoding.CodePage == 1201) // UTF-16BE
        {
            if (byteBuffer[byteLen - 2] >= 0xd8) // high-surrogate
                numFragments = 2; // account for the partial character
        }
    }
    return reader.BaseStream.Position - numBytesLeft - numFragments;
}

Конечно, это использует Reflection для получения частных переменных, поэтому есть риск. Однако этот метод работает с .Net 2.0, 3.0, 3.5, 4.0, 4.0.3, 4.5, 4.5.1, 4.5.2, 4.6 и 4.6.1. Помимо этого риска единственным критическим предположением является то, что базовый байт-буфер является byte[1024]; если Microsoft изменяет его неправильно, метод прерывается для UTF-16/-32.

Это было протестировано против файла UTF-8, заполненного Ažテ𣘺 (10 bytes: 0x41 C5 BE E3 83 86 F0 A3 98 BA), и файла UTF-16, заполненного A𐐷 (6 bytes: 0x41 00 01 D8 37 DC). Суть заключается в том, чтобы заставить фрагмент символов вдоль границ byte[1024], все различные способы, которыми они могли бы быть.

UPDATE (2013-07-03). Я исправил метод, изначально используемый сломанным кодом из этого другого ответа. Эта версия была протестирована против данных, содержащих символы, требующие использования суррогатных пар. Данные были помещены в 3 файла, каждый с другой кодировкой; один UTF-8, один UTF-16LE и один UTF-16BE.

UPDATE (2016-02). Единственный правильный способ обработки двухпользовательских символов - прямо интерпретировать базовые байты. UTF-8 правильно обрабатывается, а работа UTF-16/-32 (с учетом длины byteBuffer).

Ответ 3

Из MSDN:

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

В большинстве примеров, связанных с StreamReader, вы увидите чтение по строкам с помощью ReadLine(). Метод Seek исходит из класса Stream, который в основном используется для чтения или обработки данных в байтах.

Ответ 4

Если вы хотите просто найти начальную позицию в текстовом потоке, я добавил это расширение в StreamReader, чтобы я мог определить, где должно происходить редактирование потока. Конечно, это основано на символах как на приросте аспекта логики, но для моих целей он отлично работает, чтобы получить позицию в файле на основе текста /ASCII на основе шаблона строки. Затем вы можете использовать это местоположение в качестве начальной точки для чтения, чтобы написать новый файл, который записывает данные до начальной точки.

Возвращенное положение в потоке может быть предоставлено, чтобы Seek начал с этой позиции в текстовых потоках. Оно работает. Я протестировал его. Однако могут возникать проблемы при сопоставлении с символами Unicode без ASCII во время алгоритма сопоставления. Это было основано на американском английском и связанной странице символов.

Основы: он просматривает текстовый поток, по-символу, и ищет последовательный шаблон строки (который соответствует строковому параметру) вперед только через поток. Как только шаблон не будет соответствовать строковому параметру (т.е. Идти вперед, char на char), тогда он начнет работу (из текущей позиции), пытаясь получить соответствие, char -by- char, В конечном итоге он прекратится, если совпадение не будет найдено в потоке. Если совпадение найдено, оно возвращает текущую позицию "символ" в потоке, а не StreamReader.BaseStream.Position, поскольку эта позиция впереди, на основе буферизации, которую делает StreamReader.

Как указано в комментариях, этот метод будет влиять на позицию StreamReader, и он будет возвращен в начало (0) в конце метода. StreamReader.BaseStream.Seek следует использовать для запуска в позицию, возвращаемую этим расширением.

Примечание. Позиция, возвращаемая этим расширением, также будет работать с BinaryReader.Seek в качестве начальной позиции при работе с текстовыми файлами. Я на самом деле использовал эту логику для этой цели, чтобы переписать файл PostScript на диск, после отбрасывания информации заголовка PJL, чтобы сделать файл "правильным" PostScript-читаемым файлом, который может быть использован GhostScript.:)

Строка для поиска в PostScript (после заголовка PJL): "%! PS-", за которой следует "Adobe" и версия.

public static class StreamReaderExtension
{
    /// <summary>
    /// Searches from the beginning of the stream for the indicated
    /// <paramref name="pattern"/>. Once found, returns the position within the stream
    /// that the pattern begins at.
    /// </summary>
    /// <param name="pattern">The <c>string</c> pattern to search for in the stream.</param>
    /// <returns>If <paramref name="pattern"/> is found in the stream, then the start position
    /// within the stream of the pattern; otherwise, -1.</returns>
    /// <remarks>Please note: this method will change the current stream position of this instance of
    /// <see cref="System.IO.StreamReader"/>. When it completes, the position of the reader will
    /// be set to 0.</remarks>
    public static long FindSeekPosition(this StreamReader reader, string pattern)
    {
        if (!string.IsNullOrEmpty(pattern) && reader.BaseStream.CanSeek)
        {
            try
            {
                reader.BaseStream.Position = 0;
                reader.DiscardBufferedData();
                StringBuilder buff = new StringBuilder();
                long start = 0;
                long charCount = 0;
                List<char> matches = new List<char>(pattern.ToCharArray());
                bool startFound = false;

                while (!reader.EndOfStream)
                {
                    char chr = (char)reader.Read();

                    if (chr == matches[0] && !startFound)
                    {
                        startFound = true;
                        start = charCount;
                    }

                    if (startFound && matches.Contains(chr))
                    {
                        buff.Append(chr);

                        if (buff.Length == pattern.Length
                            && buff.ToString() == pattern)
                        {
                            return start;
                        }

                        bool reset = false;

                        if (buff.Length > pattern.Length)
                        {
                            reset = true;
                        }
                        else
                        {
                            string subStr = pattern.Substring(0, buff.Length);

                            if (buff.ToString() != subStr)
                            {
                                reset = true;
                            }
                        }

                        if (reset)
                        {
                            buff.Length = 0;
                            startFound = false;
                            start = 0;
                        }
                    }

                    charCount++;
                }
            }
            finally
            {
                reader.BaseStream.Position = 0;
                reader.DiscardBufferedData();
            }
        }

        return -1;
    }
}

Ответ 5

FileStream.Position(или, что то же самое, StreamReader.BaseStream.Position), как правило, впереди - возможно, впереди - позиции TextReader из-за текущей буферизации.

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

File.WriteAllText("test.txt", "1234" + System.Environment.NewLine + "56789");

long position = -1;
long bytesRead = 0;
int newLineBytes = System.Environment.NewLine.Length;

using (var sr = new StreamReader("test.txt"))
{
    string line = sr.ReadLine();
    bytesRead += line.Length + newLineBytes;

    Console.WriteLine(line);

    position = bytesRead;
}

Console.WriteLine("Wait");

using (var sr = new StreamReader("test.txt"))
{
    sr.BaseStream.Seek(position, SeekOrigin.Begin);
    Console.WriteLine(sr.ReadToEnd());
}

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