操作文件
Path和Files类封装了用户机器上处理文件系统所需要的所有功能。
Files类可以用来移除或重命名文件,或者查询文件最后被修改的时间。
Path接口和Files类是在JavaSE7中新添加进来的。比File类要方便的多。
Path
Path表示的是一个目录名序列,其后还可以跟着一个文件名。
路径的第一个部件可以是根部件: / 或者 C:\,允许访问的根部件取决于文件系统。
以根部件开始的路径是绝对路径;否则,就是相对路径。
Paths.get方法接受一个或多个字符串,并将它们用默认文件系统的路径分隔符(类Unix文件系统是/,Windows是\)连接起来。然后它解析连接起来的结果,如果其表示的不是给定文件系统中的合法路径,那么就会抛出InvalidPathException异常。这个连接结果就是Path对象。
get方法可以获取包含多个部件构成的单个字符串。
StringbaseDir=prop.getProperty("base.dir");
PathbasePath=Paths.get(baseDir);
注意:路径不必对应着某个实际存在的文件,它仅仅只是一个抽象的名字序列。
p.resolve(q):组合或解析路径
- 如果q是绝对路径,则结果就是q
- 否则,根据文件系统的规则,将“p后面跟着q”作为结果。
如果你的应用系统需要查找相对于给定基目录的工作目录:
PathworkRelative=Paths.get("word");
PathworkPath=basePath.resolve(workRelative);
resolve方法还有一种快捷方式,他会接受一个字符串而不是路径:
PathworkPath=basePath.resolve("word");
resolveSibling:通过解析指定路径的父路径产生其兄弟路径。例如:workPath:/opt/myapp/work
PathtmpPath=workPath.resolveSibling("tmp");
// tmpPath = /opt/myapp/tmp
resovle的对立面是relativize,调用p.relativize(r)将产生路径q,而对q进行解析的结果正是r。
例如:以“/home/cay”为目标对“/home/fred/myprog”进行相对化操作,会产生“../fred/myprog”
normalize:移除所有冗余的"."和".."部件(后者文件系统认为冗余的所有部件)。
toAbsolutePath:将产生给定路径的绝对路径,该绝对路径从根部件开始。
例如:/home/red/input.txt或c:\Users\fred\input.txt
path类有很多有用的方法用来将路径断开:
Pathp=Paths.get("/home", "fred", "myprog.properties");
Pathparent=p.getParent(); // /home/fred
Pathfile=p.getFilename(); // myprog.properties
Pathroot=p.getRoot(); // /
注意:偶尔,你可能需要与遗留系统的API交互,它们使用的是File类,而不是Path接口。Path接口有一个toFile方法,而File类有一个toPath方法
读写文件
Files类可以使得普通文件操作变得快捷。
byte[] bytes = Files.readAllBytes(path):读取文件的所有内容。
List<String> lines = Files.readAllLines(path):将文件当作行序列读入
Files.write(path, contents.getBytes(charset)):写出一个字符串到文件中
Files.write(path, contents.getBytes(charset), StandardOpenOption.Append):向指定文件追加
Files.write(path, lines):将一个行的集合写出到文件中
以上所有方法适用于处理中等长度的文件,如果要处理的文件长度比较大,或者是二进制文件,那么还是应该使用所熟悉的输入\输出流或者读入器\写出器
- InputStream in = Files.newInputStream(path);
- OutputStream out = Files.newOutputStream(path);
- Reader in = Files.newBufferedReader(path, charset);
- Writer out = Files.newBufferedWriter(path, charset);
创建文件或目录
Files.createDirectory(path):创建目录,路径中除了最后一个部件外,其他部分都必须存在。
Files.createDirectories(path):创建目录,中间部分可以不存在。
Files.createFile(path):创建一个新文件,如果文件已经存在,则抛出异常。
检查文件是否存在和创建文件应该是原子性的,如果文件不存在,该文件就会被创建,并且其他程序在此过程中是无法执行文件创建操作的。
创建临时文件或临时目录:
- Path newPath = Files.createTempFile(dir, prefix, suffix);
- Path newPath = Files.createTempFile(prefix, suffix);
- Path newPath = Files.createTempDirectory(dir, prefix);
- Path newPath = Files.createTempDirectory(prefix);
dir是一个path对象,prefix和suffix是可以为null的字符串。例如调用Files.createTempFile(null, ".txt")可能会返回一个像/tmp/123431535.txt这样的路径。
在创建文件或目录时,可以指定属性。例如文件的拥有者和权限。但是,指定属性的细节取决于文件系统。
复制、移动和删除文件
Files.copy(fromPath, toPath):将文件从一个位置复制到另一个位置
Files.move(fromPath, toPath):移动文件(即复制并删除源文件)
如果目标已经存在,那么复制或删除将失败。如果想要覆盖已有的目标路径,可以使用REPLACE_EXISTING选项。如果想要复制所有的文件属性,可以使用COPY_ATTRIBUTES选项。可以同时选择
Files.copy(fromPath, toPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
可以将移动操作定义为原子性的,保证要么移动成功完成,要么源文件保持在原来的位置ATOMIC_MOVE
Files.move(fromPath, toPath, StandardCopyOption.ATOMIC_MOVE);
可以将输入流复制到path中,也可以将一个path复制到输出流中。
Files.copy(inputStream, toPath);
Files.copy(fromPath, outputStream);
Files.delete(path):删除文件
如果要删除的文件不存在,这个方法会抛出异常。可以使用下面的方法:
boolean deleted = Files.deleteIfExists(path);
该删除方法还可以用来移除空目录。
用于文件操作的标准选项
选项 | 描述 |
StandardOpenOption;与newBufferedWriter,newInputStream,newOutputStream,write一起使用 | |
READ | 用于读取而打开 |
WRITE | 用于写入而打开 |
APPEND | 如果用于写入而打开,那么在文本末尾追加 |
TRUNCATE_EXISTING | 如果用于写入而打开,那么移除已有内容 |
CREATE_NEW | 创建新文件并且在文件已存在的情况下会创建失败 |
CREATE | 自动在文件不存在的情况下创建新文件 |
DELETE_ON_CLOSE | 当文件被关闭时,尽可能地删除文件 |
SPARSE | 给文件系统一个提示,表示该文件是稀疏的 |
DSYN|SYN | 要求对文件数据|数据和元数据的每次更新都必须同步地写入到存储设备中 |
StandardCopyOption;与copy,move一起使用 | |
ATOMIC_MOVE | 原子性地移动文件 |
COPY_ATTRIBUTES | 复制文件的属性 |
REPLACE_EXISTING | 如果目标已存在,则替换它 |
LinkOption;与上面的所有方法以及exists,isDirectory,isRegularFile等一起使用 | |
NOFOLLOW_LINKS | 不要跟踪符号链接 |
FileVisitOption;与find,walk,walkFileTree一起使用 | |
FOLLOW_LINKS | 跟踪符号链接 |
获取文件信息
下面的方法都返回一个boolean值,表示检查路径的某个属性的结果:
- exists
- isHidden
- isReadable, isWritable, isExecutable
- isRegularFile, isDirectory, isSymbolicLink
Files.size(path):返回文件的字节数。
getOwner:将文件的拥有者作为java.nio.file.attribute.UserPrincipal的一个实例返回。
所有的文件系统都会报一个基本属性集,它们被封装在BasicFileAttributes接口中。基本属性包括:
- 创建文件、最后一次访问以及最后一次修改文件的时间,这些时间都表示成java.nio.file.attribute.FileTime
- 文件是常规文件、目录还是符号链接,抑或这三者都不是
- 文件尺寸
- 文件主键,这是某种类的对象,具体所属类与文件系统相关,有可能是文件的唯一标识符,也可能不是。
获取这些属性,可以使用
BasicFileAttributes attributes = Files.readAttributes(path, BasicFileAttributes.class);
如果你了解用户的文件系统兼容POSIX,那么可以获取一个PosixFileAttributes实例
PosixFileAttributes attributes = Files.readAttributes(path, PosixFileAttributes);
访问目录的项
Files.list方法会返回一个可以读取目录中各个项的Stream<Path>对象。目录是惰性读取的。
因为读取目录涉及需要关闭的系统资源,应该使用try块
try(Stream<Path>entries=Files.list(path)){
...
}
list方法不会进入子目录。
为了处理目录中的所有子目录,需要使用File.walk方法
try(Stream<Path>entries=Files.walk(path)){
...
}
只要遍历的项是目录,那么在进入它之前,会继续访问它的兄弟项。
可以通过Files.walk(pathRoot, depth)来限制想要访问的树的深度。
两种walk方法都具有FileVisitOption...的可变参数,但是你只能提供一个选项:FOLLOW_LINKS,即跟踪符号链接。
注意:如果要过滤walk返回的路径,并且你的过滤标准涉及与目录存储相关的文件属性,例如尺寸、创建时间或类型(文件、目录、符号链接),那么应该使用find方法来替代walk方法。
可以用某个谓词函数调用这个方法,该函数接受一个路径和一个BasicFileAttributes对象。这样做唯一的优势就是效率高。因为路径总是会被读入,所以这些属性很容易获取。
使用Files.walk方法将一个目录复制到另一个目录
Files.walk(source).forEach(p-> {
try{
Pathq=target.resolve(source.relativize(p));
if(Files.isDirectory(q)){
Files.createDirectory(q);
}else{
Files.copy(p, q);
}
}catch(IOExceptione){
thrownewUncheckIOException(e);
}
})
遗憾的是无法很容易地使用Files.walk方法来删除目录,因为你需要在删除之前必须先删除子目录。
使用目录流
需要对遍历过程进行更加细粒度的控制。需要使用Files.newDirectoryStream对象,它会产生一个DirectoryStream。它不是java.util.strea.Stream的子接口,而是专门用于目录遍历的接口。它是Iterator的子接口,因此你可以在增强的for循环中使用目录流。
try(DirectoryStream<Path>entries=Files.newDirectoryStream(dir)){
for(Pathentry: entries){
...
}
}
try语句块用来确保目录流可以正常关闭。访问目录中的项并没有具体的顺序
可以使用glob模式来过滤文件
try(DirectoryStream<Path>entries=Files.newDirectoryStream(dir, "*.java")){
}
Glob模式
模式 | 描述 | 示例 |
* | 匹配路径组成部分中0个或多个字符 | *.java匹配当前目录中的所有Java文件 |
** | 匹配跨目录边界的0个或多个字符 | **.java匹配在所有子目录中的Java文件 |
? | 匹配一个字符 | ????.java匹配所有四个字符的Java文件(不包含扩展名) |
[...] | 匹配一个字符集合可以使用连接符[0-9]和取反符[!0-9] | Test[0-9A-F].java匹配Testx.java,其中x死一个十六进制数字 |
{...} | 匹配由逗号隔开的多个可选项之一 | *.{java,class}匹配所有的Java文件和类class文件 |
\ | 转移上述任意模式中的字符以及\字符 | *\**匹配所有文件中包含*的文件。 |
警告:如果使用Windows的glob语法,则必须对反斜杠转义两次;一次为glob语法转义,一次为Java语法转义;Files.newDirectoryStream(dir, "C:\\\\")
如果想要访问某个目录的所有子孙成员,可以转而调用walkFileTree方法,并向其传递一个FileVisitor类型的对象,这个对象会得到下列通知:
- 在遇到一个文件或目录时:FileVisitResutl visitFile(T path, BasicFileAttributes attrs)
- 在一个目录被处理前:FileVisitResult preVisitDirectory(T dir, IOException ex)
- 在一个目录被处理后:FileVisitResult postVisitDirectory(T dir, IOException ex)
- 在试图访问文件或目录时发生错误,例如没有权限打开目录:FileVisitResult visitFileFailed(path, IOException)
对于上面的每种情况,都可以指定是否希望执行下面的操作
- 继续访问下一个文件:FileVisitResult.CONTINUE
- 继续访问,但是不再访问这个目录下的任何项了:FileVisitResult.SKIP_SUBTREE
- 继续访问,但是不再访问这个文件的兄弟了:FileVisitResult.SKIP_SUBLINGS
- 终止访问,FileVisitResult.TERMINATE
当有任何方法抛出异常时,就会终止访问,而这个异常会从walkFileTree方法中抛出。
注意:FileVisitor接口是泛化类型,但是你也可能会使用除FileVisitor<Path>之外的东西。walFileTree方法可以接受FileVisitor<? super Path>类型的参数,但是path并没有多少超类。
便捷类SimpleFileVisitor实现了FileVisitor接口,但是除visitFileFailed方法之外的所有方法并不做任何处理而是直接继续访问,而visitFileFailed方法会抛出失败导致的异常,并进而终止访问。
例如:如何打印给定目录下的所有子目录:
Files.walkFileTree(Paths.get("/"), newSimpleFileVisitor<Path>(){
publicFileVisitResultpreVisitDirectory(Pathpath, BasicFileAttributesattrs){
System.out.println(path);
returnFileVisitResult.CONTINUE;
}
publicFileVisitResultpostVisitDirectory(Pathdir, IOExceptionexc){
returnFileVisitResult.CONTINUE;
}
publicFileVisitResultvisitFileFailed(Pathpath, IOExceptione) throwsIOException{
returnFileVisitResult.SKIP_SUBTREE;
}
})
我们需要覆盖postVisitDirectory方法和visitFileFailed方法,否则,访问会在遇到不允许打开的目录或不允许访问的文件时立即失效
注意:路径的众多属性是作为preVisitDirectory和visitFile方法的参数传递的。访问者不得不通过操作系统调用来获得这些属性,因为它需要区分文件和目录。
如果你需要在进入一个目录时执行某些操作,那么FileVisitor接口的其他方法就显得非常有用了。
例如:在删除目录树时,需要在移除当前目录的所有文件之后,才能移除目录
Files.walkFilesTree(root, newSimpleFileVisitor<Path>(){
publicFileVisitResultvisitFile(Pathfile, BasicFileAttributesattrs) throwsIOException{
Files.delete(file);
returnFileVisitResult.CONTINUE;
}
publicFileVisitResultpostVisitDirectory(Pathdir, IOExceptione)throwsIOException{
if (e!=null) {
throwe;
}
Files.delete(dir);
returnFileVisitResult.CONTINUE;
}
})
ZIP文件系统
Paths类会在默认文件系统中查找路径,即在用户本地磁盘中的文件。也可以有别的文件系统,其中最有用的之一是ZIP文件系统。
如果zipname是ZIP文件的名字。
FileSystem fs = FileSystems.newFileSystem(Paths.get(zipname), null);
建立一个文件系统,它包含ZIP文档这种的所有文件。如果直到文件名,那么从ZIP文档中复制出这个文件会变得很容易
Files.copy(fs.getPath(sourceName), targetPath);
fs.getPath对于任意文件系统来说,都与Paths.get类似。
列出ZIP文档中的所有文件:
FileSystemfs=Files.newFileSystem(Paths.get(zipname), null);
Files.walkFileTree(fs.getPath("/"), newSimpleFileVisitor<Path>(){
publicFileVisitResultvisitFile(Pathfile, BasicFileAttributesattrs) throwsIOException{
System.out.println(file);
returnFileVisitResult.CONTINUE;
}
})