Introduction to the Java NIO2 File API
NIO2中的文件API是Java 7附帶的Java平台的主要新功能之一,特別是新的文件系統API的一個子集以及Path APIs。
在用戶的主目錄(home directory)下操作,使得對於所有的操作系統都是有效的。[內部是如何實現的呢???]
private static String HOME = System.getProperty("user.home");
Files類是Java.nio.file包的一個主要入口。這個類提供了豐富的APIs來讀,寫,操縱文件和目錄。Files類方法在Path對象的實例上起作用。
1.檢查一個文件或目錄
我們可以讓一個Path實例表示文件系統中的一個文件或目錄。它所指向的那個文件或目錄是否存在,是否可訪問,可以通過文件操作來確認。
每當我們使用術語File時,除非另有說明,否則我們將指代File和目錄。
檢查一個File是否存在,使用exists:
@Test public void givenExistentPath_whenConfirmsFileExists_thenCorrect() { Path p = Paths.get(HOME); assertTrue(Files.exists(p)); }
檢查一個File不存在,使用notExists:
@Test public void givenNonexistentPath_whenConfirmsFileNotExists_thenCorrect() { Path p = Paths.get(HOME + "/inexistent_file.txt"); assertTrue(Files.notExists(p)); }
檢查一個File是標准文件,像myfile.txt,還是只是個目錄,使用isRegularFile :
@Test public void givenDirPath_whenConfirmsNotRegularFile_thenCorrect() { Path p = Paths.get(HOME); assertFalse(Files.isRegularFile(p)); }
可以檢查文件的權限。檢查一個文件是否可讀,使用isReadable :
@Test public void givenExistentDirPath_whenConfirmsReadable_thenCorrect() { Path p = Paths.get(HOME); assertTrue(Files.isReadable(p)); }
檢查一個文件是否可寫,使用isWritable :
@Test public void givenExistentDirPath_whenConfirmsWritable_thenCorrect() { Path p = Paths.get(HOME); assertTrue(Files.isWritable(p)); }
檢查一個文件是否可執行,使用isExecutable :
@Test public void givenExistentDirPath_whenConfirmsExecutable_thenCorrect() { Path p = Paths.get(HOME); assertTrue(Files.isExecutable(p)); }
有兩個paths,可以檢查它們在底層文件系統上是否指向相同的文件。
@Test public void givenSameFilePaths_whenConfirmsIsSame_thenCorrect() { Path p1 = Paths.get(HOME); Path p2 = Paths.get(HOME); assertTrue(Files.isSameFile(p1, p2)); }
2.創建Files
文件系統API提供了用於創建文件的單行操作。要創建一個標准文件,使用createFile,傳給它代表我們要創建的文件的path對象。
在path中的所有名字元素(name elements)除了文件名外必須存在。否則會得到一個IOException。
@Test public void givenFilePath_whenCreatesNewFile_thenCorrect() { String fileName = "myfile_" + UUID.randomUUID().toString() + ".txt"; Path p = Paths.get(HOME + "/" + fileName); assertFalse(Files.exists(p)); Files.createFile(p); assertTrue(Files.exists(p)); }
要創建目錄,使用createDirectory :
@Test public void givenDirPath_whenCreatesNewDir_thenCorrect() { String dirName = "myDir_" + UUID.randomUUID().toString(); Path p = Paths.get(HOME + "/" + dirName); assertFalse(Files.exists(p)); Files.createDirectory(p); assertTrue(Files.exists(p)); assertFalse(Files.isRegularFile(p)); assertTrue(Files.isDirectory(p)); }
這個操作也要求path中的所有名字元素都存在。如果不是的話,也會得到IOException
@Test(expected = NoSuchFileException.class) public void givenDirPath_whenFailsToCreateRecursively_thenCorrect() { String dirName = "myDir_" + UUID.randomUUID().toString() + "/subdir"; Path p = Paths.get(HOME + "/" + dirName); assertFalse(Files.exists(p)); Files.createDirectory(p); }
如果想僅一次調用就創建一個層次的目錄結構,使用createDirectories。和之前的操作不一樣,當它在path中遇到丟失的名字元素時,它不會拋出IOException,它會遞歸創建它們直到最后一個元素:
@Test public void givenDirPath_whenCreatesRecursively_thenCorrect() { Path dir = Paths.get( HOME + "/myDir_" + UUID.randomUUID().toString()); Path subdir = dir.resolve("subdir"); assertFalse(Files.exists(dir)); assertFalse(Files.exists(subdir)); Files.createDirectories(subdir); assertTrue(Files.exists(dir)); assertTrue(Files.exists(subdir)); }
3.創建臨時Files
許多應用在它們運行時會在文件系統中創建臨時文件的蹤跡。結果大部分文件系統有一個專用目錄來存儲這樣的應用產生的臨時文件。
新的文件系統API為這個目的提供了特定的操作。createTempFile API就執行這個操作。它要傳入的參數時一個path對象,一個文件前綴,一個文件后綴。
@Test public void givenFilePath_whenCreatesTempFile_thenCorrect() { String prefix = "log_"; String suffix = ".txt"; Path p = Paths.get(HOME + "/"); Files.createTempFile(p, prefix, suffix); assertTrue(Files.exists(p)); }
這些參數對於創建臨時文件的需求是足夠了的。但是,如果要指定文件特定的屬性,還有第四個可變參數的參數。
上面的測試在HOME目錄創建了一個臨時文件,分別插入和追加了提供的前后綴字符串。將會得到文件如:log_8821081429012075286.txt。
這個長的數字串是系統生成的。
然而,如果不提供前后綴,文件名就只包含長數字串和默認的.tmp擴展名。
@Test public void givenPath_whenCreatesTempFileWithDefaults_thenCorrect() { Path p = Paths.get(HOME + "/"); Files.createTempFile(p, null, null); assertTrue(Files.exists(p)); }
生成的文件像:8600179353689423985.tmp
如果path,前綴,后綴都不提供,這個操作將使用默認的輸出。被創建文件的默認位置就是文件系統提供的臨時文件目錄:
@Test public void givenNoFilePath_whenCreatesTempFileInTempDir_thenCorrect() { Path p = Files.createTempFile(null, null); assertTrue(Files.exists(p)); }
Windows中,默認就像:C:\Users\user\AppData\Local\Temp\6100927974988978748.tmp.
通過使用createTempDirectory而不是createTempFile,上面所有的操作可用於創建目錄而不是標准文件。
4.刪除File
為了刪除一個文件,使用delete API。為了清晰起見,下面的測試首先確保文件已經不存在,然后創建它並且確認現在它存在了,最后刪掉它並確認它不再存在了。
@Test public void givenPath_whenDeletes_thenCorrect() { Path p = Paths.get(HOME + "/fileToDelete.txt"); assertFalse(Files.exists(p)); Files.createFile(p); assertTrue(Files.exists(p)); Files.delete(p); assertFalse(Files.exists(p)); }
如果文件不存在文件系統中,delete操作拋出IOException。
@Test(expected = NoSuchFileException.class) public void givenInexistentFile_whenDeleteFails_thenCorrect() { Path p = Paths.get(HOME + "/inexistentFile.txt"); assertFalse(Files.exists(p)); Files.delete(p); }
使用deleteIfExists可以避免這個場景,如果文件不存在它會默默地失敗。當多個線程執行這個操作並且我們不想要失敗信息時很有用。因為一個線程比當前失敗的線程更早執行操作。[???]
@Test public void givenInexistentFile_whenDeleteIfExistsWorks_thenCorrect() { Path p = Paths.get(HOME + "/inexistentFile.txt"); assertFalse(Files.exists(p)); Files.deleteIfExists(p); }
處理目錄而不是標准文件時,要記住,delete操作默認不會遞歸操作。如果目錄不是空的,就會失敗拋出IOException。
@Test(expected = DirectoryNotEmptyException.class) public void givenPath_whenFailsToDeleteNonEmptyDir_thenCorrect() { Path dir = Paths.get( HOME + "/emptyDir" + UUID.randomUUID().toString()); Files.createDirectory(dir); assertTrue(Files.exists(dir)); Path file = dir.resolve("file.txt"); Files.createFile(file); Files.delete(dir); assertTrue(Files.exists(dir)); }
5.復制Files
使用copy API復制一個文件或目錄:
@Test public void givenFilePath_whenCopiesToNewLocation_thenCorrect() { Path dir1 = Paths.get( HOME + "/firstdir_" + UUID.randomUUID().toString()); Path dir2 = Paths.get( HOME + "/otherdir_" + UUID.randomUUID().toString()); Files.createDirectory(dir1); Files.createDirectory(dir2); Path file1 = dir1.resolve("filetocopy.txt"); Path file2 = dir2.resolve("filetocopy.txt"); Files.createFile(file1); assertTrue(Files.exists(file1)); assertFalse(Files.exists(file2)); Files.copy(file1, file2); assertTrue(Files.exists(file2)); }
目標文件存在的話,復制就失敗。除非指定了REPLACE_EXISTING。
@Test(expected = FileAlreadyExistsException.class) public void givenPath_whenCopyFailsDueToExistingFile_thenCorrect() { Path dir1 = Paths.get( HOME + "/firstdir_" + UUID.randomUUID().toString()); Path dir2 = Paths.get( HOME + "/otherdir_" + UUID.randomUUID().toString()); Files.createDirectory(dir1); Files.createDirectory(dir2); Path file1 = dir1.resolve("filetocopy.txt"); Path file2 = dir2.resolve("filetocopy.txt"); Files.createFile(file1); Files.createFile(file2); assertTrue(Files.exists(file1)); assertTrue(Files.exists(file2)); Files.copy(file1, file2); Files.copy(file1, file2, StandardCopyOption.REPLACE_EXISTING); }
復制目錄時,內容是不會遞歸地復制的。
比如:/baeldung包含/articles.db和/authors.db文件。復制/baeldung 到新位置將會創建一個空的目錄。
6.移動Files
使用move API移動文件或目錄。它在很大程度上與復制操作相似。如果復制操作類似於基於GUI的系統中的復制和粘貼操作,則移動類似於剪切和粘貼操作:
@Test public void givenFilePath_whenMovesToNewLocation_thenCorrect() { Path dir1 = Paths.get( HOME + "/firstdir_" + UUID.randomUUID().toString()); Path dir2 = Paths.get( HOME + "/otherdir_" + UUID.randomUUID().toString()); Files.createDirectory(dir1); Files.createDirectory(dir2); Path file1 = dir1.resolve("filetocopy.txt"); Path file2 = dir2.resolve("filetocopy.txt"); Files.createFile(file1); assertTrue(Files.exists(file1)); assertFalse(Files.exists(file2)); Files.move(file1, file2); assertTrue(Files.exists(file2)); assertFalse(Files.exists(file1)); }
如果目標文件存在,移動操作將失敗,除非REPLACE_EXISTING選項被指定了,就像我們對復制操作所做的一樣:
@Test(expected = FileAlreadyExistsException.class) public void givenFilePath_whenMoveFailsDueToExistingFile_thenCorrect() { Path dir1 = Paths.get( HOME + "/firstdir_" + UUID.randomUUID().toString()); Path dir2 = Paths.get( HOME + "/otherdir_" + UUID.randomUUID().toString()); Files.createDirectory(dir1); Files.createDirectory(dir2); Path file1 = dir1.resolve("filetocopy.txt"); Path file2 = dir2.resolve("filetocopy.txt"); Files.createFile(file1); Files.createFile(file2); assertTrue(Files.exists(file1)); assertTrue(Files.exists(file2)); Files.move(file1, file2); Files.move(file1, file2, StandardCopyOption.REPLACE_EXISTING); assertTrue(Files.exists(file2)); assertFalse(Files.exists(file1)); }