2011-01-30 3 views
6

내 프로그램에서 내 프로그램에서 읽는 .ged 접미사가있는 텍스트 파일을 찾을 수있는 "파일 찾기"기능이 있습니다. 델파이의 여러 파일 중 몇 줄을 효율적으로 읽을 수 있습니까?

enter image description here

내가 표준의 FindFirst/FindNext 방법을 사용하고, 이것은 매우 빠르게 작동합니다 :이처럼 보이는 탐색기와 같은 창에 발견 된 결과를 표시합니다. 위에 표시된 584 개의 파일이 몇 초 내에 검색되고 표시됩니다.

내가 지금하고 싶은 것은 디스플레이에 두 개의 열을 추가하여 각 파일에 포함 된 "소스"와 "버전"을 표시하는 것입니다. 이 정보는 다음과 같다 라인에 각 파일의 처음 10 줄에서 일반적으로 발견된다 :

1 SOUR FTM 
2 VERS Family Tree Maker (20.0.0.368) 

가 지금은 매우 빠르게 자신이 구문 분석 아무런 문제가없고, 그게 내가 부탁 해요 것이 아니다.

내가 도움이 필요한 것은 단순히 이들 파일에서 처음 10 줄 정도를로드하여 구문 분석 할 수 있도록하는 것입니다.

StringList.LoadFromFile을 시도했지만, 1MB 이상의 큰 파일을로드하는 데 너무 많은 시간이 걸립니다.

처음 10 줄 정도만 필요하기 때문에 어떻게하면 좋을까요?

저는 델파이 2009를 사용하고 있습니다. 입력 파일은 유니 코드 일 수도 유니 코드가 아닐 수도 있습니다. 따라서 모든 인코딩에서 작동해야합니다. 잘 작동 감사 안토니오,

내가이 일을 결국 :


후속 조사

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로 자른 다음 TStrings로 나눕니다. –

+0

실제로 웜은 생각만큼 나쁘지 않습니다. 그것은 약 10 MB 초를 읽고로드 할 수 있습니다. 나는 그 파일들에서 텍스트들을 검색해야 할 때 그것을 성공적으로 사용한다. 하지만 전체 파일을로드하고 처음 몇 줄만 필요할 때 사용자가 40 초를 기다리는 이유는 무엇입니까? – lkessler

답변

14

사용 TFileStream을 필요 바이트의 읽기 방법 읽을 번호. 다음은 파일의 시작 부분에 저장된 비트 맵 정보를 읽는 예제입니다.

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

+4

+1 네이티브 OS 파일 API를 매우 멋지게 마무리하므로 TFileStream을 사용합니다. –

+5

+1. 첫 번째 4KB의 데이터를 읽는 것만으로도 처음 몇 줄을 충분히 포함 할 수 있으며 디스크에서 읽는 최소량의 데이터입니다. 많은 파일 (그리고 584 개의 파일이 정확히 "많이"는 아님)에서 읽는다면 멋진 기능을 원한다면 캐싱하지 않고 CreateFile을 사용하고 Handle을 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 블록만큼 빠름).

추신.또한 WinAPI 파일 함수 (CreateFile 등)에서 스레드 또는 비동기 기능을 사용하면 나머지 응용 프로그램이 작동하는 동안 비동기 적으로 파일에서 해당 데이터를로드 할 수 있습니다. 특히, 큰 디렉토리를 읽는 동안 인터페이스가 멈추지 않습니다.

실제 읽기 속도를 높이 지 않으면 서 파일로드가 직접로드되고 몇 밀리 초 후에 나머지 정보가 표시되기 때문에 정보로드 속도가 빨라집니다.

다른 방법을 시도했지만 추가 부스트가 필요하다고 생각하는 경우에만이 작업을 수행하십시오.

+0

FileRead/FileWrite는 API 함수입니다. –

+0

'ReadFile()'과'WriteFile()'은 Win32 API 함수입니다. 'FileRead()'와'FileWrite()'는 SysUtils 래퍼입니다. –

0

가끔 oldschool pascal 스타일이 그렇게 나쁘지 않습니다. 비 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) 메소드는 유니 코드를 지원하지 않습니다. –

+1

-1 @David와 같은 이유로 파일에 유니 코드 인코딩 –

+0

-1이 사용될 수 있습니다. 유니 코드 지원이 없기 때문에이 대답은 실행 가능하지 않습니다. –

3

당신은 같은 TFileStream 같은 TStream 객체에서 개별 라인을 읽어 TStreamReader를 사용할 수 있습니다. 더 빠른 파일 I/O의 경우, TCustomMemoryStream과 함께 Memory-Mapped Views를 사용할 수 있습니다.

+0

TStreamReader는 readline을 할 수 있는가? –

+0

필자는 Remy의 제안을 바탕으로 예제를 작성했다. –

+0

@ 워렌 : 네. TStreamReader에는 공개 ReadLine() 메서드가 있습니다. –

2

좋아요, 제 첫 대답을 삭제했습니다. 위의 레미 (Rey)의 첫 번째 제안을 사용하여 내장 된 항목으로 다시 시도했습니다. 여기서 싫어하는 것은 두 개의 객체를 만들어서 해제해야한다는 것입니다. 나는이 문제를 마무리하기 위해 내 자신의 클래스를 만들 것이라고 생각 : 사람, 그것은 유니 코드 파일로 작업하지 않는 문제가 있었다 내가 전에 여기에 있었다 무엇에 관심이

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

ReadLine 때문에 더 나은 솔루션을 얻으려면 +1하지만이 방법이 더 빠릅니다 * –

+0

TStreamReader에는 별도의 TStream 개체 포인터 대신 파일 이름을 지정할 수있는 여러 생성자가 있습니다. –

관련 문제