Mapcon
11-04-2003, 06:22 PM
ngờ uồn Tạp chí Tin học & nhà trường .
Tui thấy cũng đọc được nên post (post nguyên bản vì ko có thời gian edit lại :D) lên trên đây cho vui, ai quan tâm thì ngó chút . ;) :D
-------------------------------------------------
Sao chép files trong chế độ real-mode MS-DOS thì tốc độ chậm hơn hẳn so với Windows. Có nhiều lý do khác nhau, nhưng một trong những lýdo chính là các giải thuật sao chép files trong Windows được cải thiện hơn và có phần tối ưu hơn so với các phương thức sao chép files trong DOS. Tuy nhiên, ta vẫn có thể cải thiện vấn đề bằng cách tìm chương trình SMARTDRV.EXE và gõ lệnh SMARTDRV C+ ở DOS. Vấn đề này sẽ được đề cập kĩ hơn dưới đây.
Trong bài viết này tôi không có ý định đưa ra các đặc tính kĩ thuật của Windows hay của SMARTDRV.EXE, mà chỉ muốn cung cấp cho bạn đọc những ý tưởng chính của việc sao chép files, và một vài vấn đề cho bạn đọc tham khảo.
Để sao chép file trong Pascal, bạn cần phải biết các thao tác xử lý vào/ra file cơ bản, một vài lệnh và hàm vào/ra file thông dụng của Pascal như Reset, Rewrite, Eof, Close, Assign,… Khi viết một ứng dụng hoàn chỉnh bạn đọc cần bổ sung phần xử lý lỗi (dùng macro{$I-} {$I+} và hàm ioresult…) vào các chương trình mẫu dưới đây.
Giải thuật sao chép files đơn giản nhất
Để sao chép file, điều đầu tiên ta có thể nghĩ đến là đọc từng byte của file này và ghi vào file kia.
Minh họa ý tưởng này như sau:
Var f1: file of char;
f2: text;
ch: char;
Begin
If paramcount<>2 then exit;
Assign(f1,paramstr(1));reset(f1);
Assign(f2,paramstr(2));rewrite(f2);
While not eof(f1) do
Begin
Read(f1,ch);
Write(f2,ch);
End;
Close(f1);
Close(f2);
End.
Chú ý: Nếu f1 được khai báo kiểu text thì chương trình sẽ hoạt động sai, do các files ta chỉ ra có thể không phải là file văn bản. Và trong các chương trình giới thiệu ở đây, tôi giả thiết rằng nó làm việc trong điều kiện tối ưu, không có lỗi.
Chương trình trên hoạt động bình thường. Tuy nhiên, tốc độ quá chậm, đôi khi còn chậm hơn việc download từ Internet, phải mất gần 1 phút mới chép xong 1 file vài trăm KB. Và tốc độ này càng chậm hơn khi hoạt động trên đĩa mềm
Vì sao? Giả sử rằng chúng ta cần di chuyển n (n khá lớn) vật. Nếu một lần bạn chỉ di chuyển 1 vật thì có thể sẽ tốn thời gian. Và bạn cũng không thể di chuyển một lần cả n vật. Trên máy tính cũng vậy, nếu mỗi lần chỉ đọc và ghi 1 byte, thì máy sẽ cất và chuyển dịch đầu đọc nhiều lần, tốn thời gian.
Để cải thiện tốc độ, mỗi lần ta phải di chuyển x vật (0 < x < n+1) sao cho ít tốn công nhất, hay nói cách khác mỗi lần phải sao chép x bytes sao cho tốc độ sao chép là nhanh nhất. Đối với Pascal x < 64 KB, và 64KB cũng là số x thường được sử dụng trong Windows.
Đến đây chắc hẳn bạn đã hiểu SMARTDRV.EXE hoạt động ra sao. Giả sử chương trình chúng ta mỗi lần ghi 1 byte vào đĩa, nó sẽ cất trong vùng đệm (buffer) đến chừng nào đủ x bytes thì mới ghi, nên cải thiện được đáng kể tốc độ chép. Tuy nhiên, nếu chương trình chúng ta mỗi lần đọc 1 byte, thì SMARTDRV (và cả Windows) có thể sẽ chẳng giúp nhiều.
Mở rộng − tối ưu hóa giải thuật (optimizing algorithm)
Vậy thì chúng ta sẽ đọc một lần n bytes, và sau khi đã đọc đủ n bytes thì sẽ ghi n bytes đó ra đĩa. ở đây tôi chỉ minh họa giải thuật, bạn đọc tự mình xử lý trường hợp nếu như chưa đọc đủ kn bytes mà đã hết file (k > 0), nếu không chương trình sao chép sẽ làm mất dữ liệu, file kết quả bao giờ cũng có kích cỡ chia hết cho n.
Const nmax=32*1024;
Var f1: file of char;
F2: text;
a: array[1..nmax] of char;
i: longint;
Begin
If paramcount<>2 then exit;
Assign(f1,paramstr(1));reset(f1);
Assign(f2,paramstr(2));rewrite(f2);
While not eof(f1) do
Begin
For i:=1 to nmax do Read(f1,a[i]);
For i:=1 to nmax do Write(f2,a[i]);
End;
Close(f1);
Close(f2);
End.
Chương trình trên đây dù có nhanh hơn chưong trình ban đầu, nhưng vẫn còn quá chậm, bởi vì cho dù chúng ta đã đọc một lần n bytes, rồi ghi n bytes thế nhưng, chúng ta vẫn bắt buộc máy phải đọc mỗi lần một byte rồi cất chúng vào mảng a gọi là vùng đệm, rồi mới sao chép. Vì vậy thực chất máy vẫn phải di chuyển đầu đọc nhiều lần. Bạn đọc có thể sửa mảng a thành mảng của các chuỗi kí tự, rồi sửa chữa các biến khác cho phù hợp thì thấy tốc độ sao chép nhanh hơn (vì mỗi lần chuyển đầu đọc, máy đọc 255 bytes). Tuy nhiên bạn sẽ phải sửa chữa nmax thành nhỏ hơn (vì Pascal chỉ hỗ trợ dữ liệu đến 64KB) và việc xử lý trong chương trình sẽ vô cùng phức tạp. Rõ ràng sao chép files theo kiểu này là không hiệu quả.
Có thể bạn chưa biết nhưng Pascal hỗ trợ BlockRead và BlockWrite để sao chép file rất nhanh. Pascal chuẩn của Niklaus Wirth không có các lệnh này, nhưng hãng Borland và những người cải thiện Pascal thế hệ sau đã thêm cho Pascal đặc tính tuyệt vời đó.
Lệnh này đọc mỗi lần n bytes (cất vào vùng đệm) hay ít hơn nếu đã hết file vào bộ nhớ, sau đó có thể dùng BlockWrite để ghi ra đĩa với cú pháp tương tự như BlockRead. Máy tự xử lý vấn đề đọc được bao nhiêu bytes,... người dùng không cần quan tâm. Chỉ với một vài câu lệnh đơn giản, ta có một chương trình sao chép file tương đối tốt (không xử lý lỗi) như sau:
var
FromF, ToF: file;
NumRead, NumWritten: Word;
Buf: array[1..63*1024] of Char;
begin
Assign(FromF, ParamStr(1)); {Open input file}
Reset(FromF, 1); { Record size = 1 }
Assign(ToF, ParamStr(2)); {Open output file}
Rewrite(ToF, 1); { Record size = 1 }
Writeln('Copying ', FileSize(FromF), ' bytes...');
repeat
BlockRead(FromF, Buf, SizeOf(Buf), NumRead);
BlockWrite(ToF, Buf, NumRead, NumWritten);
until (NumRead = 0) or (NumWritten <> NumRead);
Close(FromF);
Close(ToF);
end.
Rõ ràng là chương trình này hiệu quả hơn hẳn, chép nhanh và ít sai sót hơn các chương trình ở trên. Bạn có thể tự bổ sung các phần kiểm tra lỗi khi chép. Dưới đây tôi chỉ minh hoạ cách mở rộng chương trình cho nhiều files.
ước tính thời lượng sao chép như trong Windows
Ta sẽ có công thức ước lượng thời gian còn lại trong quá trình sao chép file như sau:
Remaining = Elapsed x (Copied/Total - 1)
Bạn hãy tự chứng minh công thức trên và một cách tương tự, ước tính được tốc độ của quá trình sao chép file (KB/s)
Mở rộng cho nhiều files. Để sao chép nhiều files, bạn có thể sửa chữa chương trình mẫu ở trên thành một chương trình con và dùng FindFirst, FindNext để sao chép nhiều file trong cùng một thư mục.
Minh hoa ý tưởng này như sau:
uses Dos;
var DirInfo: SearchRec;
................
Procedure CopyFile(source, dest:string);
................
Procedure Prepare;
................
BEGIN
Prepare;
ChDir(SourceDir);
FindFirst(filemask, Archive, DirInfo);
while DosError = 0 do
begin
Writeln(DirInfo.Name);
................
NewFile:=Dest+′′+DirInfo.Name;
CopyFile(source,NewFile);
................
FindNext(DirInfo);
end;
................
END.
Trong đó Prepare là thủ tục test tham số, phân tích dạng file cần chép (filemask) và thư mục ngờ uốn (sourcedir) và đích đến (dest). Trong lệnh gọi CopyFile, source là tên file cần chép còn NewFile là tên file mới. Bạn đọc tự bổ sung những phần còn lại.
Duyệt cây thư mục dùng phép đệ quy (recursion) theo chiều sâu (depth)
Sau đây tôi minh hoạ cách duyệt tất cả các thư mục con của một thư mục. Bạn đọc tự bổ sung các thao tác như copy, move,... nếu muốn
{$M $FFF0,0,0}
Uses Dos;
Const {File Attributes Declarations}
ReadOnly=$01; Hiđen=$02; SysFile=$04; VolumeID=$08;
Directory=$10; Archive=$20; Anyfile=$3F;
................
Procedure Browse(path:string);
Var DirInfo:SearchRec;Attr:Word;
................
Begin
Path:=Path+′′;
FindFirst(Path+′*.*′,Anyfile-VolumeID, DirInfo);
While DosError=0 do
Begin
If (DirInfo.Attr=Directory) and (DirInfo.name<>′.′) and (DirInfo.Name <>′..′)
then Browse(path+DirInfo.name) {Continue browsing}
else if (DirInfo.name<>′.′) and (DirInfo.name <>′..′)
then ................ {Process the file};
FindNext(DirInfo);
End;
................
Macro $M trước chương trình tăng kích cỡ stack lên tối đa, nếu không, khi phải duyệt quá nhiều cấp thư mục g nhau dễ gây tràn stack và máy báo lỗi Runtime Error 005 (Stack Overflow Error)
Giải thuật di chuyển bấy lâu nay là moving=copying+deleting (di chuyển = sao chép + xóa). Với giải thuật này việc di chuyển sẽ chậm. Nếu say mê Asembly, bạn đọc có thể dùng hàm 56HEX (DOS 2+) [Rename/Move] để cải thiện giải thuật. Hàm này chỉ di chuyển điểm vào (entry point) của file trong bảng FAT, mà không thực sự di chuyển file.
Tui thấy cũng đọc được nên post (post nguyên bản vì ko có thời gian edit lại :D) lên trên đây cho vui, ai quan tâm thì ngó chút . ;) :D
-------------------------------------------------
Sao chép files trong chế độ real-mode MS-DOS thì tốc độ chậm hơn hẳn so với Windows. Có nhiều lý do khác nhau, nhưng một trong những lýdo chính là các giải thuật sao chép files trong Windows được cải thiện hơn và có phần tối ưu hơn so với các phương thức sao chép files trong DOS. Tuy nhiên, ta vẫn có thể cải thiện vấn đề bằng cách tìm chương trình SMARTDRV.EXE và gõ lệnh SMARTDRV C+ ở DOS. Vấn đề này sẽ được đề cập kĩ hơn dưới đây.
Trong bài viết này tôi không có ý định đưa ra các đặc tính kĩ thuật của Windows hay của SMARTDRV.EXE, mà chỉ muốn cung cấp cho bạn đọc những ý tưởng chính của việc sao chép files, và một vài vấn đề cho bạn đọc tham khảo.
Để sao chép file trong Pascal, bạn cần phải biết các thao tác xử lý vào/ra file cơ bản, một vài lệnh và hàm vào/ra file thông dụng của Pascal như Reset, Rewrite, Eof, Close, Assign,… Khi viết một ứng dụng hoàn chỉnh bạn đọc cần bổ sung phần xử lý lỗi (dùng macro{$I-} {$I+} và hàm ioresult…) vào các chương trình mẫu dưới đây.
Giải thuật sao chép files đơn giản nhất
Để sao chép file, điều đầu tiên ta có thể nghĩ đến là đọc từng byte của file này và ghi vào file kia.
Minh họa ý tưởng này như sau:
Var f1: file of char;
f2: text;
ch: char;
Begin
If paramcount<>2 then exit;
Assign(f1,paramstr(1));reset(f1);
Assign(f2,paramstr(2));rewrite(f2);
While not eof(f1) do
Begin
Read(f1,ch);
Write(f2,ch);
End;
Close(f1);
Close(f2);
End.
Chú ý: Nếu f1 được khai báo kiểu text thì chương trình sẽ hoạt động sai, do các files ta chỉ ra có thể không phải là file văn bản. Và trong các chương trình giới thiệu ở đây, tôi giả thiết rằng nó làm việc trong điều kiện tối ưu, không có lỗi.
Chương trình trên hoạt động bình thường. Tuy nhiên, tốc độ quá chậm, đôi khi còn chậm hơn việc download từ Internet, phải mất gần 1 phút mới chép xong 1 file vài trăm KB. Và tốc độ này càng chậm hơn khi hoạt động trên đĩa mềm
Vì sao? Giả sử rằng chúng ta cần di chuyển n (n khá lớn) vật. Nếu một lần bạn chỉ di chuyển 1 vật thì có thể sẽ tốn thời gian. Và bạn cũng không thể di chuyển một lần cả n vật. Trên máy tính cũng vậy, nếu mỗi lần chỉ đọc và ghi 1 byte, thì máy sẽ cất và chuyển dịch đầu đọc nhiều lần, tốn thời gian.
Để cải thiện tốc độ, mỗi lần ta phải di chuyển x vật (0 < x < n+1) sao cho ít tốn công nhất, hay nói cách khác mỗi lần phải sao chép x bytes sao cho tốc độ sao chép là nhanh nhất. Đối với Pascal x < 64 KB, và 64KB cũng là số x thường được sử dụng trong Windows.
Đến đây chắc hẳn bạn đã hiểu SMARTDRV.EXE hoạt động ra sao. Giả sử chương trình chúng ta mỗi lần ghi 1 byte vào đĩa, nó sẽ cất trong vùng đệm (buffer) đến chừng nào đủ x bytes thì mới ghi, nên cải thiện được đáng kể tốc độ chép. Tuy nhiên, nếu chương trình chúng ta mỗi lần đọc 1 byte, thì SMARTDRV (và cả Windows) có thể sẽ chẳng giúp nhiều.
Mở rộng − tối ưu hóa giải thuật (optimizing algorithm)
Vậy thì chúng ta sẽ đọc một lần n bytes, và sau khi đã đọc đủ n bytes thì sẽ ghi n bytes đó ra đĩa. ở đây tôi chỉ minh họa giải thuật, bạn đọc tự mình xử lý trường hợp nếu như chưa đọc đủ kn bytes mà đã hết file (k > 0), nếu không chương trình sao chép sẽ làm mất dữ liệu, file kết quả bao giờ cũng có kích cỡ chia hết cho n.
Const nmax=32*1024;
Var f1: file of char;
F2: text;
a: array[1..nmax] of char;
i: longint;
Begin
If paramcount<>2 then exit;
Assign(f1,paramstr(1));reset(f1);
Assign(f2,paramstr(2));rewrite(f2);
While not eof(f1) do
Begin
For i:=1 to nmax do Read(f1,a[i]);
For i:=1 to nmax do Write(f2,a[i]);
End;
Close(f1);
Close(f2);
End.
Chương trình trên đây dù có nhanh hơn chưong trình ban đầu, nhưng vẫn còn quá chậm, bởi vì cho dù chúng ta đã đọc một lần n bytes, rồi ghi n bytes thế nhưng, chúng ta vẫn bắt buộc máy phải đọc mỗi lần một byte rồi cất chúng vào mảng a gọi là vùng đệm, rồi mới sao chép. Vì vậy thực chất máy vẫn phải di chuyển đầu đọc nhiều lần. Bạn đọc có thể sửa mảng a thành mảng của các chuỗi kí tự, rồi sửa chữa các biến khác cho phù hợp thì thấy tốc độ sao chép nhanh hơn (vì mỗi lần chuyển đầu đọc, máy đọc 255 bytes). Tuy nhiên bạn sẽ phải sửa chữa nmax thành nhỏ hơn (vì Pascal chỉ hỗ trợ dữ liệu đến 64KB) và việc xử lý trong chương trình sẽ vô cùng phức tạp. Rõ ràng sao chép files theo kiểu này là không hiệu quả.
Có thể bạn chưa biết nhưng Pascal hỗ trợ BlockRead và BlockWrite để sao chép file rất nhanh. Pascal chuẩn của Niklaus Wirth không có các lệnh này, nhưng hãng Borland và những người cải thiện Pascal thế hệ sau đã thêm cho Pascal đặc tính tuyệt vời đó.
Lệnh này đọc mỗi lần n bytes (cất vào vùng đệm) hay ít hơn nếu đã hết file vào bộ nhớ, sau đó có thể dùng BlockWrite để ghi ra đĩa với cú pháp tương tự như BlockRead. Máy tự xử lý vấn đề đọc được bao nhiêu bytes,... người dùng không cần quan tâm. Chỉ với một vài câu lệnh đơn giản, ta có một chương trình sao chép file tương đối tốt (không xử lý lỗi) như sau:
var
FromF, ToF: file;
NumRead, NumWritten: Word;
Buf: array[1..63*1024] of Char;
begin
Assign(FromF, ParamStr(1)); {Open input file}
Reset(FromF, 1); { Record size = 1 }
Assign(ToF, ParamStr(2)); {Open output file}
Rewrite(ToF, 1); { Record size = 1 }
Writeln('Copying ', FileSize(FromF), ' bytes...');
repeat
BlockRead(FromF, Buf, SizeOf(Buf), NumRead);
BlockWrite(ToF, Buf, NumRead, NumWritten);
until (NumRead = 0) or (NumWritten <> NumRead);
Close(FromF);
Close(ToF);
end.
Rõ ràng là chương trình này hiệu quả hơn hẳn, chép nhanh và ít sai sót hơn các chương trình ở trên. Bạn có thể tự bổ sung các phần kiểm tra lỗi khi chép. Dưới đây tôi chỉ minh hoạ cách mở rộng chương trình cho nhiều files.
ước tính thời lượng sao chép như trong Windows
Ta sẽ có công thức ước lượng thời gian còn lại trong quá trình sao chép file như sau:
Remaining = Elapsed x (Copied/Total - 1)
Bạn hãy tự chứng minh công thức trên và một cách tương tự, ước tính được tốc độ của quá trình sao chép file (KB/s)
Mở rộng cho nhiều files. Để sao chép nhiều files, bạn có thể sửa chữa chương trình mẫu ở trên thành một chương trình con và dùng FindFirst, FindNext để sao chép nhiều file trong cùng một thư mục.
Minh hoa ý tưởng này như sau:
uses Dos;
var DirInfo: SearchRec;
................
Procedure CopyFile(source, dest:string);
................
Procedure Prepare;
................
BEGIN
Prepare;
ChDir(SourceDir);
FindFirst(filemask, Archive, DirInfo);
while DosError = 0 do
begin
Writeln(DirInfo.Name);
................
NewFile:=Dest+′′+DirInfo.Name;
CopyFile(source,NewFile);
................
FindNext(DirInfo);
end;
................
END.
Trong đó Prepare là thủ tục test tham số, phân tích dạng file cần chép (filemask) và thư mục ngờ uốn (sourcedir) và đích đến (dest). Trong lệnh gọi CopyFile, source là tên file cần chép còn NewFile là tên file mới. Bạn đọc tự bổ sung những phần còn lại.
Duyệt cây thư mục dùng phép đệ quy (recursion) theo chiều sâu (depth)
Sau đây tôi minh hoạ cách duyệt tất cả các thư mục con của một thư mục. Bạn đọc tự bổ sung các thao tác như copy, move,... nếu muốn
{$M $FFF0,0,0}
Uses Dos;
Const {File Attributes Declarations}
ReadOnly=$01; Hiđen=$02; SysFile=$04; VolumeID=$08;
Directory=$10; Archive=$20; Anyfile=$3F;
................
Procedure Browse(path:string);
Var DirInfo:SearchRec;Attr:Word;
................
Begin
Path:=Path+′′;
FindFirst(Path+′*.*′,Anyfile-VolumeID, DirInfo);
While DosError=0 do
Begin
If (DirInfo.Attr=Directory) and (DirInfo.name<>′.′) and (DirInfo.Name <>′..′)
then Browse(path+DirInfo.name) {Continue browsing}
else if (DirInfo.name<>′.′) and (DirInfo.name <>′..′)
then ................ {Process the file};
FindNext(DirInfo);
End;
................
Macro $M trước chương trình tăng kích cỡ stack lên tối đa, nếu không, khi phải duyệt quá nhiều cấp thư mục g nhau dễ gây tràn stack và máy báo lỗi Runtime Error 005 (Stack Overflow Error)
Giải thuật di chuyển bấy lâu nay là moving=copying+deleting (di chuyển = sao chép + xóa). Với giải thuật này việc di chuyển sẽ chậm. Nếu say mê Asembly, bạn đọc có thể dùng hàm 56HEX (DOS 2+) [Rename/Move] để cải thiện giải thuật. Hàm này chỉ di chuyển điểm vào (entry point) của file trong bảng FAT, mà không thực sự di chuyển file.