Java核心技术读书笔记11- 2 文件操作


2.操作文件

在Java中,Path类和Files类封装了在用户机器上处理文件系统所需的所用功能。这两个类在Java SE 7中被引入,组合这两个类操作文件、路径将比java第一个版本的File类要方便很多。

2.1 Path类
Path表示的是一个目录名序列,其后还可以跟随一个文件名。路径中第一个部件可以是根目录,例如/或C:,允许访问的根部件取决于文件系统。以根部件开始的路径是绝对路径否则是相对路径。
创建一个Path可以使用Paths类的静态方法get,该方法可以接受一个或多个字符串参数,并用系统的路径分隔符将它们拼接起来

父路径是指除了最后一级外的前面所有路径串。

2.2 Files类
Files类包含了大量对文件的操作,包括对文件与目录的创建、删除、改变位置与内容,获取属性以及使用相应方法完成迭代目录。
创建文件、目录
Files的静态方法createFile与createDirectoy方法可以创建Path参数的最后一级文件与路径,若路径上存在不存在的部件则抛出异常。这两个方法在创建时的操作是原子性的,同时若创建的目录或文件已经存在则会抛出异常。
使用createDirectories方法可以创建路径,若路径上有不存在的部件也可以一并创建出来。

复制、移动和删除文件

上述相关方法要求源Path必须存在,对于复制或移动操作,相当于把文件和目录从from复制或移动成to。删除操作同理,删除路径最后一级的文件/空目录。如果复制或移动时目标已经存在则将抛出异常,若想对其进行覆盖可以在参数列表最后指定REPLACE_EXISTING,若果想复制所有文件属性可以使用ATTRIBUTES参数,你也可以把操作定义为原子性的,参数为ATOMIC_MOVE,可以使用的参数列表如下:

读写文件
Files方法可以使普通文件的读写变得更快捷。Files方法可以很便捷的使用readAllBytes方法和readAllLines方法将一个文件的内容全部读取为一个字节数组或者字符串集合(每个元素是一行)。也可以使用write方法将数组或元素为CharSequence的集合写入文件。如果文件过大或不方便一次读写也可以根据文件返回输入输出流与带缓冲的读写器。

获取文件信息
包括检查一个Path是文件、路径或者是符号链接,以及一些文件的属性,包括读写属性,是否存在,隐藏,文件的字节数,所有者等信息。同时,你也可以获取一个文件属性集对象,再通过该对象获取一些文件属性。

迭代目录与文件访问器
通过Path和Files两个类可以更方便的访问目录中的文件或子目录。若想进行更深层次的递归访问,也可以使用文件访问器来处理详细的处理遍历中每一步的逻辑,实例代码如下:

    public static void main(String[] args) throws IOException {
        File f = new File("file");
        String[] list = f.list();
        System.out.println(Arrays.toString(list)); //旧的File类下访问一个目录下所有子项的过程,如果想递归遍历更深层,需要检查每一项字符串是不是目录

//        DirectoryStream file = Files.newDirectoryStream(Paths.get("file"));
        DirectoryStream file = Files.newDirectoryStream(Paths.get("file"), "*.xml"); //支持windows的glob语法作为参数
        file.iterator().forEachRemaining(System.out::println); //新版Files搭配Path使用返回的是Path组成的可迭代流,可以很方便的直接处理Path对象
        file.close();

        //如果想要访问一个目录下的所有子孙成员可以使用WalkFileTree方法并传入一个文件访问器(FileVisitor)对象,这个对象中的方法将响应访问文件时的不同阶段
        //接着,可以在方法中编写相应的逻辑完成处理
        Files.walkFileTree(Paths.get("file"), new FileVisitor() { //这里使用一个匿名内部类作为传入对象
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                System.out.println("即将访问目录:"+ dir);
                return FileVisitResult.CONTINUE; //在遍历完成一个阶段后可以指定相应的处理策略,FileVisitResult.CONTINUE为继续访问
            }

            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                System.out.println("可以处理文件:" + file);
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
                System.out.println(file + "访问失败");
                return FileVisitResult.TERMINATE; //意为遇到一个异常则整体终止访问并抛出这个异常
            }

            @Override
            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                System.out.println("目录" + dir + "访问结束");
                return FileVisitResult.CONTINUE;
            }
        });
    }

方法完成后可以进行处理如下:

目录结构与结果如下:

因为FileVisitor是一个接口,所以你如果要实现这个接口就必须要把所有方法都实现。如果看过上述代码之后,感觉觉得过于麻烦,实际的需求不需要太多的处理,例如你可能只想能够处理文件就可以,那么可以使用扩展SimpleFileVisitor类,然后将该类作为参数传入。该类是一个FileVisitor的实现类,除了visitFileFailed方法会抛出异常外,其余方法均不做任何处理,因此,你可以只重写你需要的方法即可:

    public static void main(String[] args) throws IOException {
        Files.walkFileTree(Paths.get("file"), new SimpleFileVisitor(){
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                System.out.println("可以处理文件:" + file);
                return FileVisitResult.CONTINUE;
            }
        });
    }

Glob模式的写法:

ZIP文件系统
Paths类会在默认文件系统中查找路径,即用户本地磁盘中的文件。你也可以有别的文件系统,其中最有用的之一就是ZIP文件系统。使用该系统可以以ZIP文档作为目录并在其中进行文件操作。
首先你需要使用FileSystem.newFileSystem方法根据一个文件返回文件系统,之后对这个文件系统内部文件的所有操作都有这个文件系统getPath方法返回的路径提供。
实例代码:

    public static void main(String[] args) throws IOException {
        FileSystem fs = FileSystems.newFileSystem(Paths.get("file/zipFile.zip"), (ClassLoader) null); //加载文件系统
        Files.walkFileTree(fs.getPath("\\"), new SimpleFileVisitor(){ //使用文件系统返回的目录
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                System.out.println("可以处理文件:" + file.getFileName());
                Files.copy(file, Paths.get("file\\f1\\" + file.getFileName()),StandardCopyOption.REPLACE_EXISTING); //将ZIP文档中的文件复制出来,若文件已经存在,则覆盖
                System.out.println("该文件已经被拷贝至"+Paths.get("file\\f1")+"目录下");
                return FileVisitResult.CONTINUE;
            }
        });
    }