2011-01-30 2 views
6

私のプログラムで読み込まれた.ged接尾辞付きのテキストファイルを検索する "ファイル検索"機能があります。デルファイの多くのファイルの最初の行を効率的に読み取るには

enter image description here

私は標準関数は、FindFirst/FindNextのメソッドを使用しており、これは非常に迅速に動作します:私はこのようになりますエクスプローラのようなウィンドウで見出された結果を表示します。上記の584ファイルが数秒で​​見つけられて表示されます。

ここでは、これらのファイルに含まれる「ソース」と「バージョン」を示す2つの列をディスプレイに追加します。この情報は、のように見えるの行に、各ファイルの最初の10行以内に通常発見された:

1 SOUR FTM 
2 VERS Family Tree Maker (20.0.0.368) 

は今、私は非常に迅速に自分自身をこれを解析する問題がない、それは私が求めているものではありません。

私が助けが必要なのは、これらのファイルから最初の10行ほどを読み込んで解析する方法です。

私はStringList.LoadFromFileを実行しようとしましたが、1 MB以上の大きなファイルをロードするには時間がかかります。

私は最初の10行程度しか必要ないので、どうすればそれらを得るのがベストでしょうか?

私はDelphi 2009を使用しています。私の入力ファイルはUnicodeかもしれないし、そうでないかもしれないので、これはどんなエンコーディングでも動作する必要があります。正常に動作おかげアントニオ、

私はこれをやってしまった:


フォロー

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は非常に非効率的です。忘れてしまいます。ボックスから考えて、妥当な数(NumLines * AvgLineLengthなど)のバイト数を読み、LineStartで切り捨ててからTStringに分割してください。 –

+0

実際には、Worm、それはあなたが考えると悪くありません。約10 MB /秒の読み込みと読み込みが可能です。私はそれらのファイルのテキストを検索する必要があるときに、私はまだ正常に使用します。しかし、ファイル全体を読み込んで、最初の数行だけが必要なときにユーザーが40秒待つようにするのはなぜでしょうか。 – lkessler

答えて

14

使用TFileStreamと必要なバイトのReadメソッド読んで番号を指定します。次に、ファイルの先頭にも格納されているビットマップ情報を読み取る例を示します。

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

+4

+1ネイティブOSファイルAPIを非常にきれいにまとめるので、TFileStreamを使用します。 –

+5

+1。最初の4Kバイトのデータを読み込むだけです。おそらく、最初の数行を完全に格納するのに十分であり、それはディスクから何らかの形で読み込まれるデータの最小量です。多くのファイル(そして584個のファイルが正確に「多数」ではない)から読み込み中で、空想を得たい場合は、キャッシュせずにファイルを開き、CreateFileを使用してハンドルをTHandleStreamに渡したい場合があります。 OSは、要求されない可能性が非常に高いデータをキャッシュしないことを知っているため、少々の改善になります。 –

+2

TFileStreamには、readLn機能がありません。おそらく十分でない場合はどうすればよいですか? –

4

ジャストブロックは(TStringListの組み込み機能を使用していない)を読み取るためにあなた自身をファイルを開き、ファイルの最初のブロックを読み、次にあなたが例えばstrings.SetTextとSTRINGLISTにそのブロックを読み込むことができます()(ブロック関数を使用している場合)または単純にstrings.LoadFromStream()を使用してストリームを使用してブロックをロードします。

私は個人的にはFileRead/FileWriteブロック関数に行き、ブロックをバッファにロードします。 similair winapi関数を使用することもできますが、それは理由がないだけのコードです。

OSは、ほとんどのプラットフォーム/ファイルシステムで512バイト以上のブロックを読み込みます。最初に512バイトを読み込むことができます(そして、10行すべてが得られることを望みます。十分な)。これは100または200バイトを読み取るほど(実際的に)速くなります。

文字列オブジェクトの行数が10行未満であることがわかったら、次の512バイトブロックを読み込み、再度解析してみてください。 (1024ブロック、2048ブロック、多くのシステムでは、ファイルシステムのクラスタサイズが一般的に512バイトよりも大きいので、おそらく512ブロックほど高速になります)。

PS。また、WinAPIファイル関数(CreateFileなど)でスレッドや非同期機能を使用すると、残りのアプリケーションが動作している間に、そのデータをファイルから非同期に読み込むことができます。具体的には、大きなディレクトリの読み取り中にインターフェイスがフリーズすることはありません。

実際の読み取り速度を上げずに、ファイルのリストが直接ロードされ、残りの情報が数ミリ秒後に表示されるため、情報の読み込みが高速になります。

これは、他の方法を試したが、追加の追加が必要と思われる場合にのみ実行してください。

+0

FileRead/FileWriteはAPI関数です –

+0

'ReadFile()'と 'WriteFile()'はWin32 API関数です。 'FileRead()'と 'FileWrite()'はそれらの周りのSysUtilsラッパーです。 –

0

時々oldschoolのパスカルスタイルはそれほど悪くありません。 非ooファイルへのアクセスはこれ以上普及していないようですが、ReadLn(F,xxx)はあなたのような状況ではかなりうまく動作します。

以下のコードは、TDictionaryに情報(ファイル名、ソース、バージョン)を読み込んで、簡単に検索できるようにしたり、バーチャルモードでlistviewを使用したり、ondata

警告:以下のコードは、ユニコードでは動作しません。

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

D2009での読み取り/書き込み(Ln)メソッドは、Unicodeをサポートしていないことに注意してください。 –

+1

-1 @Davidと同じ理由でファイルにUnicodeエンコード –

+0

-1が使用されることがあるとの質問があります。 Unicodeサポートの欠如は、この答えを実行可能にしません。 –

3

あなたは、このようなTFileStreamなどの任意TStreamオブジェクトから個々の行を、読み取るためにTStreamReaderを使用することができます。より高速なファイルI/Oの場合は、TCustomMemoryStreamのメモリマップビューを使用できます。

+0

TStreamReaderは同等のreadlineを行うことができますか? –

+0

私は答えとしてレミーの提案に基づいて例を書いた。 –

+0

@ワーレン:はい。 TStreamReaderには公開ReadLine()メソッドがあります。 –

2

私は最初の答えを削除しました。上記のレミーの最初の提案を使用して、私は組み込みのものでもう一度試しました。私がここで気に入らないのは、2つのオブジェクトを作成して解放する必要があるということです。私はこれを包むために私自身のクラスになるだろうと思う:

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; 

誰もが私が前にここに持っていたものに興味があるならば、それはユニコードのファイルで作業していないという問題がありました。

+0

ウォーレンの代わりになりました。私はAntonioが提案したようにTFileStreamを実装することができていました。他に何も試してみる必要はありません。私は代わりとしてこれを覚えています。 – lkessler

+0

+1はReadLineのために良い解決策ですが、これはもっと速いとは確信していません。 –

+0

TStreamReaderには、別のTStreamオブジェクトポインタの代わりにファイル名を指定できるコンストラクタがいくつかあります。 –

関連する問題