Java NIO学习系列七:Path、Files、AsynchronousFileChannel
相對(duì)于標(biāo)準(zhǔn)Java IO中通過File來指向文件和目錄,Java NIO中提供了更豐富的類來支持對(duì)文件和目錄的操作,不僅僅支持更多操作,還支持諸如異步讀寫等特性,本文我們就來學(xué)習(xí)一些Java NIO提供的和文件相關(guān)的類:
Java NIO Path
Java NIO Files
Java NIO AsynchronousFileChannel
? 總結(jié)
1.?Java NIO Path
Java Path是一個(gè)接口,位于java.nio.file包中,Java 7中引入到Java NIO中。
??一個(gè)Java Path實(shí)現(xiàn)的實(shí)例對(duì)象代表文件系統(tǒng)中的一個(gè)路徑,指向文件和目錄,(標(biāo)準(zhǔn)Java IO中是通過File來指向文件和路徑的),以絕對(duì)路徑或者相對(duì)路徑的方式。
??java.nio.file.Path接口很多方面類似于java.io.File類,但是兩者之間也是有細(xì)微的差別的。在大多數(shù)場(chǎng)景下是可以用Path來代替File的。
1.1 創(chuàng)建Path實(shí)例對(duì)象
??可以通過Paths類的靜態(tài)工廠方法get()來創(chuàng)建一個(gè)Path實(shí)例對(duì)象:
import java.nio.file.Path; import java.nio.file.Paths;public class PathExample {public static void main(String[] args) {Path path = Paths.get("c:\\data\\myfile.txt");} }1.2 Creating an Absolute Path
??通過直接指定絕對(duì)路徑可以創(chuàng)建使用絕對(duì)路徑方式指向文件的Path:
// windows系統(tǒng) Path path = Paths.get("c:\\data\\myfile.txt");// linux系統(tǒng) Path path = Paths.get("/home/jakobjenkov/myfile.txt");1.3 Creating a Relative Path
??通過如下方式可以創(chuàng)建使用相對(duì)路徑方式指向文件的Path:
Path projects = Paths.get("d:\\data", "projects");Path file = Paths.get("d:\\data", "projects\\a-project\\myfile.txt");采用相對(duì)路徑的方式時(shí),有兩個(gè)符號(hào)可以用來表示路徑:
- .
- ..
“.”可以表示當(dāng)前目錄,如下例子是打印當(dāng)前目錄(即應(yīng)用程序的根目錄):
Path currentDir = Paths.get("."); System.out.println(currentDir.toAbsolutePath());".."表示父文件夾。
??當(dāng)路徑中包含如上兩種符號(hào)時(shí),可以通過調(diào)用normalize()方法來將路徑規(guī)范化:
String originalPath = "d:\\data\\projects\\a-project\\..\\another-project";Path path1 = Paths.get(originalPath); System.out.println("path1 = " + path1);Path path2 = path1.normalize(); System.out.println("path2 = " + path2);輸出結(jié)果如下:
path1 = d:\data\projects\a-project\..\another-project path2 = d:\data\projects\another-project2.?Java NIO Files
Java NIO Files類(java.nio.file.Files)提供了一些方法用來操作文件,其是和上面提到的Path一起配合使用的。
2.1 Files.exists()
??該方法可以用來檢查Path指向的文件是否真實(shí)存在,直接看例子:
Path path = Paths.get("data/logging.properties");boolean pathExists = Files.exists(path, new LinkOption[]{ LinkOption.NOFOLLOW_LINKS});2.2 Files.createDirectory()
??該方法會(huì)在硬盤上創(chuàng)建一個(gè)新的目錄(即文件夾):
Path path = Paths.get("data/subdir"); try {Path newDir = Files.createDirectory(path); } catch(FileAlreadyExistsException e){// the directory already exists. } catch (IOException e) {//something else went wronge.printStackTrace(); }2.3 Files.copy()
??該方法會(huì)將文件從一個(gè)地方復(fù)制到另一個(gè)地方:
Path sourcePath = Paths.get("data/logging.properties"); Path destinationPath = Paths.get("data/logging-copy.properties"); try {Files.copy(sourcePath, destinationPath); } catch(FileAlreadyExistsException e) {//destination file already exists } catch (IOException e) {//something else went wronge.printStackTrace(); }如果目標(biāo)文件已存在,這里會(huì)拋出java.nio.file.FileAlreadyExistsException異常,想要強(qiáng)制覆蓋文件也是可以的:
Path sourcePath = Paths.get("data/logging.properties"); Path destinationPath = Paths.get("data/logging-copy.properties"); try {Files.copy(sourcePath, destinationPath,StandardCopyOption.REPLACE_EXISTING); } catch(FileAlreadyExistsException e) {//destination file already exists } catch (IOException e) {//something else went wronge.printStackTrace(); }2.4 Files.move()
??該方法能夠移動(dòng)文件,也可以實(shí)現(xiàn)重命名的效果:
Path sourcePath = Paths.get("data/logging-copy.properties"); Path destinationPath = Paths.get("data/subdir/logging-moved.properties"); try {Files.move(sourcePath, destinationPath,StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) {//moving file failed.e.printStackTrace(); }2.5 Files.delete()
??該方法能夠刪除Path實(shí)例指向的文件或目錄:
Path path = Paths.get("data/subdir/logging-moved.properties"); try {Files.delete(path); } catch (IOException e) {//deleting file failede.printStackTrace(); } Path path = Paths.get("data/subdir/logging-moved.properties"); try {Files.delete(path); } catch (IOException e) {//deleting file failede.printStackTrace(); }該方法刪除目錄時(shí)只能刪除空目錄,如果想刪除下面有文件的目錄則需要進(jìn)行遞歸刪除,后面會(huì)介紹。
2.6 Files.walkFileTree()
??該方法能夠遞歸地獲取目錄樹,該方法接收兩個(gè)參數(shù),一個(gè)是指向目標(biāo)目錄,另一個(gè)是一個(gè)FileVisitor類型對(duì)象:
Files.walkFileTree(path, new FileVisitor<Path>() {@Overridepublic FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {System.out.println("pre visit dir:" + dir);return FileVisitResult.CONTINUE;}@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {System.out.println("visit file: " + file);return FileVisitResult.CONTINUE;}@Overridepublic FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {System.out.println("visit file failed: " + file);return FileVisitResult.CONTINUE;}@Overridepublic FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {System.out.println("post visit directory: " + dir);return FileVisitResult.CONTINUE;} });FileVisitor是一個(gè)接口,你需要實(shí)現(xiàn)它,接口的定義如下:
public interface FileVisitor {public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException;public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException;public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException;public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {}該接口中包含4個(gè)方法,分別在目錄轉(zhuǎn)換的四個(gè)不同階段調(diào)用:
- preVisitDirectory()方法在訪問目錄之前調(diào)用,而postVisitorDirectory()方法是在訪問目錄之后調(diào)用;
- visitFile()方法會(huì)在訪問每個(gè)文件(訪問目錄是不會(huì)調(diào)用的)時(shí)調(diào)用一次,而visitorFileFailed()會(huì)在訪問文件失敗時(shí)被調(diào)用,比如沒有訪問權(quán)限或者別的問題。
??這四個(gè)方法都會(huì)返回一個(gè)FileVisitResult枚舉對(duì)象,包含如下成員:
- CONTINUE
- TERMINATE
- SKIP_SIBLINGS
- SKIP_SUBTREE
??被調(diào)用的如上四個(gè)方法通過這些返回值來判斷是否要繼續(xù)遍歷目錄。
- CONTINUE,意味著繼續(xù);
- TERMINATE,意味著終止;
- SKIP_SIBLINGS,意味著繼續(xù),但是不再訪問該文件或目錄的兄弟;
- SKIP_SUBTREE,意味著繼續(xù),但是不再訪問該目錄下的條目。只有preVisitDirectory()返回該值才有意義,其余三個(gè)方法返回則會(huì)當(dāng)做CONTINUE處理;
??如果不想自己實(shí)現(xiàn)該接口,也可以使用SimpleFileVisitor,這是一個(gè)默認(rèn)實(shí)現(xiàn),如下是一個(gè)利用SimpleFileVisitor來實(shí)現(xiàn)文件查找、刪除的例子:
遞歸查找文件
Path rootPath = Paths.get("data"); String fileToFind = File.separator + "README.txt"; try {Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {String fileString = file.toAbsolutePath().toString();if(fileString.endsWith(fileToFind)){System.out.println("file found at path: " + file.toAbsolutePath());return FileVisitResult.TERMINATE;}return FileVisitResult.CONTINUE;}}); } catch(IOException e){e.printStackTrace(); }遞歸刪除目錄
??因?yàn)閐elete()方法只能刪除空目錄,對(duì)于非空目錄則需要將其進(jìn)行遍歷以逐個(gè)刪除其子目錄或文件,可以通過walkFileTree()來實(shí)現(xiàn),在visitFile()方法中刪除子目錄,而在postVisitDirectory()方法中刪除該目錄本身:
Path rootPath = Paths.get("data/to-delete"); try {Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {System.out.println("delete file: " + file.toString());Files.delete(file);return FileVisitResult.CONTINUE;}@Overridepublic FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {Files.delete(dir);System.out.println("delete dir: " + dir.toString());return FileVisitResult.CONTINUE;}}); } catch(IOException e){e.printStackTrace(); }其實(shí)利用walkFileTree()方法,我們可以很輕松地指定自己的邏輯,而無需考慮是如何遍歷的,如果要用標(biāo)準(zhǔn)Java IO提供的File來實(shí)現(xiàn)類似功能我們還需要自己處理整個(gè)遍歷的過程。
2.7 其它有用方法
??java.nio.file.Files類還包含了很多別的有用方法,比如創(chuàng)建符號(hào)鏈接、文件大小、設(shè)置文件權(quán)限,這里就不一一介紹了,有興趣的可以參考Java官方文檔。
3.?Java NIO AsynchronousFileChannel
Java 7中引入了AsynchronousFileChannel,使得可以異步地讀寫數(shù)據(jù)到文件。
3.1 Creating an AsynchronousFileChannel
??通過其靜態(tài)方法可以創(chuàng)建一個(gè)AsynchronousFileChannel。
Path path = Paths.get("data/test.xml"); AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);第一個(gè)參數(shù)是一個(gè)指向要和AsynchronousFileChannel關(guān)聯(lián)的文件的Path實(shí)例。第二個(gè)參數(shù)代表要對(duì)文件指向的操作,這里我們指定StandardOpenOption.READ,意思是執(zhí)行讀操作。
3.2 Reading Data
??從AsynchronousFileChannel讀數(shù)據(jù)有兩種方式:
通過Future讀數(shù)據(jù)
??第一種方式是調(diào)用一個(gè)返回Future的read()方法:
Future<Integer> operation = fileChannel.read(buffer, 0);這個(gè)版本的read()方法,其第一個(gè)參數(shù)是一個(gè)ByteBuffer,數(shù)據(jù)從channel中讀到buffer中;第二個(gè)參數(shù)是要從文件中開始讀取的字節(jié)位置。
??該方法會(huì)馬上返回,即使讀操作實(shí)際上還沒有完成。通過調(diào)用Future的isDone()方法可以知道讀操作是否完成了。
??如下是一個(gè)更詳細(xì)的例子:
AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.READ); ByteBuffer buffer = ByteBuffer.allocate(1024); long position = 0; Future<Integer> operation = fileChannel.read(buffer, position); while(!operation.isDone()); buffer.flip(); byte[] data = new byte[buffer.limit()]; buffer.get(data); System.out.println(new String(data)); buffer.clear();? 在這個(gè)例子中,當(dāng)調(diào)用了AsynchronousFileChannel的read()方法之后,進(jìn)入循環(huán)直到Future對(duì)象的isDone()返回true。當(dāng)然這種方式并沒有有效利用CPU,只是因?yàn)楸纠行枰鹊阶x操作完成,其實(shí)這個(gè)等待過程我們可以讓線程做別的事情。
通過CompletionHandler讀數(shù)據(jù)
??第二種讀數(shù)據(jù)的方式是調(diào)用其包含CompletionHandler參數(shù)的read()方法:
fileChannel.read(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {@Overridepublic void completed(Integer result, ByteBuffer attachment) {System.out.println("result = " + result);attachment.flip();byte[] data = new byte[attachment.limit()];attachment.get(data);System.out.println(new String(data));attachment.clear();}@Overridepublic void failed(Throwable exc, ByteBuffer attachment) {} });當(dāng)讀操作完成之后會(huì)調(diào)用ComplementHandler的completed()方法,該方法的第一個(gè)入?yún)⑹且粋€(gè)整型變量,代表讀了多少字節(jié)數(shù)據(jù),第二個(gè)入?yún)⑹且粋€(gè)ByteBuffer,保存著已經(jīng)讀取的數(shù)據(jù)。
??如果讀失敗了,則會(huì)調(diào)用ComplementHandler的fail()方法。
3.3 Writing Data
??與讀類似,寫數(shù)據(jù)也支持兩種方式。
通過Future寫
??如下是一個(gè)寫數(shù)據(jù)的完整例子:
Path path = Paths.get("data/test-write.txt"); AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.WRITE);ByteBuffer buffer = ByteBuffer.allocate(1024); long position = 0; buffer.put("test data".getBytes()); buffer.flip();Future<Integer> operation = fileChannel.write(buffer, position); buffer.clear();while(!operation.isDone()); System.out.println("Write done");過程比較簡(jiǎn)單,就不講一遍了。這個(gè)例子中有一個(gè)問題需要注意,文件必須事先準(zhǔn)備好,如果不存在文件則會(huì)拋出java.nio.file.NoSuchFileException異常。
??可以通過如下方式判斷文件是否存在:
if(!Files.exists(path)){Files.createFile(path); }通過CompletionHandler寫數(shù)據(jù)
??可以借助CompletionHandler來通知寫操作已經(jīng)完成,示例如下:
Path path = Paths.get("data/test-write.txt"); if(!Files.exists(path)){Files.createFile(path); } AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.WRITE);ByteBuffer buffer = ByteBuffer.allocate(1024); long position = 0;buffer.put("test data".getBytes()); buffer.flip();fileChannel.write(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {@Overridepublic void completed(Integer result, ByteBuffer attachment) {System.out.println("bytes written: " + result);}@Overridepublic void failed(Throwable exc, ByteBuffer attachment) {System.out.println("Write failed");exc.printStackTrace();} }); System.out.println(“異步執(zhí)行哦”);如上是一個(gè)異步寫入數(shù)據(jù)的例子,為了演示效果,我特意在 調(diào)用write方法之后打印了一行日志,運(yùn)行結(jié)果如下:
異步執(zhí)行哦 bytes written: 9說明調(diào)用write方法并沒有阻塞,而是繼續(xù)往下執(zhí)行,所以先打印日志,然后數(shù)據(jù)寫好之后回調(diào)completed()方法。
4.?總結(jié)
本文總結(jié)了Java NIO中提供的對(duì)文件操作的相關(guān)類:Path、Files、AsynchronousFileChannel。
Path是一個(gè)接口,其實(shí)現(xiàn)實(shí)例可以指代一個(gè)文件或目錄,作用與Java IO中的File類似。Path接口很多方面類似于java.io.File類,但是兩者之間也是有細(xì)微的差別的,不過在大多數(shù)場(chǎng)景下是可以用Path來代替File的。
Files是一個(gè)類,提供了很多方法用來操作文件,是和上面提到的Path一起配合使用的,Files提供的對(duì)文件的操作功能要多于File。
AsynchronousFileChannel是Channel的子類,提供了異步讀取文件的能力。
總結(jié)
以上是生活随笔為你收集整理的Java NIO学习系列七:Path、Files、AsynchronousFileChannel的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java NIO学习系列六:Java中的
- 下一篇: Nginx - request_time