zoukankan      html  css  js  c++  java
  • Java NIO Path接口和Files类配合操作文件

    Java NIO Path接口和Files类配合操作文件

    @author ixenos

    Path接口


    1、Path表示的是一个目录名序列,其后还可以跟着一个文件名,路径中第一个部件是根部件时就是绝对路径,例如 / 或 C: ,而允许访问的根部件取决于文件系统;

    2、以根部件开始的路径是绝对路径,否则就是相对路径;

    3、静态的Paths.get方法接受一个或多个字符串,字符串之间自动使用默认文件系统的路径分隔符连接起来(Unix是 /,Windows是 ),这就解决了跨平台的问题,接着解析连接起来的结果,如果不是合法路径就抛出InvalidPathException异常,否则就返回一个Path对象;

    1 //假设是Unix的文件系统
    2 Path absolute = Paths.get("/home", "cat"); //绝对路径
    3 
    4 Path relative = Pahts.get("ixenos", "config", "user.properties"); //相对路径

    4、由String路径获取Path对象

    get还可以获取一整条路径(即多个部件构成的单个字符串),例如从配置文件中读取路径:

    1 String baseDir = properties.getProperty("base.dir");
    2 //可能获得 /opt/ixenos 或者 C:Program Filesixenos
    3 Path basePath = Paths.get(baseDir);

    5、组合或解析路径

      1) 调用 p.resolve(q) 将按下面的规则返回一个Path:如果q是绝对路径,则返回q,否则追加路径返回 p/q 或者 pq

    1 Path workRelative = Paths.get("work");
    2 Path workPath = basePath.resolve(workRelative);
    3 
    4 //resolve也可以接受字符串形参
    5 Path workPath = basePath.resolve("work");

      2) 调用 p.resolveSibling("q") 将解析指定路径 p 的父路径 o ,并产生兄弟路径 o/q

    1 Path tempPath = workPath.resolveSibling("temp");
    2 /*
    3    如果workPath是 /opt/ixenos/work
    4    那么将创建 /opt/ixenos/temp  
    5 */

      3) 调用 p.relativize(r) 将产生一个冗余路径q,对q进行解析将产生相对路径r,最终r不包含和p的交集路径

     1 /*
     2     pathA为 /home/misty
     3     pathB为 /home/ixenos/config 
     4 
     5     现已pathA对pathB进行相对化操作,将产生冗余路径
     6 */
     7 Path pathC = pathA.relativize(pathB); //此时pathC为 ../ixenos/config
     8 
     9 /*
    10     normalize方法将移除冗余部件
    11 */
    12 Path pathD = pathC.normalize(); //pathD为 /ixenos/config

      4) toAbsolutePath 将产生给定路径的绝对路径,从根部件开始

      5) Path类还有一些有用的断开和组合路径的方法,比如 getParentgetFileNamegetRoot//获得根目录

      6) Path有个toFile方法用来跟遗留类File类打交道,File类也有个toPath方法

    Files工具类


    1、读写文件

    方法签名:

       static path write(Path path, byte[] bytes, OpenOption... options) 

     static path write(Path path, Iterable<? extends CharSequence> lines, OpenOption... options)

     这里只列举下面用到的方法,更多方法请看API文档...

    其中OpenOption是个nio接口,StandardOpenOption是其枚举实现类,各枚举实例功能请查看API文档

     1 /*
     2     Files提供的简便方法适用于处理中等长度的文本文件
     3 
     4     如果要处理的文件长度较大,或者二进制文件,那么还是应该使用经典的IO流
     5     
     6 */
     7 
     8 //将文件所有内容读入byte数组中
     9 byte[] bytes = Files.readAllBytes(path); //传入Path对象
    10 
    11 //之后可以根据字符集构建字符串
    12 String content = new String(bytes, charset);
    13 
    14 //也可以直接当作行序列读入
    15 List<String> lines = Files.readAllLines(path, charset);
    16 
    17 //相反,也可以写一个字符串到文件中,默认是覆盖
    18 Files.write(path, content.getBytes(charset)); //传入byte[]
    19 
    20 //追加内容,根据参数决定追加等功能
    21 Files.write(path, content.getBytes(charset), StandardOpenOption.APPEND); //传入枚举对象,打开追加开关
    22 
    23 //将一个行String的集合List写出到文件中
    24 Files.write(path, lines);

     2、复制、剪切、删除

    方法签名:

      static path copy(Path source, Path target, CopyOption... options)

      static path move(Path source, Path target, CopyOption... options)

      static void delete(Path path) //如果path不存在文件将抛出异常,此时调用下面的比较好

      static boolean deleteIfExists(Path path) 

      这里只列举下面用到的方法,更多方法请看API文档...

    其中CopyOption是个nio接口,StandardCopyOption是其枚举实现类,各枚举实例功能请查看API文档

      其中有个ATOMIC_MOVE可以填入用来保证原子性操作,要么移动成功完成,要么源文件保持在原位置

     1 //复制
     2 Files.copy(fromPath, toPath);
     3 
     4 //剪切
     5 Files.move(fromPath, toPath);
     6 
     7 /*
     8     以上如果toPath已存在,那么操作失败,
     9     如果要覆盖,需传入参数REPLACE_EXISTING
    10     还要复制文件属性,传入COPY_ATTRIBUTES
    11 */
    12 Files.copy(fromPath, toPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);

     3、创建文件和目录

     1 //创建新目录,除了最后一个部件,其他必须是已存在的
     2 Files.createDirectory(path); 
     3 
     4 //创建路径中的中间目录,能创建不存在的中间部件
     5 Files.createDirectories(path);
     6 
     7 /*
     8    创建一个空文件,检查文件存在,如果已存在则抛出异常
     9    而检查文件存在是原子性的,因此在此过程中无法执行文件创建操作
    10 */
    11 Files.createFile(path);
    12 
    13 //添加前/后缀创建临时文件或临时目录
    14 Path newPath = Files.createTempFile(dir, prefix, suffix);
    15 Path newPath = Files.createTempDirectory(dir, prefix);

    4、获取文件信息

    略,具体看API文档,或者corejava page51

    5、迭代目录中的文件

      旧的File类有两个方法获取目录中所有文件构成的字符串数组,String[] list() 和String[] list(FileFilter filter),但是当目录中包含大量文件时,这两方法性能会非常低。

    原因分析:

     1  1、//File类list所有文件
     2     public String[] list() {
     3         SecurityManager security = System.getSecurityManager(); //文件系统权限获取
     4         if (security != null) {
     5             security.checkRead(path);
     6         }
     7         if (isInvalid()) {
     8             return null;
     9         }
    10         return fs.list(this); //底层调用FileSystem的list
    11     }
    12 
    13   //FileSystem抽象类的list
    14  //File类中定义fs是由DefaultFileSystem静态生成的
    15 private static final FileSystem fs = DefaultFileSystem.getFileSystem();
    16 
    17 //因此我们来看一下DefaultFileSystem类,发现是生成一个WinNtFileSystem对象
    18 class DefaultFileSystem {
    19 
    20     /**
    21      * Return the FileSystem object for Windows platform.
    22      */
    23     public static FileSystem getFileSystem() {
    24         return new WinNTFileSystem();
    25     }
    26 }
    27 
    28 
    29 //而WinNtFileSystem类继承于FileSystem抽象类,这里我们主要观察它的list(File file)方法
    30     @Override
    31 public native String[] list(File f);
    32 /*我们可以看到这是个native方法,说明list的操作是由操作系统的文件系统控制的,当目录中包含大量的文件时,这个方法的性能将会非常低。
    33 由此为了替代,NIO的Files类设计了newDirectoryStream(Path dir)及其重载方法,将生成Iterable对象(可用foreach迭代)*///~
    34 
    38 
    39  2、//回调过滤
    40     public String[] list(FilenameFilter filter) { //采用接口回调
    41         String names[] = list(); //调用list所有
    42         if ((names == null) || (filter == null)) {
    43             return names;
    44         }
    45         List<String> v = new ArrayList<>();
    46         for (int i = 0 ; i < names.length ; i++) {
    47             if (filter.accept(this, names[i])) {  //回调FilenameFileter对象的accept方法
    48                 v.add(names[i]);
    49             }
    50         }
    51         return v.toArray(new String[v.size()]);
    52     }

     这时候高科技来了——Files获得可迭代的目录流,

     传入一个目录Path,遍历子孙目录返回一个目录Path的Stream,注意这里所有涉及的Path都是目录而不是文件!

    因此,Files类设计了newDirectoryStream(Path dir)及其重载方法,将生成Iterable对象(可用foreach迭代)

    遍历目录得到一个可迭代的子孙文件集合

    static DirectoryStream<Path> newDirectoryStream(Path dir)
    Opens a directory, returning a DirectoryStream to iterate over all entries in the directory.
    static DirectoryStream<Path> newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter)
    Opens a directory, returning a DirectoryStream to iterate over the entries in the directory.
    static DirectoryStream<Path> newDirectoryStream(Path dir, String glob)

      返回一个 目录流 ,可以看成一个存放着全部Path的实现了Iterable的集合

        因此可用迭代器或foreach迭代,只是使用迭代器的时候要注意不能invoke另一个Iterator:

      • While DirectoryStream extends Iterable, it is not a general-purpose Iterable as it supports only a single Iterator; invoking the iterator method to obtain a second or subsequent iterator throws IllegalStateException

    示例:

    1 try(DirectoryStream<Path> entries = Files.newDirectoryStream(dir))
    2 {
    3     for(Path entry : entries)
    4     {
    5          ...
    6     }
    7 }

      可以传入glob参数,即使用glob模式过滤文件(以取代list(FileFilter filter)):

      newDirectoryStream(Path dir, String glob) 注意是String类型

    1 try(DirectoryStream<Path> entries = Files.newDirectoryStream(dir, "*.java")) //
    2 {
    3     ...
    4 }

      glob模式

    所谓的 glob 模式是指 shell 所使用的简化了的正则表达式

    1.星号 * 匹配路径组成部分0个或多个字符;例如 *.java 匹配当前目录中的所有Java文件

    2.两星号 ** 匹配跨目录边界0个或多个字符;例如 **.java 匹配在所有子目录中的Java文件

    3.问号(?)只匹配一个字符;例如 ????.java 匹配所有四个字符的Java文件,不包括扩展名;使用?是因为*是通配符不指定数量

    4.[...] 匹配一个字符集合,可以用连线 [0-9] 和取反符 [!0-9];例如 Test[0-9A-F].java 匹配Testx.java,假设x是一个十六进制数字,[0-9A-F]是匹配单个字符为十六进制数字,比如B(十六进制不区分大小写)

      如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配(比如 [0-9] 表示匹配所有 0 到 9 的数字)

    5.{...} 匹配由逗号隔开的多个可选项之中的一个;例如 *.{java,class} 匹配所有Java文件和类class文件

    6. 转义上述任意模式中的字符;例如 *** 匹配所有子目录中文件名包含*的文件,这里为 ** 转义,前面是匹配0个或多个字符

    下面是网友总结的Glob模式:

    Glob模式描述
    *.txt 匹配所有扩展名为.txt的文件
    *.{html,htm} 匹配所有扩展名为.html或.htm的文件。{ }用于组模式,它使用逗号分隔
    ?.txt 匹配任何单个字符做文件名且扩展名为.txt的文件
    . 匹配所有含扩展名的文件
    C:Users* 匹配所有在C盘Users目录下的文件。反斜线“”用于对紧跟的字符进行转义
    /home/** UNIX平台上匹配所有/home目录及子目录下的文件。**用于匹配当前目录及其所有子目录
    [xyz].txt 匹配所有单个字符作为文件名,且单个字符只含“x”或“y”或“z”三种之一,且扩展名为.txt的文件。方括号[]用于指定一个集合
    [a-c].txt 匹配所有单个字符作为文件名,且单个字符只含“a”或“b”或“c”三种之一,且扩展名为.txt的文件。减号“-”用于指定一个范围,且只能用在方括号[]内
    [!a].txt 匹配所有单个字符作为文件名,且单个字符不能包含字母“a”,且扩展名为.txt的文件。叹号“!”用于否定

    遍历得到某个目录的所有子孙文件集合再迭代不够爽?来,我们来直接遍历某个目录的所有子孙成员(包括目录和文件)

      我们可以调用Files类的walkFileTree方法,并传入一个FileVisitor接口类型的对象(还有更多方法在API里等你发现……)

     1 /*传入一个FileVisitor子类的匿名对象*/
     2 Files.walkFileTree(dir, new SimpleFileVisitor<Path>()
     3     {
     4          //walkFileTree回调此方法来遍历所有子孙
     5          public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException
     6          {
     7               if(attrs.isDirectory()) //自定义的选择,属于业务代码,这和walkFileTree的宗旨(遍历所有子孙成员)无关
     8                   System.out.println(path);
     9               return FileVisitResult.CONTINUE;
    10          }
    11 
    12          public FileVisitResult visitFileFailed(Path path, IOException exc) throws IOException
    13          {
    14               return FileVisitResult.CONTINUE;
    15          }
    16     });

    咱们来总结一下,

    Files.newDirectoryStream(Path dir) 遍历后返回一个可迭代的子孙文件集合;

    Files.walkFileTree(Path dir, FileVisitor fv) 是一个遍历子孙目录和文件过程

    ZIP文件系统


    由上文知道,Paths类会在默认的文件系统中查找路径,即在用户本地磁盘中的文件。

    其实,我们也可以有其他的文件系统,比如ZIP文件系统

    1 /*假设zipname是某个ZIP文件的名字*/
    2 FileSystem fs = FileSystems.newFileSystem(Paths.get(zipname), null);

     上述代码将建立一个基于zipname的文件系统,它包含ZIP文档中的所有文件

      1)如果知道文件名(String类型),那么从这个ZIP文档中复制出这个文件就很容易:

    1 Files.copy(fs.getPath(fileName), targetPath);

        Q:fs.getPath是使用了ZIP文件系统来getPath,那么默认的文件系统能调用吗?

        A:能。FileSystem类中有一个静态的getDefault()方法,返回一个默认的文件系统对象,同样可以由文件名getPath。

          具体getPath(String name)是遍历还是随机访问,有空再去看源码实现。

      2)要列出ZIP文档中的所有文件,同样可以用walkFileTree遍历文件树

     1 FileSystem fs = FileSystems.newFileSystem(Paths.get(fileName), null);
     2 
     3 //walkFileTree需要传入一个要被遍历的目录Path,和一个FileVisitor对象
     4 Files.walkFileTree(fs.getPath("/"), 
     5         newSimpleFileVisitor<Path>(){
     6                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws Exception{
     7                      System.out.println(file);
     8                      return FileVisitResult.CONTINUE; 
     9         });
  • 相关阅读:
    chrome sarfri form.submit bug
    ruby首字母大写
    ubuntu 10.04 中文设置
    添加centos用户
    农行的短信提醒很滞后
    从github删除敏感数据
    从github删除敏感数据
    国内有不有做虚拟化或是叫云平台的朋友
    设置rails header编码
    centos5.5
  • 原文地址:https://www.cnblogs.com/ixenos/p/5851976.html
Copyright © 2011-2022 走看看