txt - .NET C#- Acceso aleatorio en archivos de texto-¿no es una forma fácil?
manejo de archivos en c# ejemplos (9)
Tengo un archivo de texto que contiene varios ''registros'' dentro de él. Cada registro contiene un nombre y una colección de números como datos.
Intento crear una clase que lea el archivo, presente solo los nombres de todos los registros y luego permita al usuario seleccionar qué datos de registro quiere.
La primera vez que reviso el archivo, solo leo los nombres de los encabezados, pero puedo hacer un seguimiento de la ''posición'' en el archivo donde está el encabezado. Necesito acceso aleatorio al archivo de texto para buscar el comienzo de cada registro después de que un usuario lo solicite.
Tengo que hacerlo de esta manera porque el archivo es demasiado grande para leerlo completamente en la memoria (1GB +) con las otras demandas de memoria de la aplicación.
He intentado usar la clase .NET StreamReader para lograr esto (que proporciona una funcionalidad ''ReadLine'' muy fácil de usar, pero no hay forma de capturar la posición verdadera del archivo (la posición en la propiedad BaseStream está sesgada debido a la buffer que usa la clase).
¿No hay una manera fácil de hacer esto en .NET?
¿Estás seguro de que el archivo es "demasiado grande"? ¿Lo has intentado de esa manera y ha causado un problema?
Si asigna una gran cantidad de memoria y no la está utilizando en este momento, Windows simplemente la cambiará al disco. Por lo tanto, al acceder desde "memoria", habrá logrado lo que desea: acceso aleatorio al archivo en el disco.
¿La codificación es de tamaño fijo (por ejemplo, ASCII o UCS-2)? De ser así, podría hacer un seguimiento del índice de caracteres (basado en la cantidad de caracteres que ha visto) y encontrar el índice binario basado en eso.
De lo contrario, no, básicamente necesitaría escribir su propia implementación de StreamReader, que le permite echar un vistazo al índice binario. Es una pena que StreamReader no implemente esto, estoy de acuerdo.
Creo que la función de registros de tiempo de ejecución de la biblioteca FileHelpers podría ser útil. http://filehelpers.sourceforge.net/runtime_classes.html
Esta pregunta exacta se hizo en 2006 aquí: http://www.devnewsgroups.net/group/microsoft.public.dotnet.framework/topic40275.aspx
Resumen:
"El problema es que el StreamReader almacena en búfer datos, por lo que el valor devuelto en la propiedad BaseStream.Position siempre está por delante de la línea procesada real".
Sin embargo, "si el archivo está codificado en una codificación de texto de ancho fijo, puede realizar un seguimiento de la cantidad de texto que se ha leído y multiplicarlo por el ancho"
y si no, puede usar FileStream y leer un char a la vez y luego la propiedad BaseStream.Position debería ser correcta
FileStream tiene el método seek ().
Puede usar System.IO.FileStream en lugar de StreamReader. Si sabe exactamente qué archivo contiene (la codificación, por ejemplo), puede hacer todas las operaciones como con StreamReader.
Si eres flexible con la forma en que se escribe el archivo de datos y no te importa que sea un poco menos amigable para el editor de texto, podrías escribir tus registros con un BinaryWriter:
using (BinaryWriter writer =
new BinaryWriter(File.Open("data.txt", FileMode.Create)))
{
writer.Write("one,1,1,1,1");
writer.Write("two,2,2,2,2");
writer.Write("three,3,3,3,3");
}
Entonces, leer inicialmente cada registro es simple porque puede usar el método ReadString del BinaryReader:
using (BinaryReader reader = new BinaryReader(File.OpenRead("data.txt")))
{
string line = null;
long position = reader.BaseStream.Position;
while (reader.PeekChar() > -1)
{
line = reader.ReadString();
//parse the name out of the line here...
Console.WriteLine("{0},{1}", position, line);
position = reader.BaseStream.Position;
}
}
El BinaryReader no está almacenado en el búfer por lo que obtiene la posición correcta para almacenar y usar más adelante. La única molestia es analizar el nombre fuera de línea, lo que puede tener que ver con un StreamReader de todos modos.
Se proporcionan algunas buenas respuestas, pero no pude encontrar algún código fuente que funcionara en mi caso muy simplista. Aquí está, con la esperanza de que le ahorrará a alguien más la hora que pasé buscando.
El "caso muy simplista" al que me refiero es: la codificación de texto es de ancho fijo, y los caracteres de final de línea son los mismos en todo el archivo. Este código funciona bien en mi caso (donde estoy analizando un archivo de registro, y en algún momento tengo que buscarlo en el archivo y luego volver. Implementé lo suficiente para hacer lo que necesitaba hacer (por ejemplo: solo un constructor) y solo reemplaza ReadLine ()), por lo que es probable que deba agregar código ... pero creo que es un punto de partida razonable.
public class PositionableStreamReader : StreamReader
{
public PositionableStreamReader(string path)
:base(path)
{}
private int myLineEndingCharacterLength = Environment.NewLine.Length;
public int LineEndingCharacterLength
{
get { return myLineEndingCharacterLength; }
set { myLineEndingCharacterLength = value; }
}
public override string ReadLine()
{
string line = base.ReadLine();
if (null != line)
myStreamPosition += line.Length + myLineEndingCharacterLength;
return line;
}
private long myStreamPosition = 0;
public long Position
{
get { return myStreamPosition; }
set
{
myStreamPosition = value;
this.BaseStream.Position = value;
this.DiscardBufferedData();
}
}
}
Aquí hay un ejemplo de cómo usar PositionableStreamReader:
PositionableStreamReader sr = new PositionableStreamReader("somepath.txt");
// read some lines
while (something)
sr.ReadLine();
// bookmark the current position
long streamPosition = sr.Position;
// read some lines
while (something)
sr.ReadLine();
// go back to the bookmarked position
sr.Position = streamPosition;
// read some lines
while (something)
sr.ReadLine();
Un par de artículos que pueden ser de interés.
1) Si las líneas son un conjunto fijo de caracteres de longitud, no es necesariamente información útil si el conjunto de caracteres tiene tamaños variables (como UTF-8). Así que revisa tu juego de caracteres.
2) Puede determinar la posición exacta del cursor de archivo desde StreamReader utilizando el valor BaseStream.Position IF If Flush () primero en los buffers (lo que obligará a la posición actual a estar donde comenzará la siguiente lectura - un byte después de la última byte leído).
3) Si sabe de antemano que la longitud exacta de cada registro será la misma cantidad de caracteres, y el juego de caracteres usa caracteres de ancho fijo (por lo que cada línea tiene el mismo número de bytes), puede usar FileStream con un el tamaño del búfer fijo para que coincida con el tamaño de una línea y la posición del cursor al final de cada lectura será, forzosamente, el comienzo de la siguiente línea.
4) ¿Hay alguna razón particular por la cual, si las líneas tienen la misma longitud (suponiendo en bytes aquí), no se usan números de línea y se calcula el byte-offset en el archivo en función del tamaño de línea x número de línea?