Java7操作文件和文件夹的新特性

简介: Path接口和Files类是在JavaSE7中新添加进来的。比File类要方便的多。

操作文件

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;

   }

})


目录
相关文章
|
22天前
|
安全 Java 数据安全/隐私保护
|
22天前
|
搜索推荐 Java
Java的面向对象特性主要包括封装、继承和多态
【4月更文挑战第5天】Java的面向对象特性主要包括封装、继承和多态
15 3
|
1月前
|
Java
有关Java发送邮件信息(支持附件、html文件模板发送)
有关Java发送邮件信息(支持附件、html文件模板发送)
31 1
|
1月前
|
Java
java中替换文件内容
java中替换文件内容
14 1
|
1月前
|
Java API
Java中文件与输入输出
Java中文件与输入输出
|
1月前
|
Java
java实现遍历树形菜单方法——映射文件VoteTree.hbm.xml
java实现遍历树形菜单方法——映射文件VoteTree.hbm.xml
10 0
|
1月前
|
Java
java程序导出堆文件
java程序导出堆文件
|
6天前
|
Java 关系型数据库 MySQL
Elasticsearch【问题记录 01】启动服务&停止服务的2类方法【及 java.nio.file.AccessDeniedException: xx/pid 问题解决】(含shell脚本文件)
【4月更文挑战第12天】Elasticsearch【问题记录 01】启动服务&停止服务的2类方法【及 java.nio.file.AccessDeniedException: xx/pid 问题解决】(含shell脚本文件)
33 3
|
1天前
|
存储 前端开发 Java
Java实现文件分片上传
Java实现文件分片上传
5 0
|
2天前
|
安全 Java 大数据
探索Java的奇妙世界:语言特性与实际应用
探索Java的奇妙世界:语言特性与实际应用