• Java> 文件操作(Files, Path, File)


    概要

    Files: 操作文件的工具类,Java7加入,封装了用户机器上处理文件系统所需所有功能。包含了文件创建、复制、写入、读出、删除,获取文件信息,快捷访问、遍历目录等功能。使用较File更方便,由于结合了Path/Stream等类,很擅长批量处理文件。
    Path: 表示文件路径,Java7加入,常用Paths创建,配合Files使用。
    File: 传统文件类,Java 1.0加入,功能强大,但使用繁琐。

    Path类

    Path通过表示一个目录名序列,后面还可以跟着一个文件名,来表示路径。

    创建方式

    • 通过指定路径字符串 Paths.get()
      通过Paths.get() 拼接多个字符串,组成路径。
      包含2类:1)绝对路径;2)相对路径。
      路径不必是一个存在的文件,仅仅只是一个抽象的名字序列。只有当要创建文件的时候,才会调用方法根据路径创建文件。
    Path absolute = Paths.get("C:\Users", "test.txt"); // 绝对路径, 使用Windows风格路径分隔符  
    Path relative = Paths.get("config", "properties", "user.properties"); // 相对路径
    
    • 通过已有Path + 字符串组合 Path.resolve()和Path.resolveSibling()
    // resolve path
    Path basePath = Paths.get("rss"); // 通过字符串获取路径
    Path resolvePath = basePath.resolve("resolvePath"); // 组合basePath和"resolvePath"得到新路径
    Path resolveSibling = basePath.resolveSibling("resolveSibling"); // 得到basePath兄弟路径"resolveSibling"
    // 打印转换path
    System.out.println("basePath = " + basePath.toAbsolutePath());
    System.out.println("resolvePath = " + resolvePath.toAbsolutePath());
    System.out.println("resolveSibling = " + resolveSibling.toAbsolutePath());
    
    • 产生相对路径relativize
      relativize是resolve逆操作。
      p.resolve(r)结果产生路径q = "p/r";p.relative(q)产生r,即r="../q"。简单来说,就是resolve是利用母路径path+字符串(作为子路径),组合成新路径;relativize是通过母路径 - 组合的新路径,得到相对路径。
    // relative path
    System.out.println(basePath.relativize(resolvePath));
    
    • 打印转换path和relative path运行结果
    basePath = F:workspaceIDEAJava_Core2
    ss
    resolvePath = F:workspaceIDEAJava_Core2
    ss
    esolvePath
    resolveSibling = F:workspaceIDEAJava_Core2
    esolveSibling
    resolvePath
    
    • 其他常用操作
      normalize 移除所有冗余.和..部件
      toAbsolutePath 产生给定路径的绝对路径
      getParent 获取父路径
      getFileName 获取文件名
      getRoot 获取根目录,Unix是 / , Windows是所在盘符根目录
      toFile 转换成File类对象

    通过Path构建Scanner对象

    Scanner in = new Scanner(Paths.get("C:\Users	est.txt"));
    

    Files类

    创建文件

    • 创建目录
      如果目录已经存在会抛出异常FileAlreadyExistsException. 创建目录是原子性的
    Path path = Paths.get("dir");
    Files.createDirectory(path); // 创建以path为路径的目录
    
    • 创建文件
      如果文件已经存在会抛出异常FileAlreadyExistsException. 创建文件是原子性的
    Path path = Paths.get("file");
    Files.createDirectory(pat); // 创建以path为路径的文件, 文件可以与目录路径及同名 
    
    • 在给定位置或者系统指定位置,创建临时文件/目录
    Path newPath = Files.createTempFile(dir, prefix, suffix); // dir路径下, 创建以prefix为前缀, suffix为后缀的名称的文件
    Path newPath = Files.createTempFile(prefix, suffix); // 系统默认临时目录路径下, 创建以prefix为前缀, suffix为后缀的名称的文件
    Path newPath = Files.createTempDirectory(dir, prefix); // dir路径下, 创建以prefix为前缀, suffix为后缀的名称的目录
    Path newPath = Files.createTempDirecotry(prefix); // 系统默认临时目录路径下, 创建以prefix为前缀, suffix为后缀的名称的目录
    

    dir是一个Path对象,给定创建临时文件路径;
    prefix,suffix可以为null字符串,分别指定文件名前缀、后缀;
    系统默认临时文件夹路径,Win10x64:C:UsersMartinAppDataLocalTemp

    读写文件

    • 读取/写中小文件
    /* 一次读取所有文件内容 */
    // 一次按二进制读取所有文件内容
    byte[] bytes = Files.readAllBytes(path); // 文件路径Path -> 二进制数组byte[]
    // 将bytes转换成字符串
    String content = new String(bytes, charset); // charset指定字符编码, 如StandardCharsets.UTF_8
    
    // 一次按行读取文件所有内容
    List<String> lines = Files.readAllLines(path);
    
    /* 一次写所有文件内容 */
    // 写一个字符串到文件
    Files.write(path, content.getBytes(charset)); 
    // 追加字符串到文件
    Files.write(path, content.getBytes(charset),StandardOpenOption.APPEND);
    // 写一个行的集合到文件
    Files.write(path, lines);
    
    • 大文件
      要处理大文件和二进制文件,需要用到输入流/输出流,或者使用读入器/写入器。
    InputStream in = Files.newInputStream(path);
    OutputStream out = Files.newOutputStream();
    Reader reader = Files.newBufferedReader(path, charset);
    Writer writer = Writer.newBufferedWriter(path, charset);
    

    上面这些方法较单纯使用FileInputStream, FileOutputStream, BufferedReader, BufferedWriter更为简便。
    例如,如果使用FileInputStream和FileOutputStream,需要这样使用

    // read data from stream
    try(DataInputStream in = new DataInputStream(new FileInputStream("filename"))) {
          ...
    }
    
    // write data to stream
    try(DataOutputStream out = new DataOutputStream(new FileOutputStream("filename"))) {
          ...
    }
    

    简单来说,就是少了专门new的语句;对于Reader/Writer,还少了包装的语句。

    复制文件

    从一个位置复制到另外一个位置

    Files.copy(fromPath, toPath); // fromPath和toPath都是Path对象, 如果目标路径已存在文件, 复制失败
    Files.copy(fromPath, toPath, StandarCopyOption.REPLACE_EXISTING, StandarCopyOption.COPY_ATTRIBUTES); // 选项REPLACE_EXISTING表示想覆盖原有目标路径, COPY_ATTRIBUTES表示复制所有文件属性
    Files.copy(inputStream, toPath); // 从输入流复制到目标路径
    Files.copy(fromPath, outputStream); // 从源路径复制到输出流
    

    移动文件

    从一个位置移动到另外一个位置

    Files.move(fromPath, toPath); // fromPath和toPath都是Path对象
    Files.move(fromPath, toPath, StandardCopyOption.ATOMIC_MOVE); // ATOMIC_MOVE表示该操作是原子性的(要么成功移动到目标路径, 要么失败文件还在原来的位置)
    // move操作无法从输入流到目标路径, 或者从源路径到输出流
    
    

    删除文件

    删除指定路径文件

    Files.delete(path); // 如果指定路径不存在, 报异常NoSuchFileException
    boolean deleted = Files.deleteIfExist(path); // 如果文件存在, 才会删除, 不会报异常. 可以用来删除空目录
    

    获取文件信息

    常用操作

    boolean exists(path) // 文件存在?
    boolean isHidden(path) // 文件隐藏?
    boolean isReadable(path) // 文件可读?
    boolean isWritable(path) // 文件可写?
    boolean isExecutable(path) // 可执行?
    boolean isRegularFile(path) // 是普通文件? 等价于!isSymbolicLink() && !isDirectory() && !isOther()
    boolean isDirectory(path) // 是目录?
    boolean isSymbolicLink(path) // 是符号链接? 
    
    long fileSize = Files.size(path); // 获取文件字节数
    

    获取基本文件属性集
    基本文件属性集主要包括:

    1. 创建文件、最后一次访问以及最后一次修改时间;
    2. 文件是常规文件、目录,还是符号链接;
    3. 文件尺寸;
    4. 文件主键,具体所属类与文件系统相关,有可能是文件唯一标识符,有可能不是;
    BasicFileAttributes attributes = Files.readAttributes(path, BasicFileAttributes.class);
    
    PosixiFileAttributes posixAttributes = Files.readAttributes(path, PosixiFileAttributes.class); // 如果文件系统兼容POSIX, 才能获取到PosixiFileAttributes 实例
    

    访问目录各项

    • 遍历指定目录下各项
      Files.list会返回Stream,而且是惰性读取,处理目录具有大量项时高效。不过,list不会进入子目录,进入子目录使用walk。使用示例
    try(Stream<Path> entries = Files.list(dirPath)) { // 读取目录涉及需要关闭系统资源, 使用try块. 不进入子目录
          entries.forEach(System.out::println); // 打印每个entries项, 也就是打印每个path
    }
    
    try(Stream<Path> entries = Files.walk(dirPath)) { // 会进入子目录
          entries.forEach(System.out.println);
    }
    
    • 遍历并删除指定目录下各项
      使用Files.walk遍历 得到Stream,再利用Stream的forEach方法对各项进行处理
    /* 示例将当前目录rss下所有文件(包括目录)及子文件, 都复制到目录rss2下 */
    Path source = Paths.get("rss"); // 根据实际情况设置字节的source路径
    Path target = Paths.get("rss2");
    
    try(Stream<Path> entries = Files.walk(source)) {
          entries.forEach( p-> {
                try{
                      Path q = target.resolve(source.relative(p)); // 取得p相对于source的相对路径后, 再拼接到target路径下. 相当于是说, 将每个文件相对路径都由source转移到target下
                      if(!Files.exists(q)) {
                            if(Files.isDirectory(q)) Files.createDirectory(q); // 如果是目录, 在target路径下, 根据相对路径创建对应目录
                            else Files.copy(p, q);  // 如果是文件, 从source路径复制到target下
                      }
                } catch(IOException e) {
                      e.printStackTrace();
                }
          });
    }
    

    目录流

    使用Files.walk有一个缺陷:无法方便地删除目录,因为要删除父目录,必须先删除子目录。否则,会抛出异常。
    使用File.newDirectoryStream对象,产生一个DirectoryStream,对遍历过程可以进行更细粒度控制。DirectoryStream不是Stream,而是专门用于目录遍历的接口。它是Iterable的子接口,可以用Iterable的迭代和增强forEach方法。
    还可以搭配glob模式来过滤文件,示例是过滤出dir目录下 后缀名为 .java的文件:

    try(DirectoryStream<Path> entries = Files.newDirectoryStream(dir, "*.java")){
          for (Path entry: entries) {
                Process entry
          }
    }
    

    glob模式

    模式 描述 示例
    * 匹配路径组成部分中0个或多个字符串 *.java 匹配当前目录中的所有java文件
    ** 匹配跨目录边界的0个或多个字符串 **.java 匹配在所有子目录中的java文件
    ? 匹配一个字符 ????.java 匹配所有4个字符的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:\\") // 相当于C:\

    访问目录所有子孙

    如果想要访问某个目录下所有子孙,可以使用walkFileTree(),并向其传递一个FileVisitor对象。这个方法并非简单遍历,而是在遇到文件或目录时,目录被处理前后,访问文件错误时,FileVisitor会收到通知,然后指定执行方式:跳过该文件、跳过目录、跳过兄弟文件、终止访问。

    // walkFileTree得到的通知:
    FileVisitResult visitFile()  // 遇到文件或目录时
    FileVisitResult preVisitDirectory() // 一个目录被处理前
    FileVisitResult postVisitDirectory() // 一个目录被处理后
    FileVisitResult visitFileFailed() // 试图访问文件失败, 或目录发生错误时
    
    // 收到通知后, 可以设置指定的操作
    FileVisitResult.CONTINURE // 继续访问下一个文件
    FileVisitResult.SKIP_SUBTREE // 继续访问, 但不再访问这个目录下任何文件
    FileVisitResult.SKIP_SIBLINGS // 继续访问, 但不再访问这个文件的兄弟文件(同一个目录下的文件)
    FileVisitResult.TERMINATE // 终止访问
    

    便捷类SimpleFileVisitor + Files.walkFileTree()可以实现对目录的细粒度访问,并在在收到相关通知时,有机会进行相应处理。默认SimpleFileVisitor类实现FileVisitor接口,除visitFileFailed() 外(抛出异常并终止访问),其余方法都是直接继续访问,而不做任何处理。
    注意:preVisitDirectory()和postVisitDirectory()通常需要覆盖,否则,访问时遇到不允许打开的目录或者不允许访问的文件时立即失败,进而直接跳转到visitFileFailed()

    示例,展示如何打印给定目录下的所有子目录:

    Files.walkFileTree(Paths.get("F:\test"), new SimpleFileVisitor<Path>() {
        @Override
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
            System.out.println(dir);
            return FileVisitResult.CONTINUE;
        }
    
        @Override
        public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
            System.out.println("postVisitDirectory " + dir);
            return FileVisitResult.CONTINUE;
        }
    
        @Override
        public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
            return FileVisitResult.SKIP_SUBTREE;
        }
    });
    

    执行结果:

    F:	est
    F:	estdir1
    F:	estdir1subdir1
    postVisitDirectory F:	estdir1subdir1
    postVisitDirectory F:	estdir1
    F:	estdir2
    postVisitDirectory F:	estdir2
    F:	estdir3
    postVisitDirectory F:	estdir3
    postVisitDirectory F:	est
    

    示例2,删除目录树(包括其中的文件)
    利用walkFileTree访问到对应路径目录时,利用便捷类SimpleFileVisitor的preVisitDirectory先删除当前目录下的所有文件,然后访问完毕后,在postVisitDirectory删除当前访问完毕的目录。

    Files.walkFileTree(Paths.get("F:\test"), new SimpleFileVisitor<Path>() {
        @Override
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
            System.out.println(dir);
    
            // 删除dir路径下所有文件(不包含子目录)
            Files.list(dir).forEach(p->{
                try {
                    if (!Files.isDirectory(p))
                        Files.delete(p);
    
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
    
            return FileVisitResult.CONTINUE;
        }
    
        // 删除目录
        @Override
        public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
            System.out.println("postVisitDirectory " + dir);
            if (null != exc) throw exc;
            Files.delete(dir);
            return FileVisitResult.CONTINUE;
        }
    } );
    

    File类

    创建文件

    通过路径字符串创建

    File file = new File("filePath");  // 在当前路径创建名为"filePath"的文件, 此时磁盘上还没有创建对应文件或目录
    
    // 磁盘上创建文件或目录
    File tempFile = File.createTempFile(prefix, suffix, directory);  // 在指定目录或当前目录(directory缺省),创建以prefix/suffix为前缀/后缀的临时文件
    file.createNewFile(); // 以file所代表路径, 创建新文件
    file.mkdir(); // 以file所代表路径, 创建新目录, 注意mkdir()和createNewFile() 不能同时用同一个file所代表路径创建同名文件/目录对象, 否则后者无法创建
    

    读写文件

    单纯File类是没有办法直接读写文件的,这点跟Files是一个明显区别,File类需要借助输入流/输出流(FileInputStream/FileOutputStream),或者读入器/写出器(Reader/Writer)来实现读写功能。
    示例是用DataInputStream和DataOutoutStream进行文件读写包装,支持字符本文以及二进制数据。使用Reader和Writer的方式,这里省略。感兴趣可参考系统学习 Java IO (十三)----字符读写 Reader/Writer 及其常用子类

    // 写数据到文件file
    try(DataOutputStream out = new DataOutputStream(new FileOutputStream((file)))) {
        out.writeInt(1);
        out.writeChar('a');
    }
    
    //从文件file读数据
    try(DataInputStream in = new DataInputStream(new FileInputStream(file))) {
        // read数据类型的个数和顺序一定要和write一致
        int a = in.readInt();
        char c = in.readChar();
    
        System.out.println(a);
        System.out.println(c);
    }
    

    复制和移动

    复制和移动文件,File类没有专门的API,需要自行实现。这里只举一个复制的例子,移动可以看成是 复制+删除源文件。

    /**
    * 利用File类复制文件(包括目录)
    * @note 要求文件只能是普通文件或者目录
    */
    public static File copyFile(String dest, String src) throws IOException {
        File destFile = new File(dest);
        File srcFile = new File(src);
        
        // 源文件不存在, 无法复制
        if (!srcFile.exists()) {
            System.out.println("源文件不存在, 路径: " + srcFile);
            return null;
        }
        
        // 根据源文件类型, 在目标路径新建文件或目录
        if (srcFile.isDirectory()) {
            destFile.mkdir();
            return destFile;
        }
        else srcFile.createNewFile();
    
        // 处理源文件为文件(非目录)的情形
        try(Scanner scanner = new Scanner(new FileInputStream(srcFile))) {
    
            try(PrintWriter writer = new PrintWriter(new FileOutputStream(destFile))){
                while (scanner.hasNext()) {
                    String line = scanner.nextLine();
                    writer.println(line);
                }
            }
        }
    
        return destFile;
    }
    

    删除文件

    删除文件包括删除一般文件,还有目录。文件比较容易,直接调用file.delete()即可,目录的情况较为复杂,因为涉及到目录不为空的情况。与Files.delete()删除目录类似,必须确保带删除目录为空目录,也就是说需要遍历目录及其子目录,先删除目录下的内容,才能删除对应目录,这里不再遨述,后续有机会再补充完整这块示例。

    获取文件信息

    直接调用File对象接口,即可以查询到文件对应信息,主要包括

    String getName() // 获取文件名称
    String getParent() // 获取所在目录名称
    File getParentFile() // 获取文件路径
    boolean canRead() // 获取文件是否可读
    boolean canWrite() // 获取文件是否可写
    boolean exists() // 获取文件是否存在
    boolean isDirectory() // 表示是否是一个目录
    boolean isFile() // 表示是否是一个标注文件
    long lastModified() // 最近一次修改
    long length() // 文件长度
    String[] list() // 路径名所表示的目录中的文件名列表
    String[] list(FilenameFilter filter) // // 路径名所表示的目录中的文件名列表, 文件名经由filter过滤
    String[] listFiles() // 路径名所表示的目录中的文件路列表
    boolean setReadOnly() // 设置文件为只读
    
    

    Java File类|菜鸟教程

  • 相关阅读:
    python 读取excel表格中的数据
    python 安装 pip 报错解决方案
    HDU-1150-MachineSchedule(二分图匹配)
    POJ-3177-RedundantPaths(边联通分量,缩点)
    POJ-3352-RoadConstruction(边双联通分量,缩点)
    F-三生三世
    D-温暖的签到题
    C-晾衣服
    A-坐飞机
    POJ-2777-CountColor(线段树,位运算)
  • 原文地址:https://www.cnblogs.com/fortunely/p/14051310.html
Copyright © 2020-2023  润新知