371 Chương 9: File, thư mục, và I/O "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. 11. 11. Ki m tra hai file có trùng nhau hay khôngể Ki m tra hai file có trùng nhau hay khôngể Bạn cần so sánh nội dung của hai file và 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; 372 Chương 9: File, thư mục, và I/O 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 file thứ nhất. FileStream fsA = new FileStream(args[0], FileMode.Open); byte[] hashBytesA = hashAlg.ComputeHash(fsA); fsA.Close(); // Tính mã băm cho file thứ 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."); } 373 Chương 9: File, thư mục, và I/O 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. 12. 12. Thao tác trên đ ng d n fileườ ẫ Thao tác trên đ ng d n fileườ ẫ 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. 374 Chương 9: File, thư mục, và I/O • 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ệ. 13. 13. Xác đ nh đ ng d n t ng ng v i m t file hay th m cị ườ ẫ ươ ứ ớ ộ ư ụ Xác đ nh đ ng d n t ng ng v i m t file hay th m cị ườ ẫ ươ ứ ớ ộ ư ụ Bạn có một đường dẫn (ở dạng chuỗi), và cần xác định nó tương ứng với một thư mục hay một file. Kiểm tra đường dẫn với phương thức Directory.Exists và File.Exists . Cả hai lớp System.IO.Directory và System.IO.File đều có phương thức Exists . • Directory.Exists —trả về true nếu đường dẫn (tương đối hoặc tuyệt đối) tương ứng với một thư mục đang tồn tại. • File.Exists —trả về true nếu đường dẫn tương ứng với một file đang tồn tại. Sử dụng hai phương thức này, bạn có thể nhanh chóng xác định một đường dẫn có tương ứng với một file hay thư mục hay không, như ví dụ sau: using System; using System.IO; public class FileOrPath { private static void Main(string[] args) { foreach (string arg in args) { 375 Chương 9: File, thư mục, và I/O Console.Write(arg); if (Directory.Exists(arg)) { Console.WriteLine(" is a directory"); } else if (File.Exists(arg)) { Console.WriteLine(" is a file"); } else { Console.WriteLine(" does not exist"); } } Console.ReadLine(); } } 14. 14. Làm vi c v i đ ng d n t ng đ iệ ớ ườ ẫ ươ ố Làm vi c v i đ ng d n t ng đ iệ ớ ườ ẫ ươ ố Bạn cần thiết lập thư mục làm việc hiện hành để có thể sử dụng đường dẫn tương đối trong mã lệnh của bạn. Sử dụng phương thức tĩnh GetCurrentDirectory và SetCurrentDirectory của lớp System.IO.Directory . Đường dẫn tương đối tự động được diễn dịch dựa trên quan hệ với thư mục hiện hành. Bạn có thể lấy thư mục hiện hành bằng phương thức Directory.GetCurrentDirectory hoặc thay đổi nó bằng phương thức Directory.SetCurrentDirectory . Ngoài ra, bạn có thể sử dụng phương thức tĩnh GetFullPath của lớp System.IO.Path để chuyển đường dẫn tương đối thành đường dẫn tuyệt đối. Dưới đây là một ví dụ minh họa: using System; using System.IO; public class RelativeDirTest { private static void Main() { Console.WriteLine("Using: " + Directory.GetCurrentDirectory()); Console.WriteLine("The relative path 'file.txt' " + "will automatically become: '" + 376 Chương 9: File, thư mục, và I/O Path.GetFullPath("file.txt") + "'"); Console.WriteLine(); Console.WriteLine("Changing current directory to C:\\"); Directory.SetCurrentDirectory("C:\\"); Console.WriteLine("Now the relative path 'file.txt' " + "will automatically become '" + Path.GetFullPath("file.txt") + "'"); Console.ReadLine(); } } Kết xuất của ứng dụng này có thể như sau (nếu bạn chạy ứng dụng trong thư mục C:\Temp): Using: C:\Temp The relative path 'file.txt' will automatically become 'C:\Temp\file.txt' Changing current directory to C:\ The relative path 'file.txt' will automatically become 'C:\file.txt' Nếu sử dụng đường dẫn tương đối, bạn nên thiết lập thư mục làm việc khi bắt đầu tương tác với file. Nếu không, có thể ảnh hưởng đến sự an toàn của hệ thống nếu người dùng cố ý thay đổi thư mục làm việc để ứng dụng của bạn truy xuất hoặc ghi đè file hệ thống. 15. 15. T o file t mạ ạ T o file t mạ ạ Bạn cần tạo một file sẽ được đặt trong thư mục tạm của người dùng cụ thể và sẽ có tên duy nhất, để nó không đụng độ với các file tạm được sinh ra bởi các chương trình khác. Sử dụng phương thức tĩnh GetTempFileName của lớp System.IO.Path , phương thức này trả về một đường dẫn kết hợp đường dẫn đến thư mục tạm với một tên file được tạo nhẫu nhiên. Có nhiều cách để tạo file tạm. Trong các trường hợp đơn giản, bạn chỉ cần tạo một file trong thư mục ứng dụng, có thể sử dụng một GUID hoặc một tem thời gian kết hợp với một giá trị ngẫu nhiên làm tên file. Tuy nhiên, lớp Path hỗ trợ một phương thức giúp bạn đỡ tốn công hơn. Nó sẽ tạo ra một file với tên duy nhất trong thư mục tạm của người dùng hiện hành, chẳng hạn C:\Documents and Settings\username\Local Settings\Temp\tmpac9.tmp. 377 Chương 9: File, thư mục, và I/O using System; using System.IO; public class TemporaryFile { private static void Main() { string tempFile = Path.GetTempFileName(); Console.WriteLine("Using " + tempFile); FileStream fs = new FileStream(tempFile, FileMode.Open); // (Ghi dữ liệu.) fs.Close(); // Xóa file. File.Delete(tempFile); Console.ReadLine(); } } 16. 16. L y dung l ng đĩa còn tr ngấ ượ ố L y dung l ng đĩa còn tr ngấ ượ ố Bạn cần xét một ổ đĩa và xác định còn bao nhiêu byte trống. Sử dụng hàm không-được-quản-lý Win32 API GetDiskFreeSpaceEx , (hàm này được khai báo trong kernell32.dll ). Không lớp nào trong các lớp về hệ thống file của .NET cho phép xác định dung lượng đĩa còn trống. Tuy nhiên, bạn có thể dễ dàng lấy được thông tin này bằng hàm Win32 API GetDiskFreeSpaceEx . Hàm này sẽ trả về dung lượng tổng cộng, dung lượng còn trống, và dung lượng còn trống có thể sử dụng được (nhà quản trị có thể sử dụng Disk Quota Management để hạn chế dung lượng mà người dùng có thể sử dụng được). 378 Chương 9: File, thư mục, và I/O Hình 9.2 Disk Quota Management Ứng dụng Console dưới đây minh họa kỹ thuật này: using System; using System.Runtime.InteropServices; public class GetFreeSpace { [DllImport("kernel32.dll", EntryPoint="GetDiskFreeSpaceExA" )] private static extern long GetDiskFreeSpaceEx( string lpDirectoryName, out long lpFreeBytesAvailableToCaller, out long lpTotalNumberOfBytes, out long lpTotalNumberOfFreeBytes); private static void Main() { long result, total, free, available; result = GetDiskFreeSpaceEx("c:", out available, out total, out free); 379 Chương 9: File, thư mục, và I/O if (result != 0) { Console.WriteLine("Total Bytes: {0:N}", total); Console.WriteLine("Free Bytes: {0:N}", free); Console.WriteLine("Available Bytes: {0:N}", available); } Console.ReadLine(); } } 17. 17. Hi n th các h p tho i fileể ị ộ ạ Hi n th các h p tho i fileể ị ộ ạ Bạn cần hiện các hộp thoại Windows chuẩn để mở, lưu file, và để chọn thư mục. Sử dụng các lớp OpenFileDialog , SaveFileDialog , và FolderBrowserDialog thuộc không gian tên System.Windows.Forms . Gọi phương thức ShowDialog để hiển thị hộp thoại, xét giá trị trả về để xác định người dùng đã nhấn OK hay Cancel , và lấy thông tin từ thuộc tính FileName hay SelectedPath . .NET cung cấp các đối tượng bọc lấy nhiều hộp thoại Windows chuẩn, bao gồm các hộp thoại dùng để mở và lưu file, và để chọn thư mục. Tất cả các lớp này đều thừa kế từ System.Windows.Forms.CommonDialog , bao gồm: • OpenFileDialog —cho phép người dùng chọn một file. Tên file và đường dẫn có thể được lấy từ thuộc tính FileName (hay tập hợp FileNames , nếu bạn cho phép chọn nhiều file bằng cách thiết lập Multiselect là true ). Ngoài ra, bạn có thể sử dụng thuộc tính Filter để chọn định dạng file và thiết lập thuộc tính CheckFileExists để kiểm tra tính hợp lệ (xem hình 9.3). [ 380 Chương 9: File, thư mục, và I/O Hình 9.3 OpenFileDialog • SaveFileDialog —cho phép người dùng chỉ định một file mới. Tên file và đường dẫn có thể được lấy từ thuộc tính FileName . Bạn có thể sử dụng thuộc tính Filter để chọn định dạng file và thiết lập các thuộc tính CreatePrompt và OverwritePrompt để hiển thị thông báo xác nhận khi người dùng chọn một file mới hay file đã tồn tại. • FolderBrowserDialog —cho phép người dùng chọn (và tạo) một thư mục. Đường dẫn đã chọn có thể được lấy từ thuộc tính SelectedPath . Ngoài ra, bạn có thể thiết lập thuộc tính ShowNewFolderButton để hiển thị nút Make New Folder (xem hình 9.4). Hình 9.4 FolderBrowserDialog Khi sử dụng OpenFileDialog hay SaveFileDialog , bạn cần thiết lập chuỗi lọc (chỉ định các phần mở rộng được phép). Chuỗi lọc được phân cách bởi ký tự "|" theo định dạng: "[Nhãn] | [Danh sách các phần mở rộng được phân cách bởi dấu chấm phẩy] | [Nhãn] | [Danh sách các phần mở rộng được phân cách bởi dấu chấm phẩy] | ". Bạn cũng có thể thiết lập thuộc tính Title (tiêu đề) và InitialDirectory (thư mục ban đầu). Ứng dụng dưới đây cho phép người dùng nạp tài liệu vào một RichTextBox , sửa nội dung, và lưu tài liệu đã được sửa (sử dụng lớp OpenFileDialog và SaveFileDialog để mở và lưu tài liệu). using System; using System.Drawing; using System.Windows.Forms; public class SimpleEditForm : System.Windows.Forms.Form { private System.Windows.Forms.MenuItem mnuFile; private System.Windows.Forms.MenuItem mnuOpen; . (\MyServer\MyShareTemp), hoặc một trong các đường dẫn tương đối (C:TempMyFiles hay C:TempMyFiles 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 . } } 17. 17. Hi n th các h p tho i fileể ị ộ ạ Hi n th các h p tho i fileể ị ộ ạ Bạn cần hiện các hộp thoại Windows chuẩn để mở, lưu file, và để chọn thư mục. Sử dụng các lớp OpenFileDialog ,. lập chuỗi lọc (chỉ định các phần mở rộng được phép). Chuỗi lọc được phân cách bởi ký tự "|" theo định dạng: "[Nhãn] | [Danh sách các phần mở rộng được phân cách bởi dấu chấm phẩy]