2011-01-30 15 views
6

Tengo una función "Buscar archivos" en mi programa que encontrará archivos de texto con el sufijo .ged que mi programa lee. Puedo mostrar los resultados en una ventana de explorador-como el que se ve así:Cómo puedo leer de manera eficiente las primeras pocas líneas de muchos archivos en Delphi

enter image description here

que utilizan los métodos estándar FindFirst/FindNext, y esto funciona muy rápidamente. Los archivos 584 que se muestran arriba se encuentran y se muestran en un par de segundos.

Lo que ahora me gustaría hacer es agregar dos columnas a la pantalla que muestra el "Origen" y "Versión" que están contenidos en cada uno de estos archivos. Esta información se encuentra generalmente dentro de las 10 primeras líneas de cada archivo, en las líneas que se parecen:

1 SOUR FTM 
2 VERS Family Tree Maker (20.0.0.368) 

ahora no tengo problema al analizar esto muy rápidamente a mí mismo, y eso no es lo que estoy preguntando.

Lo que necesito ayuda es simplemente cómo cargar más rápidamente las primeras 10 líneas de estos archivos para poder analizarlos.

He intentado hacer un StringList.LoadFromFile, pero lleva mucho tiempo cargar los archivos de gran tamaño, por ejemplo, en los de más de 1 MB.

Ya que solo necesito las primeras 10 líneas más o menos, ¿cuál es la mejor forma de obtenerlas?

Estoy usando Delphi 2009, y mis archivos de entrada pueden ser o no ser Unicode, por lo que esto debe funcionar para cualquier codificación.


Seguimiento: Gracias Antonio,

que terminé haciendo esto que funciona muy bien:

var 
    CurFileStream: TStream; 
    Buffer: TBytes; 
    Value: string; 
    Encoding: TEncoding; 

try 
    CurFileStream := TFileStream.Create(folder + FileName, fmOpenRead); 
    SetLength(Buffer, 256); 
    CurFileStream.Read(Buffer[0], 256); 
    TEncoding.GetBufferEncoding(Buffer, Encoding); 
    Value := Encoding.GetString(Buffer); 
    ... 
    (parse through Value to get what I want) 
    ... 
finally 
    CurFileStream.Free; 
end; 
+0

TStrings.LoadFromFile es muy ineficiente, olvídate de eso. Piense fuera de la caja y lea el número razonable de bytes (por ejemplo: NumLines * AvgLineLength), trunque con LineStart y luego divida en TStrings –

+0

En realidad, Worm, no es tan malo como podría pensar. Puede leer y cargar aproximadamente 10 MB por segundo. Todavía lo uso con éxito cuando tengo que buscar textos en esos archivos. Pero por qué usarlo para cargar archivos completos y hacer que el usuario espere 40 segundos cuando solo necesita las primeras líneas que se necesitan. – lkessler

Respuesta

14

Uso TFileStream y con leer método Read número de bytes necesarios. Aquí está el ejemplo de la lectura de información de mapa de bits que también se almacena al comienzo del archivo.

http://www.delphidabbler.com/tips/19

+4

+1 Me gustaría utilizar un TFileStream para esto, ya que envuelve muy bien la API nativa de archivos del sistema operativo. –

+5

+1. Simplemente lea los primeros 4 Kbytes de datos: probablemente sea suficiente para contener completamente las primeras líneas, y es la cantidad mínima de datos que se lee del disco de cualquier manera. Si está leyendo desde muchos archivos (y los archivos 584 no son exactamente "muchos"), y desea hacerse elegante, puede querer abrir los archivos sin almacenarlos en la memoria caché, utilizando CreateFile y pasando el Handle a THandleStream: podría proporcionar una pequeña cantidad pequeña de mejora porque el sistema operativo no sabe almacenar en caché los datos que muy probablemente no se volverán a solicitar. –

+2

TFileStream carece de una capacidad de lectura. ¿Qué pasa si probablemente no es lo suficientemente bueno? –

4

Sólo tiene que abrir el archivo usted mismo para el bloque de lectura (no usar TStringList funcionalidad incorporada), y leer el primer bloque del archivo, y luego se puede, por ejemplo, cargar ese bloque a un StringList con strings.SetText() (si está usando funciones de bloque) o simplemente strings.LoadFromStream() si está cargando sus bloques usando streams.

Personalmente, me gustaría ir con las funciones de bloqueo FileRead/FileWrite y cargar el bloque en un búfer. También podría usar funciones similair winapi, pero eso es solo más código sin ninguna razón.

OS lee archivos en bloques, que son al menos 512 bytes grandes en casi cualquier plataforma/sistema de archivos, para que pueda leer 512 bytes primero (y espero que tenga 10 líneas, lo que será cierto si sus líneas son generalmente cortas suficiente). Esto será (prácticamente) tan rápido como leer 100 o 200 bytes.

Luego, si observa que sus objetos de cuerdas tienen solo menos de 10 líneas, simplemente lea el próximo bloque de 512 bytes e intente analizar de nuevo. (O simplemente vaya con los bloques 1024, 2048 y demás, en muchos sistemas probablemente sea tan rápido como 512 bloques, ya que los tamaños de clúster del sistema de archivos generalmente son más grandes que 512 bytes).

PS.Además, al usar subprocesos o funcionalidad asincrónica en las funciones de archivos de winapi (CreateFile y demás), puede cargar esos datos desde archivos de forma asincrónica, mientras el resto de su aplicación funciona. Específicamente, la interfaz no se congelará durante la lectura de directorios grandes.

Esto hará que la carga de su información parezca más rápida (ya que la lista de archivos se cargará directamente, y algunos milisegundos más tarde aparecerá el resto de la información), aunque no aumente la velocidad de lectura real.

Haga esto solo si ha probado los otros métodos y siente que necesita el refuerzo adicional.

+0

FileRead/FileWrite son las funciones de la API –

+0

'ReadFile()' y 'WriteFile()' son funciones de la API Win32. 'FileRead()' y 'FileWrite()' son envoltorios SysUtils a su alrededor. –

0

A veces oldschool pascal stylee no es tan malo. Aunque el acceso a archivos no-oo ya no parece ser muy popular, ReadLn(F,xxx) todavía funciona bastante bien en situaciones como la suya.

El siguiente código carga información (nombre de archivo, fuente y versión) en un TDictionary para que pueda buscarlo fácilmente, o puede usar una vista de lista en modo virtual, y buscar cosas en esta lista cuando el ondata incluso dispara .

Advertencia: el siguiente código no funciona con Unicode.

program Project101; 
{$APPTYPE CONSOLE} 

uses 
    IoUtils, Generics.Collections, SysUtils; 

type 
    TFileInfo=record 
    FileName, 
    Source, 
    Version:String; 
    end; 

function LoadFileInfo(var aFileInfo:TFileInfo):Boolean; 
var 
    F:TextFile; 
begin 
    Result := False; 
    AssignFile(F,aFileInfo.FileName); 
    {$I-} 
    Reset(F); 
    {$I+} 
    if IOResult = 0 then 
    begin 
    ReadLn(F,aFileInfo.Source); 
    ReadLn(F,aFileInfo.Version); 
    CloseFile(F); 
    Exit(True) 
    end 
    else 
    WriteLn('Could not open ', aFileInfo.FileName); 
end; 

var 
    FileInfo:TFileInfo; 
    Files:TDictionary<string,TFileInfo>; 
    S:String; 
begin 
    Files := TDictionary<string,TFileInfo>.Create; 
    try 
    for S in TDirectory.GetFiles('h:\WINDOWS\system32','*.xml') do 
    begin 
     WriteLn(S); 
     FileInfo.FileName := S; 
     if LoadFileInfo(FileInfo) then 
     Files.Add(S,FileInfo); 
    end; 

    // showing file information... 
    for FileInfo in Files.Values do 
     WriteLn(FileInfo.Source, ' ',FileInfo.Version); 
    finally 
    Files.Free 
    end; 
    WriteLn; 
    WriteLn('Done. Press any key to quit . . .'); 
    ReadLn; 
end. 
+3

Solo tenga en cuenta que los métodos de lectura/escritura (Ln) en D2009 + do * NOT * admiten unicode. –

+1

-1 La pregunta indica que los archivos pueden usar codificaciones Unicode –

+0

-1 por el mismo motivo que @David. La falta de soporte Unicode hace que esta respuesta no sea viable. –

3

Puede utilizar un TStreamReader para leer líneas individuales de cualquier objeto TStream, como un TFileStream. Para una E/S de archivo aún más rápida, puede usar vistas mapeadas por memoria con TCustomMemoryStream.

+0

¿TStreamReader puede hacer una lectura equivalente? –

+0

Escribí un ejemplo basado en la sugerencia de Remy, como mi respuesta. –

+0

@Warren: Sí. TStreamReader tiene un método público ReadLine() disponible. –

2

Bien, borré mi primera respuesta. Usando la primera sugerencia de Remy anterior, intenté de nuevo con cosas incorporadas. Lo que no me gusta aquí es que debes crear y liberar dos objetos. Creo que me gustaría hacer mi propia clase para terminar con esto:

var 
    fs:TFileStream; 
    tr:TTextReader; 
    filename:String; 
begin 
    filename := 'c:\temp\textFileUtf8.txt'; 
    fs := TFileStream.Create(filename, fmOpenRead); 
    tr := TStreamReader.Create(fs); 
    try 
     Memo1.Lines.Add(tr.ReadLine); 

    finally 
    tr.Free; 
    fs.Free; 
    end; 
end; 

Si alguien está interesado en lo que tenía aquí antes, que tenía el problema de no trabajar con archivos Unicode.

+0

Gracias por la alternativa, Warren. Ya había logrado implementar TFileStream como Antonio sugirió, y está funcionando lo suficientemente bien como para no tener que probar nada más. Sin embargo, recordaré esto como una alternativa. – lkessler

+0

+1 para una mejor solución debido a ReadLine, pero no estoy seguro de que sea * más rápido * –

+0

TStreamReader tiene varios constructores que le permiten especificar un nombre de archivo en lugar de un puntero de objeto TStream separado. –

Cuestiones relacionadas