2011-01-30 26 views
6

Tôi có chức năng "Tìm tệp" trong chương trình của tôi để tìm tệp văn bản có hậu tố .ged mà chương trình của tôi đọc. Tôi hiển thị kết quả tìm thấy trong một cửa sổ thật bắt mắt trông như thế này:Làm thế nào tôi có thể đọc một cách hiệu quả FIrst Vài dòng của nhiều tệp trong Delphi

enter image description here

tôi sử dụng phương pháp FindFirst/FindNext tiêu chuẩn, và các công trình này rất nhanh chóng. 584 tệp được hiển thị ở trên được tìm thấy và hiển thị trong vài giây.

Điều tôi muốn làm là thêm hai cột vào màn hình hiển thị "Nguồn" và "Phiên bản" được chứa trong mỗi tệp này. Thông tin này thường được tìm thấy trong 10 dòng đầu tiên của mỗi tệp, trên các dòng giống như sau:

1 SOUR FTM 
2 VERS Family Tree Maker (20.0.0.368) 

Bây giờ tôi không phân tích vấn đề này một cách nhanh chóng, và đó không phải là những gì tôi yêu cầu.

Điều tôi cần trợ giúp chỉ đơn giản là cách tải nhanh nhất 10 dòng đầu tiên từ những tệp này xuống để tôi có thể phân tích cú pháp chúng.

Tôi đã cố gắng thực hiện một StringList.LoadFromFile, nhưng phải mất quá nhiều thời gian tải các tệp lớn, chẳng hạn như những tệp ở trên 1 MB.

Vì tôi chỉ cần 10 dòng đầu tiên hoặc hơn, làm thế nào để tôi có được chúng tốt nhất?

Tôi đang sử dụng Delphi 2009 và các tệp nhập của tôi có thể hoặc có thể không phải là Unicode, vì vậy điều này cần phải hoạt động đối với bất kỳ mã hóa nào.


followup: Cảm ơn Antonio,

tôi đã kết thúc làm điều này mà hoạt động tốt:

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 rất kém hiệu quả, hãy quên nó đi. Hãy suy nghĩ ra khỏi hộp và đọc hợp lý (ví dụ: NumLines * AvgLineLength) số byte, cắt ngắn với LineStart và sau đó chia thành TStrings –

+0

Thực ra, Worm, nó không tệ như bạn nghĩ. Nó có thể đọc và tải khoảng 10 MB một giây. Tôi vẫn sử dụng thành công nó khi tôi phải tìm kiếm các văn bản trong những tập tin đó. Nhưng tại sao lại sử dụng nó để tải toàn bộ tập tin và khiến người dùng chờ 40 giây khi chỉ cần vài dòng đầu tiên là cần thiết. – lkessler

Trả lời

14

Sử dụng TFileStream và với phương pháp đọc đọc số byte cần thiết. Dưới đây là ví dụ về đọc thông tin bitmap cũng được lưu trữ khi bắt đầu tệp.

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

+4

+1 Tôi sẽ sử dụng một TFileStream cho việc này vì nó kết thúc tốt đẹp API tệp hệ điều hành gốc rất độc đáo. –

+5

+1. Đơn giản chỉ cần đọc 4 Kbyte dữ liệu đầu tiên: Đó có thể là đủ để chứa đầy đủ các dòng đầu tiên, và đó là số lượng dữ liệu tối thiểu được đọc từ đĩa theo bất kỳ cách nào. Nếu bạn đang đọc từ nhiều tệp (và 584 tệp không chính xác là "nhiều") và bạn muốn có được ưa thích, bạn có thể muốn mở tệp mà không cần lưu vào bộ nhớ cache, bằng cách sử dụng CreateFile và chuyển Xử lý tới THANDLEStream: nó có thể cung cấp một số lượng nhỏ cải tiến bởi vì hệ điều hành không biết lưu trữ dữ liệu rất có khả năng sẽ không được yêu cầu lại. –

+2

TFileStream thiếu khả năng readLn. Điều gì xảy ra nếu có lẽ không đủ tốt? –

4

Chỉ cần mở file mình cho khối đọc (không sử dụng TStringList chức năng dựng sẵn), và đọc khối đầu tiên của tập tin, và sau đó bạn có thể ví dụ tải khối đó để một stringlist với strings.SetText() (nếu bạn đang sử dụng các hàm khối) hoặc đơn giản là strings.LoadFromStream() nếu bạn đang tải các khối của mình bằng cách sử dụng các luồng.

Tôi cá nhân chỉ cần đi với chức năng khối FileRead/FileWrite và tải khối vào bộ đệm. Bạn cũng có thể sử dụng các chức năng của similair winapi, nhưng đó chỉ là nhiều mã hơn mà không có lý do gì.

Hệ điều hành đọc tệp theo khối, ít nhất 512bytes trên hầu hết mọi nền tảng/hệ thống tệp, vì vậy bạn có thể đọc 512 byte trước (và hy vọng rằng bạn có tất cả 10 dòng, điều này sẽ đúng nếu các dòng của bạn thường ngắn đủ). Điều này sẽ được (thực tế) nhanh như đọc 100 hoặc 200 byte.

Sau đó, nếu bạn nhận thấy rằng các đối tượng chuỗi của bạn chỉ có dưới 10 dòng, chỉ cần đọc khối 512 byte tiếp theo và thử phân tích lại. (Hoặc chỉ cần đi với 1024, 2048 và như vậy trên khối, trên nhiều hệ thống nó có thể sẽ nhanh như 512 khối, như kích thước cluster hệ thống tập tin nói chung là lớn hơn 512 byte).

PS.Ngoài ra, bằng cách sử dụng các chủ đề hoặc chức năng không đồng bộ trong các hàm tệp winapi (CreateFile và như vậy), bạn có thể tải dữ liệu đó từ các tệp không đồng bộ, trong khi phần còn lại của ứng dụng của bạn hoạt động. Cụ thể, giao diện sẽ không bị đóng băng trong khi đọc các thư mục lớn.

Điều này sẽ làm cho việc tải thông tin của bạn xuất hiện nhanh hơn, (vì danh sách tệp sẽ tải trực tiếp, và sau đó một vài phần nghìn giây phần còn lại của thông tin sẽ xuất hiện), trong khi không thực sự tăng tốc độ đọc thực.

Chỉ thực hiện việc này nếu bạn đã thử các phương pháp khác và bạn cảm thấy như bạn cần tăng thêm.

+0

FileRead/FileWrite là các hàm API –

+0

'ReadFile()' và 'WriteFile()' là các hàm API của Win32. 'FileRead()' và 'FileWrite()' là các trình bao bọc SysUtils xung quanh chúng. –

0

Đôi khi phong cách pascal cũ không phải là xấu. Mặc dù truy cập tập tin không phải là oo không có vẻ là rất phổ biến nữa, ReadLn(F,xxx) vẫn hoạt động khá ok trong các tình huống như của bạn.

Mã bên dưới tải thông tin (tên tệp, nguồn và phiên bản) vào TDictionary để bạn có thể tra cứu dễ dàng hoặc có thể sử dụng chế độ xem danh sách ở chế độ ảo và tìm nội dung trong danh sách này khi số ondata .

Cảnh báo: mã bên dưới không hoạt động với 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

Chỉ cần nhớ rằng các phương thức Read/Write (Ln) trong D2009 + làm * NOT * hỗ trợ unicode. –

+1

-1 Câu hỏi nói rằng các tập tin có thể sử dụng mã hóa Unicode –

+0

-1 vì lý do tương tự như @David. Thiếu hỗ trợ Unicode làm cho câu trả lời này không khả thi. –

3

Bạn có thể sử dụng một TStreamReader để đọc các dòng cá nhân từ bất kỳ đối tượng TStream, chẳng hạn như một TFileStream. Đối với I/O tệp nhanh hơn, bạn có thể sử dụng Chế độ xem bộ nhớ được ánh xạ với TCustomMemoryStream.

+0

TStreamReader có thể làm một tương đương readline? –

+0

Tôi đã viết một ví dụ dựa trên gợi ý của Remy, như câu trả lời của tôi. –

+0

@Warren: Có. TStreamReader có sẵn phương thức ReadLine() công khai. –

2

OK, tôi đã xóa câu trả lời đầu tiên của mình. Sử dụng gợi ý đầu tiên của Remy ở trên, tôi đã thử lại với công cụ tích hợp sẵn. Những gì tôi không thích ở đây là bạn phải tạo và giải phóng hai đối tượng. Tôi nghĩ rằng tôi sẽ làm cho lớp học của riêng tôi để bọc này lên:

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; 

Nếu ai quan tâm đến những gì tôi đã có ở đây trước đó, nó có vấn đề không làm việc với các tập tin unicode.

+0

Cảm ơn vì sự thay thế, Warren. Tôi đã quản lý để thực hiện TFileStream như Antonio gợi ý, và nó hoạt động tốt đến mức tôi không phải thử bất cứ thứ gì khác. Tôi sẽ nhớ điều này như là một thay thế, mặc dù. – lkessler

+0

+1 cho giải pháp tốt hơn vì ReadLine, nhưng tôi không chắc rằng đây là * nhanh hơn * –

+0

TStreamReader có một số hàm tạo cho phép bạn chỉ định tên tệp thay vì một con trỏ đối tượng TStream riêng biệt. –

Các vấn đề liên quan