Java NIO 中的 Files 类(java.nio.file.Files)提供了多种在文件系统中操作文件的方法。本节教程将覆盖最常用的方法。Files 类包含了许多方法,所以如果本文没有提到的你也可以直接查询 JavaDoc 文档。
阅读文章的过程中如果有任何疑问,欢迎添加笔者为好友,拉您进【七日书摘】微信交流群,一起交流技术,一起打造高质量的职场技术交流圈子,抱团取暖,共同进步。
java.nio.file.Files 类是和 java.nio.file.Path 相结合使用的,所以在用 Files 之前确保你已经理解了 Path 类。
Files.exists()
Files.exits()
方法用来检查给定的 Path 在文件系统中是否存在。
在文件系统中你可以创建一个原本不存在的 Path 实例。例如,你要新建一个目录,那么首先创建对应的 Path 实例,然后再创建目录。
由于 Path 实例可能指向文件系统中不存在的路径,因此你需要使用 Files.exists()
方法来确认。
下面是一个使用 Files.exists()
的示例:
Path path = Paths.get("data/logging.properties");
boolean pathExists = Files.exists(path, new LinkOption[]{ LinkOption.NOFOLLOW_LINKS});
在该示例中,首先创建了一个 Path 对象,然后利用 Files.exists()
来检查这个 Path 实例是否真实存在。
注意 Files.exists()
的的第二个参数。它是一个数组,这个参数直接影响 Files.exists()
如何确定一个路径是否存在。在该示例中,数组内包含了 LinkOptions.NOFOLLOW_LINKS
,表示 Files.exists()
检测时不包含符号链接文件。
创建目录(Files.createDirectory())
Files.createDirectory()
方法的作用是从 Path 实例创建一个新目录。
下面是一个使用 Files.createDirectory()
示例:
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 wrong
e.printStackTrace();
}
第一行表示需要创建目录的 Path 实例。接着在 try-catch
块中使用 Path 作为 Files.createDirectory()
方法的参数进行创建,并进行异常捕获。如果目录创建成功,那么返回值就是新创建的路径。
如果目录已经存在,那么会抛出 java.nio.file.FileAlreadyExistException
异常。如果出现其他问题则会抛出 IOException
。例如,要创建的目录的父目录不存在,那么就会抛出 IOException
。父目录指的是你要创建的目录所在的位置,也就是新创建的目录的上一级目录。
文件拷贝(Files.copy())
Files.copy()
方法的作用可以把一个文件从一个路径复制到另一个路径。
下面是一个 Files.copy()
的使用示例:
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 wrong
e.printStackTrace();
}
在该示例中,首先创建原文件和目标文件的 Path 实例。然后把它们作为参数传递给 Files.copy()
,接着就会实现把文件从源文件路径拷贝到目标路径下。
如果目标文件已经存在,则会抛出 java.nio.file.FileAlreadyExistsException
异常。类似的如果发生其他错误,将抛出 IOException
。例如,如果要将文件复制到的目录不存在,将引发IOException。
覆盖已经存在的文件(Overwriting Existing Files)
Files.copy()
方法可以强制覆盖已经存在的文件。
下面是一个使用 Files.copy()
的示例:
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 wrong
e.printStackTrace();
}
注意 Files.copy()
方法的第三个可选参数,这个参数决定是否覆盖已存在的文件。
移动文件(Files.move())
Java NIO 的 Files 类还包含了一个用于将文件从一个路径移动到另一个路径的函数。移动文件和重命名是一样的,但是移动文件除了可以更改其名称外还可以改变文件的目录位置。java.io.File 类中的 renameTo()
方法可更改其名称。
下面是一个 Files.move()
示例:
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();
}
首先创建原路径和目标路径,原路径指向需要移动的文件,而目标路径指向应将文件移动到的位置,然后调用 Files.move()
方法进行文件移动。
注意传递给Files.move()
的第三个可选参数,此参数告诉 Files.move()
方法覆盖目标路径下的相应文件。
如果移动文件失败,Files.move()
方法可能会引发 IOException。例如,如果目标路径中已经存在一个文件,并且你忽略了第三个参数 StandardCopyOption.REPLACE_EXISTING
选项,或者移动的文件不存在等等。
删除文件(Files.delete())
Files.delete()
方法用于删除一个文件或目录。下面是一个 Files.delete()
使用示例:
Path path = Paths.get("data/subdir/logging-moved.properties");
try {
Files.delete(path);
} catch (IOException e) {
//deleting file failed
e.printStackTrace();
}
首先创建指向要删除的文件的路径,然后调用 Files.delete()
方法。如果 Files.delete()
由于某种原因(例如:文件或目录不存在)未能删除文件,则会引发 IOException
。
Files.walkFileTree()
Files.walkFileTree()
方法包含递归遍历目录树的功能。Files.walkFileTree()
接受一个 Path
和 FileVisitor
作为参数。Path 对象指向需要遍历的目录,FileVistor 则会在每次遍历中被调用。
在解释遍历是如何工作的之前,首先先看下面 FileVisitor 接口的定义示例:
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 {
}
FileVisitor 接口需要调用方自行实现,然后将实现的实例作为参数传入 walkFileTree()
方法。FileVisitor的每个方法会在遍历过程中的不同时期被调用。如果不需要处理每个方法,那么可以继承他的默认实现类 SimpleFileVisitor
,该类包含了 FileVisitor 接口中所有方法的默认实现。
下面是 walkFileTree()
示例:
Files.walkFileTree(path, new FileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
System.out.println("pre visit dir:" + dir);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
System.out.println("visit file: " + file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
System.out.println("visit file failed: " + file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
System.out.println("post visit directory: " + dir);
return FileVisitResult.CONTINUE;
}
});
在遍历过程中,FileVisitor 的方法会在的不同时间被调用:
preVisitDirectory()
方法是在访问任何目录之前被调用。postVisitDirectory()
方法是在访问目录之后后被调用的。
visitFile()
会在整个遍历过程中的每次访问文件时被调用。该方法不是针对目录的,而是针对文件的。visitFileFailed()
方法则是在文件访问失败的时候被调用。例如,当无访问权限或者发生了其他错误。
上述四个方法都返回一个 FileVisitResult
枚举对象。FileVisitResult 枚举包含以下四个选项:
- CONTINUE
- TERMINATE
- SKIP_SIBLINGS
- SKIP_SUBTREE
通过返回其中一个枚举值,可以让调用方决定如何继续文件遍。
CONTINE
表示文件遍历应继续正常进行。
TERMINATE
表示文件遍历现在需要终止。
SKIP_SIBLINGS
表示文件遍历应继续,但不要访问其同级文件或任何同级目录。
SKIP_SUBTREE
表示文件遍历应该继续,但不要访问该目录下的子目录。这个枚举值仅在 preVisitDirectory()
中返回才有效。如果在另外几个方法中返回,那么会被理解为 CONTINE。
搜索文件(Searching For Files)
下面是一个 walkFileTree()
例子,通过 walkFileTree()
来寻找一个README.txt文件:
Path rootPath = Paths.get("data");
String fileToFind = File.separator + "README.txt";
try {
Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
String fileString = file.toAbsolutePath().toString();
//System.out.println("pathString = " + fileString);
if(fileString.endsWith(fileToFind)){
System.out.println("file found at path: " + file.toAbsolutePath());
return FileVisitResult.TERMINATE;
}
return FileVisitResult.CONTINUE;
}
});
} catch(IOException e){
e.printStackTrace();
}
Deleting Directories Recursively
Files.walkFileTree()
方法也可以用来删除一个目录以及目录以下的所有文件和子目录。Files.delete()
方法仅用于删除一个空目录。通过遍历所有目录然后在 visitFile()
接口中删除每个目录中的所有文件,最后在 postVisitDirectory()
内删除目录本身。
下面是一个删除遍历目录示例:
Path rootPath = Paths.get("data/to-delete");
try {
Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
System.out.println("delete file: " + file.toString());
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public 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();
}
Files类中的其他方法(Additional Methods in the Files Class)
java.nio.file.Files 类还包含血多其它有用的方法,例如用于创建符号链接、确定文件大小以及设置文件权限的方法等。具体用法可以查阅 java.nio.file.Files 类的JavaDoc 说明。
------完------
推荐阅读:
Java NIO 简明教程 之 Java NIO 非阻塞式服务器(Non-blocking Server)
Java NIO 简明教程 之 Java NIO 数据报通道(DatagramChannel)
Java NIO 简明教程 之 Java NIO 管道(Pipe)
Java NIO 简明教程 之 Java NIO vs. IO
参考资源:
http://tutorials.jenkov.com/java-nio/files.html
https://wiki.jikexueyuan.com/project/java-nio-zh/java-nio-files.html