Java: Lớp Path
Lớp Path được giới thiệu từ phiên bản Java SE 7, là một trong những điểm đầu vào chính của gói java.nio.file
. Nếu ứng dụng của bạn sử dụng tập tin I/O thì bạn sẽ cần tìm hiểu về những tính năng mạnh mẽ của lớp này.
Lưu ý phiên bản: Nếu bạn đã có mã lệnh ứng với phiên bản trước JDK7 và muốn sử dụng
java.io.File
, thì bạn vẫn có thể tận dụng lợi thế của lớp Path bằng cách sử dụng phương thức File.toPath
.
Như tên gọi của nó, lớp Path là một đại diện chương trình của một đường dẫn trong hệ thống tập tin. Mỗi đối tượng Path
có chứa danh sách tên tập tin và thư mục dùng để xây dựng đường dẫn, và được sử dụng để kiểm tra, xác định vị trí và thao tác tập tin.
Đối tượng của lớp Path
phản ánh nền tảng cơ bản. Trong hệ điều hành Solaris thì Path sử dụng cú pháp Solaris (ví dụ: /home/joe/foo
) và trong Microsoft Windows thì nó sử dụng cú pháp Windows (ví dụ như C:\home\joe\foo
). Path không phải là hệ thống độc lập. Ta không thể so sánh Path ở hệ thống tập tin Solaris và mong muốn nó để phù hợp với Path ở hệ thống tập tin Windows, ngay cả khi các cấu trúc thư mục là giống nhau và cả hai trường hợp xác định vị trí các tập tin tương đối giống nhau.
Các tập tin hoặc thư mục tương ứng với Path
có thể không tồn tại. Bạn có thể tạo ra một đối tượng Path và thao tác với nó theo nhiều cách khác nhau: bạn có thể gắn thêm phần đường dẫn vào nó, trích ra đường dẫn từ nó, so sánh nó với đường dẫn khác. Tại thời điểm thích hợp, ta có thể sử dụng các phương thức trong lớp File để kiểm tra sự tồn tại của tập tin tương ứng với đường dẫn, tạo ra các tập tin, mở nó, xóa, thay đổi quyền hạn của nó.
Lớp Path có nhiều phương thức khác nhau có thể được sử dụng để có được thông tin về đường dẫn, các thành phần truy cập của đường dẫn, chuyển đổi đường dẫn sang các dạng khác, hoặc trích xuất một phần đường dẫn. Ngoài ra còn có phương thức phù hợp với các chuỗi đường dẫn và phương thức để loại bỏ phần thừa của đường dẫn. Bài học này đề cập đến những phương thức của lớp Path, đôi khi được gọi là các hoạt động cú pháp, bởi vì chúng hoạt động trên đường dẫn riêng và không truy cập vào hệ thống tập tin.
Phần này bao gồm những điều sau đây:
- Tạo một đường dẫn
- Lấy thông tin về đường dẫn
- Loại bỏ dư thừa khỏi đường dẫn
- Chuyển đổi đường dẫn
- Nối hai đường dẫn
- Tạo một đường dẫn từ hai đường dẫn
- So sánh hai đường dẫn
Tạo Path
Một đường dẫn
có chứa các thông tin được sử dụng để xác định vị trí của một tập tin hoặc thư mục. Tại thời điểm được xác định, một đường dẫn
được cung cấp với một loạt của một hoặc nhiều tên. Một phần tử gốc hoặc tên một tập tin có thể được bao gồm, nhưng không phải là bắt buộc. Một đường dẫn
có thể bao gồm chỉ một thư mục hoặc tên tập tin duy nhất.
Bạn có thể dễ dàng tạo ra một đối tượng đường dẫn
bằng cách sử dụng một trong các phương thức get
sau đây từ lớp trợ giúp Paths:
Path p2 = Paths.get(args[0]);
Path p3 = Paths.get(URI.create("file:///Users/joe/FileTest.java"));
Phương thức Paths.get
là cách viết tắt của đoạn mã sau đây:
Path p4 = FileSystems.getDefault().getPath("/users/sally");
Ví dụ sau tạo đường dẫn /u/joe/logs/foo.log
với giả sử rằng thư mục home của ta là /u/joe
, hoặc C:\joe\logs\foo.log
nếu bạn đang sử dụng hệ điều hành Windows.
Path p5 = Paths.get(System.getProperty( "user.home"),"logs", "foo.log");
Truy xuất thông tin về đường dẫn
Bạn có thể nghĩ đến đường dẫn giống như việc lưu trữ tên của những phần tử thành một chuỗi. Các phần tử cao nhất trong cấu trúc thư mục sẽ được đặt chỉ số 0, các phần tử thấp nhất trong cấu trúc thư mục sẽ được đặt chỉ số [n-1]
, với n
là số lượng tên các phần tử trong đường dẫn
. Các phương thức có sẵn dùng để truy xuất các phần tử riêng biệt hoặc một chuỗi con các đường dẫn
sử dụng các chỉ số.
Các ví dụ trong bài viết này sử dụng cấu trúc thư mục sau:
Cấu trúc thư mục mẫu
Đoạn mã sau định nghĩa một đối tượng Path và sau đó gọi một số phương thức để có được thông tin về đường dẫn:
// Không một phương thức nào trong số những phương thức này yêu cầu rằng các tập tin tương ứng với Path phải tồn tại.
// Cú pháp Microsoft Windows
Path path = Paths.get("C:\\home\\joe\\foo");
// Cú pháp Solaris
Path path = Paths.get( "/home/joe/foo");
System.out.format("toString: %s%n", path.toString());
System.out.format("getFileName: %s%n", path.getFileName());
System.out.format("getName(0): %s%n", path.getName(0));
System.out.format("getNameCount: %d%n", path.getNameCount());
System.out.format("subpath(0,2): %s%n", path.subpath(0,2));
System.out.format("getParent: %s%n", path.getParent());
System.out.format("getRoot: %s%n", path.getRoot());
Đây là output cho cả hệ điều hành Windows và Solaris:
Phương thức được gọi | Solaris | Microsoft Windows | Chú thích |
---|---|---|---|
toString | /home/joe/foo | C:\home\joe\foo | Trả về chuỗi thể hiện đường dẫn. Nếu đường dẫn đã được tạo ra sử dụng Filesystems.getDefault().GetPath(String) hoặc Paths.get(sau này là phương thức thuận tiện cho getPath), thì phương thức thực hiện việc xóa cú pháp nhỏ. Ví dụ, trong một hệ điều hành UNIX, nó sẽ sửa chuỗi đầu vào //home/joe/foo thành /home/joe/foo. |
getFileName | foo | foo | Trả về tên tập tin hoặc thành phần cuối cùng của chuỗi tên các thành phần. |
getName (0) | home | home | Trả về thành phần đường dẫn tương ứng với chỉ số xác định. Thành phần chỉ số 0 là thành phần gần gốc nhất. |
getNameCount | 3 | 3 | Trả về số lượng các thành phần trong đường dẫn. |
subpath(0,2) | home/joe | home\joe | Trả về dãy con của các đường dẫn (không bao gồm thành phần gốc) theo quy định của các chỉ số đầu và chỉ số kết thúc. |
getParent | /home/joe | \home\joe | Trả về đường dẫn của thư mục cha. |
getRoot | / | C:\ | Trả về gốc của đường dẫn. |
Ví dụ trên cho thấy đầu ra cho một đường dẫn tuyệt đối. Trong ví dụ sau đây, một đường dẫn tương đối được xác định:
Path path = Paths.get("sally/bar");
//hoặc
// Cú pháp Microsoft Windows
Path path = Paths.get("sally\\bar");
Còn dây là đầu ra dành cho Windows và Solaris:
Phương thức được gọi | Solaris | Returns trong Microsoft Windows |
---|---|---|
toString | sally/bar | sally\bar |
getFileName | bar | bar |
getName(0) | sally | sally |
getNameCount | 2 | 2 |
subpath(0,1) | sally | sally |
getParent | sally | sally |
getRoot | null | null |
Loại bỏ dư thừa từ đường dẫn
Nhiều hệ thống tập tin sử dụng ký hiệu "." để biểu thị thư mục hiện thời và ".." để biểu thị các thư mục cha. Có thể có tình huống trong đó đường dẫn
chứa thông tin thư mục dự phòng. Có lẽ một máy chủ được cấu hình để lưu file log của nó trong thư mục "/dir/logs/.
", và ta muốn xóa các dấu ký hiệu "/.
" từ đường dẫn.
Hai ví dụ dưới đây cho thấy sự dư thừa:
/home/sally/../joe/foo
Phương thức normalize loại bỏ bất kỳ thành phần dư thừa nào, trong đó bao gồm cả ".
" và "directory/..
". Cả hai ví dụ trên đều sẽ được chuyển thành dạng thông thường là /home/joe/foo
.
Có điều quan trọng cần lưu ý là phương thức normalize không kiểm tra tại các hệ thống tập tin khi nó làm sạch một đường dẫn. Đây là hoạt động hoàn toàn tuân theo cú pháp. Trong ví dụ thứ hai, nếu sally
là một liên kết tượng trưng, thì việc loại bỏ sally/..
có thể dẫn đến là không còn định vị đúng đường dẫn tới các tập tin mong muốn.
Để làm sạch một đường dẫn trong khi đảm bảo rằng kết quả của việc định vị các tập tin là chính xác, ta có thể sử dụng phương thức toRealPath
. Phương thức này được mô tả trong các phần tiếp sau đây.
Chuyển đổi đường dẫn
Bạn có thể sử dụng ba phương thức để chuyển đổi các đường dẫn
. Nếu bạn cần phải chuyển đổi đường dẫn thành một chuỗi có thể được mở ra từ một trình duyệt, bạn có thể sử dụng toUri
. Ví dụ:
//Kết quả là file: ///home/logfile
System.out.format( "% s%n", p1.toUri());
Các phương thức toAbsolutePath chuyển đổi một đường dẫn thành một đường dẫn tuyệt đối, nếu thành công thì nó trả về cùng một đối tượng Path. phương thức toAbsolutePath
có thể rất hữu ích khi xử lý tên tập tin người dùng nhập vào. Ví dụ:
public class FileTest {
public static void main(String [] args) {
if (args.length <1) {
System.out.println("sử dụng: tập tin FileTest");
System.exit(-1);
}
// Chuyển đổi một chuỗi đầu vào thành một đối tượng Path.
Path inputPath = Paths.get(args [0]);
/* Chuyển đổi đầu vào là Path thành một đường dẫn tuyệt đối.*/
// Nói chung, điều này có nghĩa là thêm vào trước thư mục làm việc hiện thời
// Nếu ví dụ này được gọi là như thế này:
// java FileTest foo
// thì các phương thức getRoot và getParent sẽ trả về null trên đối tượng "inputPath" gốc
// Lời gọi tới getRoot và getParent trên đối tượng "fullpath" sẽ trả về giá trị mong muốn
Path fullpath = inputPath.toAbsolutePath ();
}
}
Phương thức toAbsolutePath
các đầu vào từ người dùng và trả về một đối tượng Path là những giá trị hữu ích khi truy vấn. Các tập tin không cần phải tồn tại cho phương thức này để làm việc.
Phương thức toRealPath
trả về một đường dẫn thực sự (real) của tập tin hiện có. Phương thức này thực hiện một số hoạt động như sau:
- Nếu
true
được truyền tới với phương thức này và các hệ thống tập tin hỗ trợ các liên kết tượng trưng, thì phương thức này giải quyết bất kỳ liên kết tượng trưng nào trong đường dẫn. - Nếu
Path
là tương đối, nó trả về một đường dẫn tuyệt đối. - Nếu Path có chứa bất kỳ thành phần dư thừa nào thì nó sẽ trả về một đường dẫn đã được loại bỏ dư thừa đó.
Phương thức này ném một ngoại lệ nếu các tập tin không tồn tại hoặc không thể truy cập. Bạn có thể bắt ngoại lệ khi bạn muốn để xử lý bất kỳ trường hợp nào. Ví dụ:
Path fp = path.toRealPath();
} catch(NoSuchFileException x) {
System.err.format( "%s: không có" + "tập tin hay thư mục nào%n", path;
// Logic cho trường hợp khi tập tin không tồn tại.
} catch(IOException x) {
System.err.format( "%s%n", x);
// logic cho trường hợp lỗi tập tin khác.
}
Nối hai đường dẫn
Ta có thể kết hợp các đường dẫn bằng cách sử dụng phương thức resolve
. Khi ta truyền vào một đoạn đường dẫn mà không bao gồm thành phần gốc, thì phần đường dẫn đó sẽ được nối vào đường dẫn gốc.
Ví dụ, hãy xem xét các đoạn mã sau:
// Solaris
Path p1 = Paths.get("/home/joe/ foo");
// Nối thêm bar để được kết quả là /home/joe/foo/ bar
System.out.format("%s%n", p1.resolve("bar"));
//hoặc:
// Microsoft Windows
Path p1 = Paths.get("C:\\home\\joe\\foo");
System.out.format("%sn%", p1.resolve("bar"));
Nếu truyền một đường dẫn tuyệt đối tới phương thức resolve thì nó sẽ trả về chính đường dẫn đó:
Paths.get("foo").resolve("/home/joe");
Tạo một đường dẫn giữa hai đường dẫn
Một yêu cầu phổ biến khi ta đang viết mã lệnh cho tập tin I/O là khả năng để xây dựng một đường đi từ một vị trí trong hệ thống tập tin đến vị trí khác. Bạn có thể đáp ứng điều này bằng cách sử dụng phương thức relativize
. Phương thức này xây dựng một đường dẫn có nguồn gốc từ các đường dẫn ban đầu và kết thúc ở vị trí quy định bởi đường dẫn đã được truyền vào. Đường dẫn mới sẽ là tương đối với đường dẫn ban đầu.
Ví dụ, hãy xét hai đường dẫn tương đối là joe
và sally
như sau:
Path p2 = Paths.get( "sally");
Trong trường hợp không có bất kỳ thông tin nào khác thì ta giả định rằng joe
và sally
là "anh chị em", có nghĩa chúng là các nút cùng cấp trong cây phân cấp. Để điều hướng từ joe
tới sally
thì ta sẽ đặt joe là nút cha còn sally
là nút con và ngược lại:
Path p1_to_p2 = p1.relativize(p2);
// Kết quả là ../joe
Path p2_to_p1 = p2.relativize (p1);
Hãy xét một ví dụ phức tạp hơn:
Path p3 = Paths.get( "home/sally/bar");
// Kết quả là sally/bar
Path p1_to_p3 = p1.relativize(p3);
// Kết quả là ../..
Path p3_to_p1 = p3.relativize(p1);
Trong ví dụ này, hai đường dẫn chia sẻ cùng một nút là nút home. Để điều hướng từ home tới bar, trước tiên ta điều hướng home xuống sally
rồi sau đó điều hướng tiếp tới bar. Hướng từ bar
đến home
đòi hỏi phải điều hướng lên hai cấp.
Một đường dẫn tương đối không thể được xây dựng nếu chỉ một trong các đường dẫn chứa thành phần gốc. Nếu cả hai đường dẫn chứa thàn phần gốc thì khả năng xây dựng một đường dẫn tương đối là hệ thống phụ thuộc.
Bạn có thể xem thêm ví dụ đệ quy C
trong đó có sử dụng các phương thức opy
relativize
và resolve
.
So sánh hai đường dẫn
Lớp Path hỗ trợ equals, điều này cho phép ta kiểm tra hai đường đường dẫn xem chúng có giống nhau không. Các phương thức startsWith
và endsWith
cho phép ta kiểm tra xem một đường dẫn có bắt đầu hay kết thúc với một chuỗi cụ thể không. Những phương thức này rất dễ sử dụng. Ví dụ:
Path path = ...;
Path otherPath = ...;
Path beginning = Paths.get("/home");
Path ending = Paths.get("foo");
if(path.equals(otherPath)) {
/ / logic bằng nhau ở đây
} else if (path.startsWith(beginning)) {
// đường dẫn bắt đầu với "/home"
} else if (path.endsWith(kết thúc)) {
// đường dẫn kết thúc với "foo"
}
Lớp Path cũng thực thi giao diện Iterable
. Phương thức iterator
trả về một đối tượng cho phép ta duyệt qua tên của các thành tên trong đường dẫn. Thành phàn đầu tiên trả về là thành phần gần với gốc nhất trong cây thư mục. Đoạn mã sau đây sẽ lặp trên một đường dẫn để in tên của từng thành phần trong đường dẫn đó:
for (Path name : path) {
System.out.println (name);
}
Lớp Path cũng thực thi giao diện Comparable. Ta có thể so sánh các đối tượng Path
bằng cách sử dụng phương thức compareTo
, nó rất hữu ích cho thao tác sắp xếp. Đương nhiên ta cũng có thể thêm các đối tượng Path vào một tập hợp được (Collection)
.
Khi ta muốn xác minh rằng hai đối tượng Path
cùng định vị trí đến một tập tin, thì ta có thể sử dụng phương thức isSameFile
, nó được mô tả trong bài viết Kiểm tra file hoặc thư mục.