2008-11-05 19 views
19

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..NET C#: acceso aleatorio en archivos de texto: ¿no es una forma fácil?

Estoy tratando de 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 desea.

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 utilizar 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 es sesgada debido a la memoria intermedia de la clase utiliza).

¿no hay manera fácil de hacer esto en .NET?

Respuesta

5

puede utilizar un System.IO.FileStream en lugar de StreamReader. Si usted sabe exactamente, qué archivo contiene (la codificación por ejemplo), puede hacer todas las operaciones como con StreamReader.

0

¿Está seguro de que el archivo está "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.

+1

Si el archivo de más de 1 GB de tamaño, y se está ejecutando en 32 bits, es probable que se quede sin espacio de direcciones, incluso si Windows intercambia su pequeño corazón. –

6

FileStream tiene el método seek().

+0

Eso no es útil cuando no sabemos dónde buscar. –

+0

Tal vez estamos usando diferentes definiciones de acceso aleatorio. I (así como Jason aparentemente) considero que significa un archivo de registros con un tamaño específico en bytes, por lo que el inicio de un registro es (RECNUM - 1) * recsize – Powerlord

+0

Más importante aún, el OP sugiere que puedan grabar el flujo de índices en los que comienzan los registros individuales, por lo que saber dónde buscar es un problema resuelto en este caso. –

2

es la codificación una 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 - que habría básicamente necesidad de escribir su propia implementación StreamReader que le permite echar un vistazo al índice binario. Es una pena que StreamReader no implemente esto, estoy de acuerdo.

0

Esta pregunta exacta se le pidió en 2006 aquí: http://www.devnewsgroups.net/group/microsoft.public.dotnet.framework/topic40275.aspx

Resumen:

"El problema es que los tampones de datos StreamReader, por lo que el valor devuelto en propiedad BaseStream.Position es siempre por delante de la línea procesada real ".

Sin embargo, "si el archivo está codificado en una codificación de texto que es de ancho fijo, se puede realizar un seguimiento de la cantidad de texto que se ha leído y que se multiplican por el ancho"

y si no, se puede simplemente use FileStream y lea un char a la vez y luego el BaseStream.propiedad de posición debe ser correcta

5

Si usted es flexible con la forma en se escribe el archivo de datos y no les importa que sea un poco menos de texto editor de usar, se podría escribir sus 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"); 
} 

Luego, en un principio la lectura de cada registro es sencillo porque se puede utilizar el método de la ReadString 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 se almacena temporalmente para que pueda obtener la posición correcta de almacenar y utilizar más tarde. La única molestia es analizar el nombre fuera de línea, lo que puede tener que ver con un StreamReader de todos modos.

11

hay algunas buenas respuestas proporcionadas, pero no pude encontrar algo de código fuente que funcionaría 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" que me refiero es: la codificación de texto es de ancho fijo, y la línea de caracteres de final 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 sólo anular ReadLine()), por lo que es muy probable que tendrá que añadir el código ... pero yo 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í está un ejemplo de cómo utilizar el 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(); 
+0

¡Gracias! Me salvó la piel! – Armbrat

1

Un par de artículos que pueden ser de su interés.

1) Si el lin son un conjunto fijo de caracteres de longitud, que 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) Se puede determinar la posición exacta del cursor archivo desde StreamReader utilizando el valor BaseStream.Position SI que Flush() los amortiguadores primero (lo que obligará a la posición actual para estar donde comenzará la próxima lectura - un byte después de la última lectura del byte).

3) Si sabe de antemano que la longitud exacta de cada registro será el mismo número de caracteres, y el juego de caracteres usa caracteres de ancho fijo (para que cada línea tenga el mismo número de bytes) puede utilice FileStream con un tamaño de 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 en particular por qué, si las líneas son de la misma longitud (en bytes asumiendo aquí) que usted no sólo tiene que utilizar los números de línea y calcular el byte de desplazamiento en el fichero basado en el tamaño de línea x línea ¿número?

Cuestiones relacionadas