StreamReader и поиск - PullRequest
       1

StreamReader и поиск

12 голосов
/ 23 марта 2011

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

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

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

 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());

Ответы [ 5 ]

22 голосов
/ 04 июля 2013

Я понимаю, что это действительно запоздало, но я наткнулся на этот невероятный недостаток в 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's 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 байт: 0x41 C5 BE E3 83 86 F0 A3 98 BA), и файла UTF-16, заполненного A? (6 байтов: 0x41 00 01 D8 37 DC). Смысл в том, чтобы принудительно фрагментировать символы вдоль границ byte[1024], разными способами, какими они могли бы быть.

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

ОБНОВЛЕНИЕ (2016-02) : Единственный правильный способ обработки пополам символов - это непосредственная интерпретация нижележащих байтов. UTF-8 правильно обрабатывается, а UTF-16 / -32 работает (учитывая длину byteBuffer).

14 голосов
/ 23 марта 2011

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

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

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

Обновление: Используйте 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());
}
1 голос
/ 18 августа 2017

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

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

Основы: он просматривает текстовый поток, посимвольный, в поисках последовательного строкового шаблона (который соответствует строковому параметру) вперед только через поток. Как только шаблон не соответствует строковому параметру (то есть, идет вперед, символ за символом), он будет начинаться заново (с текущей позиции), пытаясь найти совпадение символ за символом. Это в конечном итоге завершится, если совпадение не может быть найдено в потоке. Если совпадение найдено, оно возвращает текущую «символьную» позицию в потоке, а не 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;
    }
}
1 голос
/ 23 марта 2011

Из MSDN:

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

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

0 голосов
/ 28 января 2015

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());
}

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...