1.1 Đọc file một cách bất đồng bộ
V
V
Bạn cần đọc dữ liệu từ một file mà không phải dừng quá trình thực thi mã lệnh
của bạn. Kỹ thuật này thường được sử dụng khi file được lưu trữ trong một
nơi có tốc độ truy xuất chậm (chẳng hạn một đĩa mạng).
#
#
Tạo một lớp để đọc file một cách bất đồng bộ. Bắt đầu đọc một khối dữ liệu
bằng phương thức FileStream.BeginRead, và truyền phương thức callback.
Khi callback được kích hoạt, gọi FileStream.EndRead để truy xuất dữ liệu, xử
lý nó, và đọc khối dữ liệu kế tiếp với BeginRead.
FileStream hỗ trợ hoạt động bất đồng bộ thông qua phương thức BeginRead và EndRead.
Sử dụ
ng các phương thức này, bạn có thể đọc một khối dữ liệu trên một trong các tiểu
trình do thread-pool cung cấp mà không cần sử dụng trực tiếp các lớp tiểu trình trong
không gian tên System.Threading.
Khi đọc file một cách bất đồng bộ, bạn cần xác định kích thước khối dữ liệu trong một
lần đọc. Tùy trường hợp, bạn có thể muốn đọc một khối dữ liệu nhỏ (ví dụ, chép từng
khố
i một sang file khác) hoặc khối dữ liệu tương đối lớn (ví dụ, bạn cần một lượng thông
tin nhất định trước khi xử lý việc gì đó). Bạn chỉ định kích thước khối khi gọi BeginRead,
và truyền một bộ đệm để chứa dữ liệu. Vì BeginRead và EndRead cần truy xuất nhiều
mẩu thông tin giống nhau (chẳng hạn FileStream, bộ đệm, kích thước khối, ), bạn nên
đóng gói mã lệnh đọc file bấ
t đồng bộ trong một lớp.
Với lớp AsyncProcessor trong ví dụ dưới đây, phương thức công khai StartProcess bắt
đầu quá trình đọc bất đồng bộ. Mỗi khi quá trình đọc hoàn tất, OnCompletedRead được
kích hoạt và khối dữ liệu được xử lý. Nếu còn dữ liệu trong file, một quá trình đọc bất
đồng bộ mới sẽ được khởi chạy. Kích thước khối bộ nhớ là 2 KB (2048 byte).
using System;
using System.IO;
using System.Threading;
public class AsyncProcessor {
private Stream inputStream;
// Kích thước mỗi khối dữ liệu là 2 KB.
private int bufferSize = 2048;
public int BufferSize {
get {return bufferSize;}
set {bufferSize = value;}
}
// Bộ đệm chứa dữ liệu.
private byte[] buffer;
public AsyncProcessor(string fileName) {
buffer = new byte[bufferSize];
// Mở file, truyền giá trị true để hỗ trợ truy xuất bất đồng bộ.
inputStream = new FileStream(fileName, FileMode.Open,
FileAccess.Read, FileShare.Read, bufferSize, true);
}
public void StartProcess() {
// Bắt đầu quá trình đọc bất đồng bộ.
inputStream.BeginRead(buffer, 0, buffer.Length,
new AsyncCallback(OnCompletedRead), null);
}
private void OnCompletedRead(IAsyncResult asyncResult) {
// Một khối đã được đọc. Truy xuất dữ liệu.
int bytesRead = inputStream.EndRead(asyncResult);
// Nếu không đọc được byte nào, stream đang ở cuối file.
if (bytesRead > 0) {
// Tạm dừng để giả lập việc xử lý dữ liệu.
Console.WriteLine("\t[ASYNC READER]: Read one block.");
Thread.Sleep(TimeSpan.FromMilliseconds(20));
// Bắt đầu đọc khối kế tiếp.
inputStream.BeginRead(buffer, 0, buffer.Length,
new AsyncCallback(OnCompletedRead), null);
}else {
// Kết thúc.
Console.WriteLine("\t[ASYNC READER]: Complete.");
inputStream.Close();
}
}
}
Ví dụ dưới sử dụng AsyncProcessor để đọc một file có kích thước 1 MB.
public class AsynchronousIO {
public static void Main() {
// Tạo filethử nghiệm có kích thước 1MB.
FileStream fs = new FileStream("test.txt", FileMode.Create);
fs.SetLength(1000000);
fs.Close();
// Bắt đầu xử lý bất đồng bộ trên một tiểu trình khác.
AsyncProcessor asyncIO = new AsyncProcessor("test.txt");
asyncIO.StartProcess();
// Cùng thời điểm này, thực hiện công việc khác.
// Ở đây chúng ta sẽ lặp trong 10 giây.
DateTime startTime = DateTime.Now;
while (DateTime.Now.Subtract(startTime).TotalSeconds < 10) {
Console.WriteLine("[MAIN THREAD]: Doing some work.");
// Tạm dừng để giả lập một thao tác tốn nhiều thời gian.
Thread.Sleep(TimeSpan.FromMilliseconds(100));
}
Console.WriteLine("[MAIN THREAD]: Complete.");
Console.ReadLine();
// Xóa file thử.
File.Delete("test.txt");
}
}
Và đây là kết xuất khi chạy ứng dụng thử nghiệm:
[MAIN THREAD]: Doing some work.
[ASYNC READER]: Read one block.
[ASYNC READER]: Read one block.
[MAIN THREAD]: Doing some work.
[ASYNC READER]: Read one block.
[ASYNC READER]: Read one block.
[ASYNC READER]: Read one block.
[ASYNC READER]: Read one block.
[MAIN THREAD]: Doing some work.
[ASYNC READER]: Read one block.
[ASYNC READER]: Read one block.
[ASYNC READER]: Read one block.
. . .
1.2 Tìm file phù hợp một biểu thức wildcard
V
V
Bạn cần xử lý nhiều file có điểm chung, dựa vào biểu thức lọc như *.dll hay
mysheet20??.xls.
#
#
Sử dụng phiên bản nạp chồng của phương thức System.IO.DirectoryInfo.
GetFiles nhận một biểu thức lọc và trả về một mảng các đối tượng FileInfo.
Các đối tượng DirectoryInfo và Directory đều cho phép dò trong thưmục hiện hành để
tìm các file phù hợp với một biểu thức lọc. Các biểu thức này thường sử dụng các ký tự
wildcard như ? và *. Bạn cũng có thể sử dụng kỹ thuật t
ương tự để lấy các thưmục phù
hợp với một mẫu nhất định bằng phương thức nạp chồng DirectoryInfo.GetDirectories.
Ví dụ dưới đây sẽ lấy tên của tất cả các file trong một thưmục phù hợp với một biểu thức
lọc. Thưmụcvà biểu thức lọc được truyền qua dòng lệnh.
using System;
using System.IO;
public class WildcardTest {
private static void Main(string[] args) {
if (args.Length != 2) {
Console.WriteLine(
"USAGE: WildcardTest [directory] [filterExpression]");
return;
}
DirectoryInfo dir = new DirectoryInfo(args[0]);
FileInfo[] files = dir.GetFiles(args[1]);
// Hiển thị tên và kích thước file.
foreach (FileInfo file in files) {
Console.Write("Name: " + file.Name + " ");
Console.WriteLine("Size: " + file.Length.ToString());
}
Console.ReadLine();
}
}
Nếu muốn tìm trong thưmục con, bạn cần sử dụng đệ quy. Nhiều mục trong chương sử
dụng kỹ thuật đệ quy để xử lý file, chẳng hạn 9.3 và 9.4.
1.3 Kiểm tra hai file có trùng nhau hay không
V
V
Bạn cần so sánh nội dung của hai filevà xác định chúng có trùng nhau hay
không.
#
#
Tính mã băm của mỗi file bằng lớp
System.Security.Cryptography.HashAlgorithm rồi so sánh các mã băm.
Có nhiều cách để so sánh nhiều file. Ví dụ, có thể xét một phần của file xem có giống
nhau, hoặc đọc cả file so sánh từng byte. Cả hai cách trên đều đúng, nhưng trong một số
trường hợp, sử dụng mã băm thuận tiện hơn.
Một giải thuật băm sinh ra một dạng nhị phân đặc trưng (với kích thước nhỏ, thường
kho
ảng 20 byte) cho file. Có khả năng hai file khác nhau có cùng mã băm, nhưng khả
năng này hầu như không xảy ra. Thực tế, cả những thay đổi nhỏ nhất (chẳng hạn, chỉ thay
đổi một bit của file nguồn) cũng có 50% khả năng thay đổi các bit của mã băm. Do đó,
mã băm thường được sử dụng để phát hiện dữ liệu bị sửa đổi (mã băm sẽ được đề cập chi
tiết h
ơn trong chương 14).
Để tạo một mã băm, trước hết bạn phải tạo một đối tượng HashAlgorithm bằng phương
thức tĩnh HashAlgorithm.Create. Sau đó gọi HashAlgorithm.ComputeHash để nhận một
mảng byte chứa mã băm.
Ví dụ dưới đây đọc hai tên file từ đối số dòng lệnh và kiểm tra hai file này có trùng nhau
hay không:
using System;
using System.IO;
using System.Security.Cryptography;
public class CompareFiles {
private static void Main(string[] args) {
if (args.Length != 2) {
Console.WriteLine("USAGE: CompareFiles [fileName] " +
[fileName]");
return;
}
Console.WriteLine("Comparing " + args[0] + " and " + args[1]);
// Tạo đối tượng băm.
HashAlgorithm hashAlg = HashAlgorithm.Create();
// Tính mã băm cho filethứ nhất.
FileStream fsA = new FileStream(args[0], FileMode.Open);
byte[] hashBytesA = hashAlg.ComputeHash(fsA);
fsA.Close();
// Tính mã băm cho filethứ hai.
FileStream fsB = new FileStream(args[1], FileMode.Open);
byte[] hashBytesB = hashAlg.ComputeHash(fsB);
fsB.Close();
// So sánh mã băm.
if (BitConverter.ToString(hashBytesA) ==
BitConverter.ToString(hashBytesB)) {
Console.WriteLine("Files match.");
}else {
Console.WriteLine("No match.");
}
Console.ReadLine();
}
}
Các mã băm được so sánh bằng cách chuyển chúng thành chuỗi. Bạn cũng có thể duyệt
qua mảng và so sánh từng byte. Cách này nhanh hơn một ít, nhưng việc chuyển 20 byte
thành chuỗi không tốn nhiều chi phí nên không cần thiết.
1.4 Thao tác trên đường dẫn file
V
V
Bạn cần lấy một phần đường dẫn file hoặc kiểm tra một đường dẫn file có ở
dạng chuẩn hay không.
#
#
Xử lý đường dẫn bằng lớp System.IO.Path. Bạn có thể sử dụng
Path.GetFileName để lấy tên file từ đường dẫn, Path.ChangeExtension để thay
đổi phần mở rộng của đường dẫn, và Path.Combine để tạo đường dẫn đầy đủ
mà không cần quan tâm thưmục của bạn đã có ký tự phân cách thư mục(\)
hay chưa.
Thường khó thao tác với các đường dẫn file vì có vô số cách để mô tả một thư mục. Ví
dụ, bạn có thể sử dụng đường dẫn tuyệt đối (C:\Temp), đường dẫn UNC
(\\MyServer\\MyShare\Temp), hoặc một trong các đường dẫn tương đối
(C:\Temp\MyFiles\ \ hay C:\Temp\MyFiles\ \ \Temp).
Cách dễ nhất để xử lý các đường dẫn file là sử dụng các phương thức tĩnh của lớp Path để
bảo đảm có thông tin đúng. Ví dụ, đoạn mã sau trích tên file từ một đường dẫn file:
string filename = @" \System\MyFile.txt";
filename = Path.GetFileName(filename);
// filename bây giờ là "MyFile.txt".
Và đoạn mã sau sử dụng Path.Combine để thêm tên file vào đường dẫn thư mục:
string filename = @" \ \myfile.txt";
string fullPath = @"c:\Temp";
filename = Path.GetFileName(filename);
fullPath = Path.Combine(fullPath, filename);
// fullPath bây giờ là "c:\Temp\myfile.txt".
Cách này có ưu điểm là ký tự phân cách thưmục (\) sẽ tự động được thêm vào đường dẫn
nếu cần thiết.
Lớp Path cũng cung cấp các phương thức hữu ích sau đây để thao tác trên thông tin
đường dẫn:
• ChangeExtension—thay đổi phần mở rộng của file. Nếu phần mở rộng mới không
được chỉ định, phần mở rộng hiện tại sẽ bị xóa.
• GetDirectoryName—trả về thông tin của các thưmục nằm giữa ký tự phân cách thư
mục (\) đầu và cuối.
• GetFileNameWithoutExtension—tương tự như GetFileName, nhưng bỏ phần mở
rộng.
• GetFullPath—không có tác dụng đối với đường dẫn tuyệt đối, và nó sử dụng thư
mục hiện hành để đổi một đường dẫn tương đối thành đường dẫn tuyệt đối. Ví dụ,
nếu C:\Temp\ là thưmục hiện hành, gọi GetFullPath cho file test.txt sẽ trả về
C:\Temp\ test.txt.
• GetPathRoot—trả về chuỗi chứa thưmục gốc (ví dụ, "C:\"). Đối với đường dẫn
tương đối, nó trả về tham chiếu rỗng.
• HasExtension—trả về true nếu đường dẫn kết thúc với phần mở rộng.
• IsPathRooted—trả về true nếu đường dẫn là tuy
ệt đối, false nếu đường dẫn là tương
đối.
# Trong hầu hết trường hợp, một ngoại lệ sẽ bị ném nếu bạn truyền đường dẫn
không hợp lệ cho một trong các phương thức này (chẳng hạn, đường dẫn có
chứa các ký tự không hợp lệ). Tuy nhiên, những đường dẫn không hợp lệ do
chứa các ký tự wildcard sẽ không làm sinh ra ngoại lệ.
. xuất bất đồng bộ.
inputStream = new FileStream(fileName, FileMode.Open,
FileAccess.Read, FileShare.Read, bufferSize, true);
}
public void StartProcess(). tâm thư mục của bạn đã có ký tự phân cách thư mục( )
hay chưa.
Thư ng khó thao tác với các đường dẫn file vì có vô số cách để mô tả một thư mục. Ví
dụ,