zoukankan      html  css  js  c++  java
  • 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类|菜鸟教程

  • 相关阅读:
    547. Friend Circles
    399. Evaluate Division
    684. Redundant Connection
    327. Count of Range Sum
    LeetCode 130 被围绕的区域
    LeetCode 696 计数二进制子串
    LeetCode 116 填充每个节点的下一个右侧节点
    LeetCode 101 对称二叉树
    LeetCode 111 二叉树最小深度
    LeetCode 59 螺旋矩阵II
  • 原文地址:https://www.cnblogs.com/fortunely/p/14051310.html
Copyright © 2011-2022 走看看