[译]Java NIO2 File API介绍
1.概览
在这篇文章中,我们要关注的是使用Java平台的NIO(译者注: NIO意即New I/O)的APIs----NIO2----用来对文件做一些基础的操作.
File APIs in NIO2 constitute one of the major new functional areas of the Java Platform that shipped with Java 7, specifically a subset of the new file system API alongside Path APIs .
2.设置
Setting up your project to use File APIs is just a matter of making this import:
使用File APIs只需要如下导包:
1 import java.nio.file.*;
由于本文的示例代码可能会运行在不同的环境中(译者注: 即不同的操作系统),因此我们不妨使用用户的根目录,这样在所有操作系统中都是可行的:
1 private static String HOME = System.getProperty("user.home");
此Flies类是java.nio.file 包的主要切入点之一.此类提供了丰富的APIs用于读,写以及巧妙的处理文件和目录(译者注:目录即文件夹).此Files类的方法作用于Path对象的实例.
3.检查一个文件或目录
于此文件系统中,我们可以用一个Path实例来表示一个文件或目录.无论一个文件或者目录存在与否,可获取与否,都可以通过一个文件系统确认.
为了方便,不管何时使用文件这个术语,我们指的都是文件和目录,除非特别指明(译者注: 若非特别指出,本文中的文件兼指文件和目录).
要确认一个文件存在,我们使用exists API:
1 @Test 2 public void givenExistentPath_whenConfirmsFileExists_thenCorrect() { 3 Path p = Paths.get(HOME); 4 5 assertTrue(Files.exists(p)); 6 }
要确认一个文件不存在,我们使用notExists API:
1 @Test 2 public void givenNonexistentPath_whenConfirmsFileNotExists_thenCorrect() { 3 Path p = Paths.get(HOME + "/inexistent_file.txt"); 4 5 assertTrue(Files.notExists(p)); 6 }
我们也可以确认一个文件是普通的文件还是目录,使用isRegularFile API:
1 @Test 2 public void givenDirPath_whenConfirmsNotRegularFile_thenCorrect() { 3 Path p = Paths.get(HOME); 4 5 assertFalse(Files.isRegularFile(p)); 6 }
也有一些静态的方法用于检查文件的权限.检查一个文件是可读的,使用isReadable API:
1 @Test 2 public void givenExistentDirPath_whenConfirmsReadable_thenCorrect() { 3 Path p = Paths.get(HOME); 4 5 assertTrue(Files.isReadable(p)); 6 }
检查一个文件是可写的,使用isWritable API:
1 @Test 2 public void givenExistentDirPath_whenConfirmsWritable_thenCorrect() { 3 Path p = Paths.get(HOME); 4 5 assertTrue(Files.isWritable(p)); 6 }
同样地,检查其是可执行的:
1 @Test 2 public void givenExistentDirPath_whenConfirmsExecutable_thenCorrect() { 3 Path p = Paths.get(HOME); 4 assertTrue(Files.isExecutable(p)); 5 }
当我们有两条路径时,我们可以检查是否他们都指向了文件系统下的同一个文件源:
1 @Test 2 public void givenSameFilePaths_whenConfirmsIsSame_thenCorrect() { 3 Path p1 = Paths.get(HOME); 4 Path p2 = Paths.get(HOME); 5 6 assertTrue(Files.isSameFile(p1, p2)); 7 }
4.创建文件
此文件系统的API提供了单线操作创建文件.创建文件(译者注:非目录)我们可以使用createFile API并传递一个Path对象来代表这个我们想要创建的文件(译者注:非目录).
所有在路径中出现的名称元素必须存在,除了文件(译者注:非目录)名.否则,将出现IOException异常:
1 @Test 2 public void givenFilePath_whenCreatesNewFile_thenCorrect() { 3 String fileName = "myfile_" + UUID.randomUUID().toString() + ".txt"; 4 Path p = Paths.get(HOME + "/" + fileName); 5 assertFalse(Files.exists(p)); 6 7 Files.createFile(p); 8 9 assertTrue(Files.exists(p)); 10 }
在上面的测试中,当我们第一次检查路径时,其并不存在,通过createFile()方法操作后,路径存在.
创建目录,可以使用createDirectory API:
1 @Test 2 public void givenDirPath_whenCreatesNewDir_thenCorrect() { 3 String dirName = "myDir_" + UUID.randomUUID().toString(); 4 Path p = Paths.get(HOME + "/" + dirName); 5 assertFalse(Files.exists(p)); 6 7 Files.createDirectory(p); 8 9 assertTrue(Files.exists(p)); 10 assertFalse(Files.isRegularFile(p)); 11 assertTrue(Files.isDirectory(p)); 12 }
此操作依然需要所有提及的路径真实存在,若非如此,也将出现IOException:
1 @Test(expected = NoSuchFileException.class) 2 public void givenDirPath_whenFailsToCreateRecursively_thenCorrect() { 3 String dirName = "myDir_" + UUID.randomUUID().toString() + "/subdir"; 4 Path p = Paths.get(HOME + "/" + dirName); 5 assertFalse(Files.exists(p)); 6 7 Files.createDirectory(p); 8 }
然而,如果我们渴望一次调用就创建多级目录,我们使用createDirectories方法.不似之前操作,当其遇任何路径中缺省的名称元素,并不抛IOException异常,而是递归地创建所有缺失的名称元素:
1 @Test 2 public void givenDirPath_whenCreatesRecursively_thenCorrect() { 3 Path dir = Paths.get( 4 HOME + "/myDir_" + UUID.randomUUID().toString()); 5 Path subdir = dir.resolve("subdir"); 6 assertFalse(Files.exists(dir)); 7 assertFalse(Files.exists(subdir)); 8 9 Files.createDirectories(subdir); 10 11 assertTrue(Files.exists(dir)); 12 assertTrue(Files.exists(subdir)); 13 }
5.创建临时文件
许多应用在运行时会在文件系统创建一系列临时文件,结果是:大多数文件系统有专用的的目录存储由这些应用生成的临时文件.
为此,新的文件系统API提供了专门的操作----createTempFile API.入参依次是一个Path对象,一个文件前缀,一个文件后缀:
1 @Test 2 public void givenFilePath_whenCreatesTempFile_thenCorrect() { 3 String prefix = "log_"; 4 String suffix = ".txt"; 5 Path p = Paths.get(HOME + "/"); 6 7 Files.createTempFile(p, prefix, suffix); 8 9 assertTrue(Files.exists(p)); 10 }
这些参数对于此操作时足够的.然而,如果你需要定义文件的某个属性,那么可以使用第四个参数.
上面的测试创建了一个练市文件在HOME目录,其前和其后分别加上了前缀和后缀字符串.最终文件名类似这样:log_8821081429012075286.txt.
长整型字符串由系统生成.
然而,如果我们不提供前后缀,那么此文件将只会包括那个长整型字符串和一个默认的.tmp扩展名:
1 @Test 2 public void givenPath_whenCreatesTempFileWithDefaults_thenCorrect() { 3 Path p = Paths.get(HOME + "/"); 4 5 Files.createTempFile(p, null, null); 6 7 assertTrue(Files.exists(p)); 8 }
上面的操作创建的文件名类似8600179353689423985.tmp.
最后,如果我们及不提供路径,也不提供前后缀,那么此操作将会使用默认输出.磨人的文件生成位置为文件系统提供的临时文件目录.
1 @Test 2 public void givenNoFilePath_whenCreatesTempFileInTempDir_thenCorrect() { 3 Path p = Files.createTempFile(null, null); 4 5 assertTrue(Files.exists(p)); 6 }
在windows系统,类似这样:
C:\Users\user\AppData\Local\Temp\6100927974988978748.tmp.
上面所有的操作,通过使用createTempDirectory替换掉createTempFile来创建临时目录.
6.删除一个文件
删除一个文件(非文件夹),我们使用delete API.为了言简意赅,下面的测试首先确保了该文件(非文件夹)不存在,然后创建该文件并确保创建成功,最后删除该文件(非文件夹)并确保其不再存在:
1 @Test 2 public void givenPath_whenDeletes_thenCorrect() { 3 Path p = Paths.get(HOME + "/fileToDelete.txt"); 4 assertFalse(Files.exists(p)); 5 Files.createFile(p); 6 assertTrue(Files.exists(p)); 7 8 Files.delete(p); 9 10 assertFalse(Files.exists(p)); 11 }
然而,如果某个文件(非文件夹)不存在于文件系统,删除操作会失败,并报错IOException:
1 @Test(expected = NoSuchFileException.class) 2 public void givenInexistentFile_whenDeleteFails_thenCorrect() { 3 Path p = Paths.get(HOME + "/inexistentFile.txt"); 4 assertFalse(Files.exists(p)); 5 6 Files.delete(p); 7 }
我们可以避免这种情况通过使用deleteIfExists,即使文件(非文件夹)不存在,删除也不会报错.这是很重要的,当很多线程做删除操作时,我们不希望看到仅仅因为一个线程在当前线程之前做删除操作就报错误信息:
1 @Test 2 public void givenInexistentFile_whenDeleteIfExistsWorks_thenCorrect() { 3 Path p = Paths.get(HOME + "/inexistentFile.txt"); 4 assertFalse(Files.exists(p)); 5 6 Files.deleteIfExists(p); 7 }
当操作文件夹而非文件时,我们应该记住删除操作默认不进行递归操作.因此,删除非空文件夹会报错IOException:
1 @Test(expected = DirectoryNotEmptyException.class) 2 public void givenPath_whenFailsToDeleteNonEmptyDir_thenCorrect() { 3 Path dir = Paths.get( 4 HOME + "/emptyDir" + UUID.randomUUID().toString()); 5 Files.createDirectory(dir); 6 assertTrue(Files.exists(dir)); 7 8 Path file = dir.resolve("file.txt"); 9 Files.createFile(file); 10 11 Files.delete(dir); 12 13 assertTrue(Files.exists(dir)); 14 }
7.复制文件
你可以复制文件或文件夹通过使用copy API:
1 @Test 2 public void givenFilePath_whenCopiesToNewLocation_thenCorrect() { 3 Path dir1 = Paths.get( 4 HOME + "/firstdir_" + UUID.randomUUID().toString()); 5 Path dir2 = Paths.get( 6 HOME + "/otherdir_" + UUID.randomUUID().toString()); 7 8 Files.createDirectory(dir1); 9 Files.createDirectory(dir2); 10 11 Path file1 = dir1.resolve("filetocopy.txt"); 12 Path file2 = dir2.resolve("filetocopy.txt"); 13 14 Files.createFile(file1); 15 16 assertTrue(Files.exists(file1)); 17 assertFalse(Files.exists(file2)); 18 19 Files.copy(file1, file2); 20 21 assertTrue(Files.exists(file2)); 22 }
如果目标文件已存在,则会复制失败,除非指明REPLACE_EXISTING:
1 @Test(expected = FileAlreadyExistsException.class) 2 public void givenPath_whenCopyFailsDueToExistingFile_thenCorrect() { 3 Path dir1 = Paths.get( 4 HOME + "/firstdir_" + UUID.randomUUID().toString()); 5 Path dir2 = Paths.get( 6 HOME + "/otherdir_" + UUID.randomUUID().toString()); 7 8 Files.createDirectory(dir1); 9 Files.createDirectory(dir2); 10 11 Path file1 = dir1.resolve("filetocopy.txt"); 12 Path file2 = dir2.resolve("filetocopy.txt"); 13 14 Files.createFile(file1); 15 Files.createFile(file2); 16 17 assertTrue(Files.exists(file1)); 18 assertTrue(Files.exists(file2)); 19 20 Files.copy(file1, file2); 21 22 Files.copy(file1, file2, StandardCopyOption.REPLACE_EXISTING); 23 }
然而,当复制文件夹时,并不会进行递归操作.这意味着复制文件夹到一个新的位置生成的是空文件夹.
8.移动文件
你可以移动文件或目录通过使用 move API.方式和copy操作很相似.如果copy操作和在图形界面操作系统(译者注:比如Windows)的复制粘贴类似,那么move操作即和剪切相似:
1 @Test 2 public void givenFilePath_whenMovesToNewLocation_thenCorrect() { 3 Path dir1 = Paths.get( 4 HOME + "/firstdir_" + UUID.randomUUID().toString()); 5 Path dir2 = Paths.get( 6 HOME + "/otherdir_" + UUID.randomUUID().toString()); 7 8 Files.createDirectory(dir1); 9 Files.createDirectory(dir2); 10 11 Path file1 = dir1.resolve("filetocopy.txt"); 12 Path file2 = dir2.resolve("filetocopy.txt"); 13 Files.createFile(file1); 14 15 assertTrue(Files.exists(file1)); 16 assertFalse(Files.exists(file2)); 17 18 Files.move(file1, file2); 19 20 assertTrue(Files.exists(file2)); 21 assertFalse(Files.exists(file1)); 22 }
和copy操作一样,如果目标文件已存在,move操作会失败,除非指明了REPLACE_EXISTING:
1 @Test(expected = FileAlreadyExistsException.class) 2 public void givenFilePath_whenMoveFailsDueToExistingFile_thenCorrect() { 3 Path dir1 = Paths.get( 4 HOME + "/firstdir_" + UUID.randomUUID().toString()); 5 Path dir2 = Paths.get( 6 HOME + "/otherdir_" + UUID.randomUUID().toString()); 7 8 Files.createDirectory(dir1); 9 Files.createDirectory(dir2); 10 11 Path file1 = dir1.resolve("filetocopy.txt"); 12 Path file2 = dir2.resolve("filetocopy.txt"); 13 14 Files.createFile(file1); 15 Files.createFile(file2); 16 17 assertTrue(Files.exists(file1)); 18 assertTrue(Files.exists(file2)); 19 20 Files.move(file1, file2); 21 22 Files.move(file1, file2, StandardCopyOption.REPLACE_EXISTING); 23 24 assertTrue(Files.exists(file2)); 25 assertFalse(Files.exists(file1)); 26 }
9.总结
在本文中,我们学习了java7在新文件系统(NIO2)关于file 的APIs,了解了大多数重要的文件操作.
本文的示例代码的源码参看 https://github.com/eugenp/tutorials/tree/master/core-java-io
译文原文地址:
http://www.baeldung.com/java-nio-2-file-api