StreamReader y buscando

¿puedes usar el lector de secuencias para leer un archivo de texto normal y luego, a la mitad de la lectura, cerrar el lector de secuencias después de guardar la posición actual y luego abrir de nuevo el lector de secuencias y comenzar a leer desde ese punto?

si no, ¿qué más puedo usar para lograr el mismo caso sin bloquear el archivo?

algo como esto:

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

aquí está el código que probé

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

Sí puedes, mira esto:

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

Actualización: tenga en cuenta que no necesariamente puede usar sr.BaseStream.Position para nada útil porque StreamReader usa búferes para que no refleje lo que realmente ha leído. Supongo que tendrás problemas para encontrar la posición verdadera. Porque no puedes simplemente contar los caracteres (diferentes codificaciones y, por lo tanto, longitudes de caracteres). Creo que la mejor manera es trabajar con los propios FileStream .

Actualización: utilice el TGREER.myStreamReader desde aquí: http://www.daniweb.com/software-development/csharp/threads/35078 esta clase agrega BytesRead etc. (funciona con ReadLine() pero aparentemente no con otros métodos de lectura) y entonces puedes hacer esto:

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

Me doy cuenta de que esto es realmente tardío, pero acabo de tropezar con este increíble error en StreamReader ; el hecho de que no puede buscar de manera confiable al usar StreamReader . Personalmente, mi necesidad específica es tener la capacidad de leer caracteres, pero luego “respaldar” si se cumple una determinada condición; es un efecto secundario de uno de los formatos de archivo que estoy analizando.

Usar ReadLine() no es una opción porque solo es útil en trabajos de análisis realmente triviales. Tengo que admitir secuencias de delimitador de línea / registro configurables y admitir secuencias de delimitación de escape. Además, no quiero implementar mi propio búfer para poder admitir “copias de seguridad” y secuencias de escape; ese debería ser el trabajo de StreamReader .

Este método calcula la posición real en la secuencia subyacente de bytes a petición. Funciona para UTF8, UTF-16LE, UTF-16BE, UTF-32LE, UTF-32BE y cualquier encoding de un solo byte (p. Ej., Páginas de códigos 1252, 437, 28591, etc.), independientemente de la presencia de un preámbulo / BOM. Esta versión no funcionará para UTF-7, Shift-JIS u otras codificaciones de bytes variables.

Cuando necesito buscar una posición arbitraria en la secuencia subyacente, fijo directamente BaseStream.Position y luego llamo a DiscardBufferedData() para que StreamReader vuelva a estar sincronizado para la siguiente llamada de Read() / Peek() .

Y un recordatorio amistoso: no establezca arbitrariamente BaseStream.Position . Si biseca a un personaje, invalidará la siguiente Read() y, para UTF-16 / -32, también invalidará el resultado de este método.

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

Por supuesto, esto usa Reflection para obtener variables privadas, por lo que hay un riesgo involucrado. Sin embargo, este método funciona con .Net 2.0, 3.0, 3.5, 4.0, 4.0.3, 4.5, 4.5.1, 4.5.2, 4.6 y 4.6.1. Más allá de ese riesgo, el único otro supuesto crítico es que el byte-buffer subyacente es un byte[1024] ; si Microsoft lo cambia de la manera incorrecta, el método se rompe para UTF-16 / -32.

Esto se ha probado con un archivo UTF-8 lleno de Ažテ𣘺 (10 bytes: 0x41 C5 BE E3 83 86 F0 A3 98 BA ) y un archivo UTF-16 lleno con A𐐷 (6 bytes: 0x41 00 01 D8 37 DC ) . El punto es forzar fragmentos de caracteres a lo largo de los límites del byte[1024] , todas las diferentes maneras en que podrían ser.

ACTUALIZACIÓN (03-07-2013) : reparé el método, que originalmente usaba el código roto de esa otra respuesta. Esta versión ha sido probada en comparación con datos que contienen caracteres que requieren el uso de pares suplentes. Los datos se colocaron en 3 archivos, cada uno con una encoding diferente; un UTF-8, un UTF-16LE y un UTF-16BE.

ACTUALIZACIÓN (2016-02) : la única forma correcta de manejar los caracteres bisecados es interpretar directamente los bytes subyacentes. UTF-8 se maneja correctamente y UTF-16 / -32 funciona (dada la longitud de byteBuffer).

Desde MSDN:

StreamReader está diseñado para la entrada de caracteres en una encoding particular, mientras que la clase Stream está diseñada para entrada y salida de bytes. Use StreamReader para leer líneas de información de un archivo de texto estándar.

En la mayoría de los ejemplos que incluyen StreamReader , verá la lectura línea por línea usando ReadLine (). El método Seek proviene de la clase Stream , que básicamente se utiliza para leer o manejar datos en bytes.

Si solo desea buscar una posición de inicio dentro de una secuencia de texto, agregué esta extensión a StreamReader para poder determinar dónde debería ocurrir la edición de la transmisión. De acuerdo, esto se basa en los personajes como el aspecto de incremento de la lógica, pero para mi propósito, funciona muy bien, para obtener la posición dentro de un archivo basado en texto / ASCII basado en un patrón de cadena. Luego, puede usar esa ubicación como punto de inicio para leer, para escribir un nuevo archivo que descifre los datos antes del punto de inicio.

La posición devuelta dentro de la secuencia se puede proporcionar a Seek para comenzar desde esa posición dentro de lecturas de secuencia basadas en texto. Funciona. Lo he probado Sin embargo, puede haber problemas cuando se combina con caracteres no ASCII Unicode durante el algoritmo de coincidencia. Esto se basó en el inglés estadounidense y la página de caracteres asociada.

Conceptos básicos: explora a través de una secuencia de texto, carácter por carácter, buscando el patrón de secuencia secuencial (que coincide con el parámetro de cadena) reenviar solo a través de la secuencia. Una vez que el patrón no coincide con el parámetro de cadena (es decir, en adelante, char por char), comenzará de nuevo (desde la posición actual) tratando de obtener una coincidencia, char-by-char. Eventualmente se cerrará si no se puede encontrar la coincidencia en la transmisión. Si se encuentra la coincidencia, devuelve la posición actual de “carácter” dentro de la secuencia, no StreamReader.BaseStream.Position, ya que esa posición está por delante, en función del almacenamiento en búfer que hace StreamReader.

Como se indica en los comentarios, este método PERMITIRÁ la posición de StreamReader, y se volverá a establecer al principio (0) al final del método. StreamReader.BaseStream.Seek debe usarse para ejecutar la posición devuelta por esta extensión.

Nota: la posición devuelta por esta extensión también funcionará con BinaryReader. Busque como posición de inicio cuando trabaje con archivos de texto. De hecho, utilicé esta lógica para volver a escribir un archivo PostScript en el disco, después de descartar la información del encabezado PJL para convertir el archivo en un archivo legible PostScript “apropiado” que podría ser consumido por GhostScript. 🙂

La cadena para buscar dentro de PostScript (después del encabezado PJL) es: “%! PS-“, que es seguido por “Adobe” y la versión.

 public static class StreamReaderExtension { ///  /// Searches from the beginning of the stream for the indicated /// . Once found, returns the position within the stream /// that the pattern begins at. ///  /// The string pattern to search for in the stream. /// If  is found in the stream, then the start position /// within the stream of the pattern; otherwise, -1. /// Please note: this method will change the current stream position of this instance of /// . When it completes, the position of the reader will /// be set to 0. 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 matches = new List(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; } } 

FileStream.Position (o equivalentemente, StreamReader.BaseStream.Position) generalmente estará adelante, posiblemente por delante, de la posición de TextReader debido a que se está produciendo el almacenamiento en búfer subyacente.

Si puede determinar cómo se manejan las nuevas líneas en sus archivos de texto, puede sumr la cantidad de bytes leídos en función de la longitud de línea y los caracteres de fin de línea.

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

Para codificaciones de archivos de texto más complejas puede que necesites ser más elegante que esto, pero funcionó para mí.