Files类提供了很多方法用于检查在于你真正实际去操作一个文件或目录。这些方法强烈推荐,也非常有用,也能避免很多异常的发生。例如,一个很好的习惯就是在你试着移动一个文件从一个地方到另一个地方的时候,先检查文件是否存在。

检查一个文件或目录是否存在

在前面的例子中已经演示到,Path实例能够有效的映射到一个文件或是目录甚至这个文件或目录物理上根本不存在。再是,Path的很多方法不会实际操作文件本身就能成功地应用。所以,事先判断一个目录或是文件存在就显得非常重要。下面有两个方法用来判断文件是否存在。

  • exists() :检查一个文件是否存在
  • notExists() : 检查一个文件是否不存在
  • 这两个方法都包含两个参数,第一个参数是path实例,第二个参数符号连接的文件是否处理。exist()方法返回 true如果文件存在。下下面代码:

    Path path = FileSystems.getDefault().getPath("C:/rafaelnadal/tournaments/2009","AEGON.txt");
     boolean path_exists = Files.exists(path, new LinkOption[]{LinkOption.NOFOLLOW_LINKS});

    注意: !Files.exists(…) Files.notExists(…) 方法不是等价的, notExists()不是 exists() 方法的补充。

    检查文件的可访问性

    一个很好的习惯就是在访问一个文件前先检查它的可访问级别,可以使用 isReadable() , isWritable() , isExecutable()这些方法。在传递一个路径被确认后,如果文件可读,可写,可执行,那么虚拟机将有权限去访问这个文件。

    另外,可以使用 isRegularFile()方法在判断文件是不是一个正常的文件。正常的文件是指没有特别的特性(例如,不是符号链接,不是目录等),包含真实的数据(例如二进制文件)。 上代码。

    C:\rafaelnadal\tournaments\2009 directory (the file must exist) looks like the following:
    Path path = FileSystems.getDefault().getPath("C:/rafaelnadal/tournaments/2009","AEGON.txt");
    boolean is_readable = Files.isReadable(path);
     boolean is_writable = Files.isWritable(path);
     boolean is_executable = Files.isExecutable(path);
     boolean is_regular = Files.isRegularFile(path, LinkOption.NOFOLLOW_LINKS);
    if ((is_readable) && (is_writable) && (is_executable) && (is_regular)) {
          System.out.println("The checked file is accessible!");
     } else {
          System.out.println("The checked file is not accessible!");
    

      或者像下面这样的代码:

    boolean is_accessible = Files.isRegularFile(path) & Files.isReadable(path) &  
                             Files.isExecutable(path) & Files.isWritable(path);
     if (is_accessible) {
         System.out.println("The checked file is accessible!");
     } else {
         System.out.println("The checked file is not accessible!");
    

       检查两个路径是否指向同一相同文件

      在以前的章节中,有可以看到如何去检查一个符号链接和目标文件是否是同一个文件。还有一个常用的测试就是你你可以使用isSameFile()方法去检查不同的Paths的表达是否指向同一个文件。例如,一个相对路径和一个绝对路径可能指向同一个文件。假设有一文件的路径为C:\rafaelnadal\tournaments\2009\MutuaMadridOpen.txt,有下面三种Path的表达方式。

    Path path_1 = FileSystems.getDefault().getPath("C:/rafaelnadal/tournaments/2009",
                                                                          "MutuaMadridOpen.txt");
    Path path_2 = FileSystems.getDefault().getPath("/rafaelnadal/tournaments/2009", 
                                                                         "MutuaMadridOpen.txt"); 
    Path path_3 = FileSystems.getDefault().getPath("/rafaelnadal/tournaments/dummy/../2009", 
                                                                         "MutuaMadridOpen.txt");
     try {
         boolean is_same_file_12 = Files.isSameFile(path_1, path_2);
         boolean is_same_file_13 = Files.isSameFile(path_1, path_3);
         boolean is_same_file_23 = Files.isSameFile(path_2, path_3);
        System.out.println("is same file 1&2 ? " + is_same_file_12);
         System.out.println("is same file 1&3 ? " + is_same_file_13);
         System.out.println("is same file 2&3 ? " + is_same_file_23);
     } catch (IOException e) {
         System.err.println(e);
    

      输出结果为:

    is same file 1&2 ? true

    is same file 1&3 ? true

    is same file 2&3 ? true

      检查文件是否可见。

      如果你需要找出一个文件是否可见,可以使用Files.isHidden()方法,需要注意的是,“hidden”这个概念是依赖平台的。

    Path path = FileSystems.getDefault().getPath("C:/rafaelnadal/tournaments/2009", 
                                                                           "MutuaMadridOpen.txt");
     try {
         boolean is_hidden = Files.isHidden(path);
         System.out.println("Is hidden ? " + is_hidden);
     } catch (IOException e) {
         System.err.println(e);
    

      --------------------------------------------------------------------------------------------------------------------------------

      创建和读取目录

      当我们需要创建和读取一个目录时,NIO.2提供了一些类方法在Files类中。在这一节中,你将会发现如何列出文件系统的根,创建目录以及临时目录,列出目录里的内容,还可以对目录进行过滤。

      列出文件系统的根目录

      在Java 6 中,文件系统的根目录被抽取出一个存放File对象的数组中,从Java 7 开始,NIO.2 会把根目录作为Path对象的Iterable,通过getRootDirectories()方法返回这个Iterable接口。

    Iterable<Path> dirs = FileSystems.getDefault().getRootDirectories();
     for (Path name : dirs) {
          System.out.println(name);
    

      可能的输出结果是:

      C:\

      D:\

      E:\

      创建一个新的目录

      直接上代码。

    Path newdir = FileSystems.getDefault().getPath("C:/rafaelnadal/tournaments/2010/");
     try {
         Files.createDirectory(newdir);
     } catch (IOException e) {
         System.err.println(e);
    

      当然,也可以在创建目录时设置权限信息:

    Path newdir = FileSystems.getDefault().getPath("/home/rafaelnadal/tournaments/2010/");
     Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rwxr-x---");
     FileAttribute<Set<PosixFilePermission>> attr = PosixFilePermissions.asFileAttribute(perms);
     try {
         Files.createDirectory(newdir, attr);
     } catch (IOException e) {
            System.err.println(e);
    

      注意,如果要创建的目录存在,则 createDirectory()方法会抛出异常。

      有的时候,我们需要多层的目录,例如\statistics\win\prizes,当然你可以使用createDirectory()方法,其实还可以优雅地使用Files.createDirectories()方法,

    Path newdir= FileSystems.getDefault().getPath("C:/rafaelnadal/", "statistics/win/prizes");
     try {
         Files.createDirectories(newdir);
     } catch (IOException e) {
         System.err.println(e);
    

      列出目录里的内容

    Path path = Paths.get("C:/rafaelnadal/tournaments/2009");
    //no filter applied
     System.out.println("\nNo filter applied:");
     try (DirectoryStream<Path> ds = Files.newDirectoryStream(path)) {
          for (Path file : ds) {
               System.out.println(file.getFileName());
     }catch(IOException e) {
        System.err.println(e);
    

      使用正则表达式列出目录里的内容

    Path path = Paths.get("C:/rafaelnadal/tournaments/2009");
     //glob pattern applied
     System.out.println("\nGlob pattern applied:");
     try (DirectoryStream<Path> ds = Files.newDirectoryStream(path, "*.{png,jpg,bmp}")) {
          for (Path file : ds) {
               System.out.println(file.getFileName());
     } catch (IOException e) {
         System.err.println(e);
    

      用户自定义过滤器列出目录里的内容

      如果正则表达式不能满足你的需求,你也可以自定义自己的过滤器,这个任务很简单,只需要继承DirectoryStream.Filter<T>接口即可。此接口有accept()方法,一个Path是接受还是拒绝完全基于你的实现。下面的例子,只列出给定目录下的所有目录。

    Path path = Paths.get("C:/rafaelnadal/tournaments/2009");
     //user-defined filter - only directories are accepted
     DirectoryStream.Filter<Path> dir_filter = new DirectoryStream.Filter<Path>() {
    public boolean accept(Path path) throws IOException {
           return (Files.isDirectory(path, NOFOLLOW_LINKS));
    
    System.out.println("\nUser defined filter applied:");
     try (DirectoryStream<Path> ds = Files.newDirectoryStream(path, dir_filter)) {
     for (Path file : ds) {
           System.out.println(file.getFileName());
     } catch (IOException e) {
         System.err.println(e);
    

      还有如下自定义的过滤器。

        1.列出文件或目录大小超过200kb的列表。

    public boolean accept(Path path) throws IOException {
          return (Files.size(path) > 204800L);
    

        2.列出只有今天修改的文件的列表。

    DirectoryStream.Filter<Path> time_filter = new DirectoryStream.Filter<Path>() {
    public boolean accept(Path path) throws IOException {
          long currentTime = FileTime.fromMillis(System.currentTimeMillis()).to(TimeUnit.DAYS);
          long modifiedTime = ((FileTime) Files.getAttribute(path, "basic:lastModifiedTime", 
                                                           NOFOLLOW_LINKS)).to(TimeUnit.DAYS);
          if (currentTime == modifiedTime) {
                  return true;
        return false;
    

        3.只列出隐藏的文件或目录

    DirectoryStream.Filter<Path> hidden_filter = new DirectoryStream.Filter<Path>() {
    public boolean accept(Path path) throws IOException {
          return (Files.isHidden(path));
    

      -----------------------------------------------------------------------------------------------------------------------------

       创建,读取和写文件

      一个Stream表示输入源或是输出目的地。stream支持不同种类的数据,例如字符串,字节,基本数据类型,本地化字符还有对象。在一个无缓冲的流,每个读或写请求由底层操作系统直接处理,而在有缓存的流中,数据从内存中称之为缓冲区的地方读取,只有当缓冲区为空的时候本地输入API会被调用。类似的,缓存的输出流把数据写入到缓冲区中,只有缓冲区写满以后本地输出API才会被调用。当缓冲区输出完没有等待填满时,外我们说这个缓冲区是清空的。

      使用标准的打开选项

      从NIO.2 开始,文件的创建,读取和写入操作都支持一个可选参数——OpenOption,它用来配置外面如何打开或是创建一个文件。实际上OpenOption是java.nio.file包中一个接口,它有两个实现类:LinkOptionStandardOpenOption。下面就是选项枚举。

    以读取方式打开文件 WRITE   已写入方式打开文件 CREATE 如果文件不存在,创建 CREATE_NEW 如果文件不存在,创建;若存在,异常。 APPEND 在文件的尾部追加 DELETE_ON_CLOSE 当流关闭的时候删除文件 TRUNCATE_EXISTING 把文件设置为0字节 SPARSE 文件不够时创建新的文件 同步文件的内容和元数据信息随着底层存储设备 DSYNC 同步文件的内容随着底层存储设备

      创建文件和创建目录类似,可以使用Files.createFile()方法,也可以在创建的时候添加权限信息。

    Path newfile = FileSystems.getDefault().
                                getPath("C:/rafaelnadal/tournaments/2010/SonyEricssonOpen.txt");
     try {
         Files.createFile(newfile);
     } catch (IOException e) {
         System.err.println(e);
    

      创建带有权限的文件.

    Path newfile = FileSystems.getDefault().
                    getPath("/home/rafaelnadal/tournaments/2010/SonyEricssonOpen.txt");
    Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rw-------");
     FileAttribute<Set<PosixFilePermission>> attr = PosixFilePermissions.asFileAttribute(perms);
     try {
         Files.createFile(newfile, attr);
     } catch (IOException e) {
         System.err.println(e);
    

      写入一个小文件

      NIO.2提供了非常优雅的方式去写入小的二进制或是文本文件。使用Files.write()方法来创建。

      使用write()方法写入bytes

    Path ball_path = Paths.get("C:/rafaelnadal/photos", "ball.png");
     byte[] ball_bytes = new byte[]{
     (byte)0x89,(byte)0x50,(byte)0x4e,(byte)0x47,(byte)0x0d,(byte)0x0a,(byte)0x1a,(byte)0x0a,
     (byte)0x00,(byte)0x00,(byte)0x00,(byte)0x0d,(byte)0x49,(byte)0x48,(byte)0x44,(byte)0x52,
     (byte)0x00,(byte)0x00,(byte)0x00,(byte)0x10,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x10,
     (byte)0x08,(byte)0x02,(byte)0x00,            
     (byte)0x49,(byte)0x45,(byte)0x4e,(byte)0x44,(byte)0xae,(byte)0x42,(byte)0x60,(byte)0x82 
    try {
         Files.write(ball_path, ball_bytes);
     } catch (IOException e) {
         System.err.println(e);
    

      按行写入文件

    Path rf_wiki_path = Paths.get("C:/rafaelnadal/wiki", "wiki.txt");
     Charset charset = Charset.forName("UTF-8");
     ArrayList<String> lines = new ArrayList<>();
     lines.add("\n");
     lines.add("Rome Masters - 5 titles in 6 years");
     lines.add("Monte Carlo Masters - 7 consecutive titles (2005-2011)");
     lines.add("Australian Open - Winner 2009");
     lines.add("Roland Garros - Winner 2005-2008, 2010, 2011");
     lines.add("Wimbledon - Winner 2008, 2010");
     lines.add("US Open - Winner 2010");
    try {
         Files.write(rf_wiki_path, lines, charset, StandardOpenOption.APPEND);
     } catch (IOException e) {
         System.err.println(e);
    

       读取一个小文件

      NIO.2 提供了两个方法Files.readAllBytes() 和 Files.readAllLines()方法用来读取小的字节或是文本文件。很简单,直接看代码。

    Path ball_path = Paths.get("C:/rafaelnadal/photos", "ball.png");
     try {
         byte[] ballArray = Files.readAllBytes(ball_path);            
     } catch (IOException e) {
         System.out.println(e);
    
    Path wiki_path = Paths.get("C:/rafaelnadal/wiki", "wiki.txt");
     Charset charset = Charset.forName("ISO-8859-1");
     try {
         List<String> lines = Files.readAllLines(wiki_path, charset);
         for (String line : lines) {
              System.out.println(line);
     } catch (IOException e) {
         System.out.println(e);
    

      根据官方文档,该方法识别以下行认为是结束符:

  • \u000D followed by \u000A: 回车接着换行
  • \u000A: 换行
  • \u000D:回车
  •   使用缓存流进行工作

      在多数操作系统中,系统调用文件的读写是一件非常昂贵的操作。在缓冲的方法区和操作系统之间提供了一块内存区域很好的解决了这个问题。

    在调用本地API之前,这些方法在操作系统和应用程序之间的缓存中获取或设置数据。这样大大地提高了效率因为它减少了调用系统的次数。只有在缓冲区为空或是满的时候,才会访问硬盘,根据读取或写入操作。NIO.2提供了两个通过缓存读取和写入文件的方法:Files.newBufferedReader()Files.newBufferedWriter(),相应的,这两个方法会得到Path实例并返回BufferedReaderBufferedWriter 实例。

      使用 newBufferedWriter()方法

       不多说,上代码。

    Path wiki_path = Paths.get("C:/rafaelnadal/wiki", "wiki.txt");
     Charset charset = Charset.forName("UTF-8");
     String text = "\nVamos Rafa!";
     try (BufferedWriter writer = Files.newBufferedWriter(wiki_path, charset, 
                                                                     StandardOpenOption.APPEND)) {
          writer.write(text);
     } catch (IOException e) {
          System.err.println(e);
    

      使用newBufferedReader()方法

    Path wiki_path = Paths.get("C:/rafaelnadal/wiki", "wiki.txt");
     Charset charset = Charset.forName("UTF-8");
     try (BufferedReader reader = Files.newBufferedReader(wiki_path, charset)) {
          String line = null;
          while ((line = reader.readLine()) != null) {
                  System.out.println(line);
     } catch (IOException e) {
          System.err.println(e);
    

       使用无缓存的流工作

      可以使用NIO.2直接获取无缓存的流也可以使用java.io的API中的包装类转换成缓存流。使用无缓存的流的方法有Files.newInputStream()(从一个文件中读取到输入流中),Files.newOutputStream()方法(从输出流中写入到文件中)。

      使用newOutputStream() 方法

      这个方法获取指向文件的路径和说明文件时如何打开的,它会返回一个线程安全的无缓存的刘对象,用来写入字节到文件中。

    //C:\rafaelnadal\equipment\racquet.txt (the file doesn’t initially exist, but it will be automatically created because no options are specified):
    Path rn_racquet = Paths.get("C:/rafaelnadal/equipment", "racquet.txt");
     String racquet = "Racquet: Babolat AeroPro Drive GT";
    byte data[] = racquet.getBytes();
     try (OutputStream outputStream = Files.newOutputStream(rn_racquet)) {
          outputStream.write(data);
     } catch (IOException e) {
          System.err.println(e);
    

      此外,如果你有更好的主意使用缓存的流代替上面的代码,推荐使用基于java.io的API的转换,正如下面的代码,

    Path rn_racquet = Paths.get("C:/rafaelnadal/equipment", "racquet.txt");
     String string = "\nString: Babolat RPM Blast 16";
    try (OutputStream outputStream = Files.newOutputStream(rn_racquet, StandardOpenOption.APPEND);
          BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream))) {
           writer.write(string);
     } catch (IOException e) {
          System.err.println(e);
    

      使用 newInputStream()方法

    //The following code snippet reads the content of the file racquet.txt (the file must exist):
    Path rn_racquet = Paths.get("C:/rafaelnadal/equipment", "racquet.txt");
     int n;     
    try (InputStream in = Files.newInputStream(rn_racquet)) {
          while ((n = in.read()) != -1) {
            System.out.print((char)n);                
     } catch (IOException e) {
         System.err.println(e);
    

      从此而外,你也可以把一个无缓存的流转换成缓存流,下面的代码跟上面的代码实现了相同的功能,但是它更加高效。

    Path rn_racquet = Paths.get("C:/rafaelnadal/equipment", "racquet.txt");
     try (InputStream in = Files.newInputStream(rn_racquet);
          BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
          String line = null;
          while ((line = reader.readLine()) != null) {
                  System.out.println(line);
     } catch (IOException e) {
          System.err.println(e);
    

      ------------------------------------------------------------------------------------------------------------------------

       创建临时目录和文件

      一个临时目录用来存放临时文件。临时目录的存放位置依赖于操作系统。在Windows下,临时目录可以通过“TEMP”环境变量来设置,通常的位置是:C:\Temp, %Windows%\Temp,或者在每个用户的:Local Settings\Temp。而Unix/Linux的临时目录为:/tmp/var/tmp。

      创建一个临时目录

      在NIO.2中通过createTempDirectory()方法用来创建一个临时目录,创建默认的操作系统的临时目录可以调用createTempDirectory()两个参数的方法:前一个用来设置目录的名字的前缀(可以为null),后一个可选参数用来设置文件属性。

    String tmp_dir_prefix = "nio_";
     try {
         //passing null prefix
         Path tmp_1 = Files.createTempDirectory(null);
         System.out.println("TMP: " + tmp_1.toString());
         //set a prefix
         Path tmp_2 = Files.createTempDirectory(tmp_dir_prefix);
         System.out.println("TMP: " + tmp_2.toString());
     } catch (IOException e) {
         System.err.println(e);
    

      则输出结果为:

    TMP: C:\Users\Leo\AppData\Local\Temp\3238630399269555448

    TMP: C:\Users\Leo\AppData\Local\Temp\nio_1097550355199661257

      如果你不知道系统的默认临时目录的路径,也可以使用下面的代码:

    //output: C:\Users\Leo\AppData\Local\Temp\
     String default_tmp = System.getProperty("java.io.tmpdir");
     System.out.println(default_tmp); 

      更进一步,你也可以通过createTempDirectory()方法自定义临时目录的路径,

    Path basedir = FileSystems.getDefault().getPath("C:/rafaelnadal/tmp/");
            String tmp_dir_prefix = "rafa_";
            try {
                if (Files.notExists(basedir)) {
                    Path dir = Files.createDirectories(basedir);
                    // create a tmp directory in the base dir
                    Path tmp = Files.createTempDirectory(dir, tmp_dir_prefix);
                    System.out.println("TMP: " + tmp.toString());
            } catch (IOException e) {
                System.err.println(e);
    

      输出结果为:

    TMP: C:\rafaelnadal\tmp\rafa_1753327229539718259

      使用Shutdown-Hook删除临时文件

      大部分操作系统都会自动地删除临时目录(如果不能,你可以使用多种清理软件),但是,有时候你需要程序级别的控制文件的删除过程。createTempDirectory()方法只是完成了一半的工作,因为删除工作由你负责。为了这一原因你可以shutdown-hook机制,这个机制用来执行任何资源的清理或者保持在JVM关闭之前生效。这个钩子可以是Java线程的实现。Thread的run()方法可以在JVM关闭时执行操作。

        Figure 4-1. The simple flow design of a shutdown-hook

    Runtime.getRuntime().addShutdownHook(new Thread() {
     @Override
     public void run() {
       System.out.println("Shutdown-hook activated ...");
       //… here, cleanup/save resources 
       System.out.println("Shutdown-hook successfully executed ...");
    

      shutdown-hook机制是一个很好的方法用来解决JVM关闭时删除临时目录的问题。同时你也知道,如果目录不为空的话是无法删除的。所以你需要循环每一层目录删除每一个文件直至每个目录为空再删除目录本身。

      使用deleteOnExit() 方法删除临时目录

      另一种删除临时目录的解决方法是调用deleteOnExit()方法,这个方法在java.io.Fi类中。它将在JVM关闭时删除传递的文件或目录参数。因为这个方法需要被每个目录和文件调用,所以它最不吸引人的地方就是需要为每个临时实体消耗内存。

      注意,如果你的系统需要长时间运行或者在很短的时间内需要创建很多文件和目录,则使用deleteOnExit()是个很坏的主意。它需要占用大量内存即使JVM退出后还没有释放。

      下面演示deleteOnExit()方法的使用。

    Path basedir = FileSystems.getDefault().getPath("C:/rafaelnadal/tmp/");
     String tmp_dir_prefix = "rafa_";
     try {
         //create a tmp directory in the base dir
         Path tmp_dir = Files.createTempDirectory(basedir, tmp_dir_prefix);
         File asFile = tmp_dir.toFile();
         asFile.deleteOnExit();
         //simulate some I/O operations over the temporary file by sleeping 10 seconds
         //when the time expires, the temporary file is deleted            
         //EACH CREATED TEMPORARY ENTRY SHOULD BE REGISTERED FOR DELETE ON EXIT
         Thread.sleep(10000);
         //operations done
     } catch (IOException | InterruptedException e) {
        System.err.println(e);
    

      创建临时文件

      不罗嗦,一些概念跟创建临时目录一样,直接上代码。

    String tmp_file_prefix = "rafa_";
     String tmp_file_sufix=".txt";
     try {
         //passing null prefix/suffix
         Path tmp_1 = Files.createTempFile(null,null);
         System.out.println("TMP: " + tmp_1.toString());
         //set a prefix and a suffix
         Path tmp_2 = Files.createTempFile(tmp_file_prefix, tmp_file_sufix);
         System.out.println("TMP: " + tmp_2.toString());
     } catch (IOException e) {
         System.err.println(e);
    

      同样,你也可以自定义临时文件的目录。

    Path basedir = FileSystems.getDefault().getPath("C:/rafaelnadal/tmp");
     String tmp_file_prefix = "rafa_";
     String tmp_file_sufix=".txt";
     try {
         Path tmp_3 = Files.createTempFile(basedir, tmp_file_prefix, tmp_file_sufix);
         System.out.println("TMP: " + tmp_3.toString());
     } catch (IOException e) {
         System.err.println(e);
    

      使用 Shutdown-Hook机制删除临时文件

      下面代码在C:\rafaelnadal\tmp下创建临时文件,等待10秒钟后,当JVM退出后删除临时文件。

    Path basedir = FileSystems.getDefault().getPath("C:/rafaelnadal/tmp");
     String tmp_file_prefix = "rafa_";
     String tmp_file_sufix = ".txt";
     try {
         final Path tmp_file = Files.createTempFile(basedir, tmp_file_prefix, tmp_file_sufix);
         Runtime.getRuntime().addShutdownHook(new Thread() {
         @Override
         public void run() {
         System.out.println("Deleting the temporary file ...");
         try {
             Files.delete(tmp_file);
         } catch (IOException e) {
             System.err.println(e);
         System.out.println("Shutdown hook completed...");
     //simulate some I/O operations over the temporary file by sleeping 10 seconds
     //when the time expires, the temporary file is deleted            
     Thread.sleep(10000);
     //operations done
     } catch (IOException | InterruptedException e) {
         System.err.println(e);
    

      使用 deleteOnExit() 方法删除临时文件

    Path basedir = FileSystems.getDefault().getPath("C:/rafaelnadal/tmp");
     String tmp_file_prefix = "rafa_";
     String tmp_file_sufix = ".txt";
     try {
         final Path tmp_file = Files.createTempFile(basedir, tmp_file_prefix, tmp_file_sufix);
         File asFile = tmp_file.toFile();
         asFile.deleteOnExit();
         //simulate some I/O operations over the temporary file by sleeping 10 seconds
         //when the time expires, the temporary file is deleted
         Thread.sleep(10000);
         //operations done
     } catch (IOException | InterruptedException e) {
         System.err.println(e);
    

      使用DELETE_ON_CLOSE枚举参数删除临时文件

      另一种比较独特的删除临时文件的方式是使用DELETE_ON_CLOSE选项。它会在流关闭的时候删除文件。

    Path basedir = FileSystems.getDefault().getPath("C:/rafaelnadal/tmp");
     String tmp_file_prefix = "rafa_";
     String tmp_file_sufix = ".txt";
     Path tmp_file = null;
     try {
         tmp_file = Files.createTempFile(basedir, tmp_file_prefix, tmp_file_sufix);
     } catch (IOException e) {
         System.err.println(e);
     try (OutputStream outputStream = Files.newOutputStream(tmp_file, 
                                           StandardOpenOption.DELETE_ON_CLOSE);
          BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream))) {
          //simulate some I/O operations over the temporary file by sleeping 10 seconds
          //when the time expires, the temporary file is deleted            
          Thread.sleep(10000);
          //operations done
     } catch (IOException | InterruptedException e) {
          System.err.println(e);
    

      除此而外,你甚至不用调用createTempFile()方法,当你使用CREATE选项与DELETE_ON_CLOSE组合使用时:

    String tmp_file_prefix = "rafa_";
     String tmp_file_sufix = ".txt";
     Path tmp_file = null;
     tmp_file = FileSystems.getDefault().getPath("C:/rafaelnadal/tmp", tmp_file_prefix + 
                                                                     "temporary" + tmp_file_sufix);
     try (OutputStream outputStream = Files.newOutputStream(tmp_file, StandardOpenOption.CREATE, 
                                                               StandardOpenOption.DELETE_ON_CLOSE);
          BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream))) {
          //simulate some I/O operations over the temporary file by sleeping 10 seconds
          //when the time expires, the temporary file is deleted            
          Thread.sleep(10000);
          //operations done
     } catch (IOException | InterruptedException e) {
          System.err.println(e);
    

      目录或文件的删除,复制和移动

      删除目录或文件

      NIO.2提供了两个用来删除目录或文件的方法:Files.delete()Files.deleteIfExits()。这两个都接受单一的参数指定删除的路径。但是Files.delete()无返回值,Files.deleteIfExits()返回boolean值根据文件是否删除成功。Files.delete()试图删除路径下的文件或目录,如果删除失败,则会抛出以下异常。NoSuchFileException (i路径不存在), DirectoryNotEmptyException (i目录不为空), IOException (输入输出错误发生), or SecurityException (无删除权限)。

    Path path = FileSystems.getDefault().getPath("C:/rafaelnadal/photos", "rafa_1.jpg");
     //delete the file
     try {
          Files.delete(path);
     } catch (NoSuchFileException | DirectoryNotEmptyException | IOException | 
              SecurityException e) {
          System.err.println(e);
    

      就像名字建议的一样,Files.deleteIfExists()方法只有在文件存在的时候删除,这就意味着如果文件不存在(代替了抛出NoSuchFileException异常)不能删除的情况下返回boolean值false。这种应用在多线程删除文件的时候非常有用。你不想第一个线程就抛出异常。

    try {
         boolean success = Files.deleteIfExists(path);
         System.out.println("Delete status: " + success);
     } catch (DirectoryNotEmptyException | IOException | SecurityException e) {
         System.err.println(e);
    

      复制目录或文件

      NIO.2中提供了Files.copy()方法来复制目录和文件。它提供了StandardCopyOptionLinkOption 枚举下的很多选项作为参数:

  • REPLACE_EXISTING: 如果目标文件存在,则替换;当拷贝符号链接文件时,真实文件不拷贝,值拷贝符号链接文件。
  • COPY_ATTRIBUTES: 拷贝文件并关联的属性。
  • NOFOLLOW_LINKS: 不包含符号链接的文件或目录.
  •   这些枚举类型静态导入到程序中,

     import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
     import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;
     import static java.nio.file.LinkOption.NOFOLLOW_LINKS; 

      在两个路径间复制

    Path copy_from = Paths.get("C:/rafaelnadal/grandslam/AustralianOpen", "draw_template.txt");
     Path copy_to= Paths.get("C:/rafaelnadal/grandslam/USOpen",copy_from.getFileName().toString());
     try {
         Files.copy(copy_from, copy_to, REPLACE_EXISTING, COPY_ATTRIBUTES, NOFOLLOW_LINKS);
     } catch (IOException e) {
         System.err.println(e);
    

       从一个输入流中拷贝到文件

    Path copy_from = Paths.get("C:/rafaelnadal/grandslam/AustralianOpen", "draw_template.txt");
     Path copy_to = Paths.get("C:/rafaelnadal/grandslam/Wimbledon", "draw_template.txt");
    try (InputStream is = new FileInputStream(copy_from.toFile())) {
         Files.copy(is, copy_to, REPLACE_EXISTING);
    } catch (IOException e) {
          System.err.println(e);
    

      文件输入流可能来自不同的方式。例如下面的代码中文件来自于互联网的URL。

    Path copy_to = Paths.get("C:/rafaelnadal/photos/rafa_winner_2.jpg");
     URI u = URI.create("https://lh6.googleusercontent.com/--
                             udGIidomAM/Tl8KTbYd34I/AAAAAAAAAZw/j2nH24PaZyM/s800/rafa_winner.jpg");
    try (InputStream in = u.toURL().openStream()) {
         Files.copy(in, copy_to);
    } catch (IOException e) {
          System.err.println(e);
    

      从一个文件拷贝到输出流

    Path copy_from = Paths.get("C:/rafaelnadal/grandslam/AustralianOpen", "draw_template.txt");
     Path copy_to = Paths.get("C:/rafaelnadal/grandslam/RolandGarros", "draw_template.txt");
    try (OutputStream os = new FileOutputStream(copy_to.toFile())) {
         Files.copy(copy_from, os);
    } catch (IOException e) {
          System.err.println(e);
    

      移动文件和目录

      在这部分,你将会学习到 如何使用Files.move()方法移动目录和文件。这个方法有一可选参数用来指定一枚举类型:

  • REPLACE_EXISTING:若目标文件存在,则替换.
  • ATOMIC_MOVE: 执行的文件将被作为一个原子操作,保证任何过程监控文件的目录将会访问一个完整的文件。
  •   默认情况下(没有明确指定选项) move()尝试移动文件到目标文件,如果目标文件存在,则会失败。除非源文件与目标文件是同一文件(isSameFile()返回 true),这种情况方法不会生效。

    Path movefrom = FileSystems.getDefault().getPath("C:/rafaelnadal/rafa_2.jpg");
     Path moveto = FileSystems.getDefault().getPath("C:/rafaelnadal/photos/rafa_2.jpg");
    try {
         Files.move(movefrom, moveto, StandardCopyOption.REPLACE_EXISTING);
     } catch (IOException e) {
         System.err.println(e);
    

      如果你不想hard-code移动文件的名字,可以使用Path.resolve()方法,通过这种方法,你可以直接从需要移动的路径中抽取文件名字给移动文件的名字。

       Path movefrom = FileSystems.getDefault().getPath("C:/rafaelnadal/rafa_2.jpg");
        Path moveto_dir = FileSystems.getDefault().getPath("C:/rafaelnadal/photos");
        System.out.println(moveto_dir.resolve(movefrom.getFileName())); // C:\rafaelnadal\photos\rafa_2.jpg
        try {
             Files.move(movefrom, moveto_dir.resolve(movefrom.getFileName()), 
                                                                 StandardCopyOption.REPLACE_EXISTING);
         } catch (IOException e) {
             System.err.println(e);
    

      重命名文件

      这开起来有些诡异,可以通过Files.move()Path.resolveSibling() 重命名一个文件。

    Path movefrom = FileSystems.getDefault().getPath("C:/rafaelnadal/photos/rafa_2.jpg");
    try {
         Files.move(movefrom, movefrom.resolveSibling("rafa_2_renamed.jpg"), 
                                                             StandardCopyOption.REPLACE_EXISTING);
     } catch (IOException e) {
         System.err.println(e);