读书笔记-《ON JAVA 中文版》-摘要17[第十七章 文件]

第十七章 文件

在丑陋的 Java I/O 编程方式诞生多年以后,Java终于简化了文件读写的基本操作。

在 Java7 中对此引入了巨大的改进。这些新元素被放在 java.nio.file 包下面,过去人们通常把 nio 中的 n 理解为 new 即新的 io,现在更应该当成是 non-blocking 非阻塞 io(io就是input/output输入*/*输出)。最重要的是 Java8 新增的 streams 与文件结合使得文件操作编程变得更加优雅。

我们将看一下文件操作的两个基本组件:

  1. 文件或者目录的路径;

  2. 文件本身。

1. 文件和目录路径

一个 Path 对象表示一个文件或者目录的路径,是一个跨操作系统(OS)和文件系统的抽象,目的是在构造路径时不必关注底层操作系统,代码可以在不进行修改的情况下运行在不同的操作系统上。java.nio.file.Paths 类包含一个重载方法 static get(),该方法接受一系列 String 字符串或一个统一资源标识符(URI)作为参数,并且进行转换返回一个 Path 对象:

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class PathInfo {
    static void show(String id, Object p) {
        System.out.println(id + ":" + p);
    }

    static void info(Path p) {
        show("toString", p);
        show("Exists", Files.exists(p)); // 检查一个Path在文件系统中是否存在
        show("RegularFile", Files.isRegularFile(p)); // 判断是否是一个文件
        show("Directory", Files.isDirectory(p));
        show("Absolute", p.isAbsolute());
        show("FileName", p.getFileName());
        show("Parent", p.getParent());
        show("Root", p.getRoot());
        System.out.println("******************");
    }

    public static void main(String[] args) {
        System.out.println(System.getProperty("os.name"));
        info(Paths.get("C:", "path", "to", "nowhere", "NoFile.txt"));
        Path p = Paths.get("PathInfo.java");
        info(p);
        Path ap = p.toAbsolutePath();
        info(ap);
        info(ap.getParent());
        try {
            info(p.toRealPath());
        } catch (IOException e) {
            System.out.println(e);
        }
        URI u = p.toUri();
        System.out.println("URI: " + u);
        Path puri = Paths.get(u);
        System.out.println(Files.exists(puri));
        File f = ap.toFile();
    }
}

1.1 选取路径部分片段

Path 对象可以非常容易地生成路径的某一部分:

import java.nio.file.Path;
import java.nio.file.Paths;

public class PartsOfPaths {
    public static void main(String[] args) {
        System.out.println(System.getProperty("os.name"));

        /*
         * PS:System.setProperty 设置 user.dir,不设置的话,
         * Paths.get("PartsOfPaths.java").toAbsolutePath()输出的就是 D:\demo\PartsOfPaths.java
         * 并不是全限定名的类路径了。
         */
        System.setProperty("user.dir", "D:\\demo\\src\\main\\java\\files");
        Path p = Paths.get("PartsOfPaths.java").toAbsolutePath();

        /*
         * PS:path.getName(int index)和path.getNameCount():分层拆分路径,
         * 例如对于/a/b/c,getNameCount()返回的就是3,
         * 而对于path.getName(int index),当index分别为0~2时,
         * 得到的结果分别是 a、b、c。
         */
        for (int i = 0; i < p.getNameCount(); i++) {
            System.out.println(p.getName(i));
        }

        /*
         * PS:endsWith() 并不是用来匹配后缀的,而是用来匹配 Path 的
         * 所以这里是 false
         */
        System.out.println("ends with '.java': " + p.endsWith(".java"));

        /*
         * PS:Path 也实现了 Iterable 接口,因此我们也可以通过增强的 for-each 进行遍历
         * path.startsWith(Path other) 或 path.startsWith(String other):
         * 判断 path 的是不是以 other 开头的。
         * 对于 /a/b/c,不是 start with a,而是 start with "/"
         * 本例的 start with 为 "D:\" ,通过 getRoot() 可以获取
         */
        for (Path pp : p) {
            System.out.print(pp + ": ");
            System.out.print(p.startsWith(pp) + " : ");
            System.out.println(p.endsWith(pp));
        }

        System.out.println("Starts with " + p.getRoot() + " " + p.startsWith(p.getRoot()));
    }
}

输出:

Windows 10
demo
src
main
java
files
PartsOfPaths.java
ends with '.java': false
demo: false : false
src: false : false
main: false : false
java: false : false
files: false : false
PartsOfPaths.java: false : true
Starts with D:\ true

—PS: Path 接口,主要用来在文件系统中定位文件,通常表示系统相关的文件路径。Paths 静态工具类,用来根据String格式的路径或者URI返回Path的实例

1.2 路径分析

Files 工具类包含一系列完整的方法用于获得 Path 相关的信息。

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class PathAnalysis {
    static void say(String id, Object result) {
        System.out.println(id + ": " + result);
    }

    public static void main(String[] args) throws IOException {
        System.out.println(System.getProperty("os.name"));
        System.setProperty("user.dir", "D:\\demo\\src\\main\\java\\files");
        Path p = Paths.get("PathAnalysis.java").toAbsolutePath();
        say("Exists", Files.exists(p));
        say("Directory", Files.isDirectory(p));
        say("Executable", Files.isExecutable(p));
        say("Readable", Files.isReadable(p));
        say("RegularFile", Files.isRegularFile(p));
        say("Writable", Files.isWritable(p));
        say("notExists", Files.notExists(p));
        say("Hidden", Files.isHidden(p));
        say("size", Files.size(p));
        say("FileStore", Files.getFileStore(p));
        say("LastModified: ", Files.getLastModifiedTime(p));
        say("Owner", Files.getOwner(p));
        say("ContentType", Files.probeContentType(p));
        say("SymbolicLink", Files.isSymbolicLink(p));
        if (Files.isSymbolicLink(p)) {
            say("SymbolicLink", Files.readSymbolicLink(p));
        }
        
        // 个测试方法 getPosixFilePermissions() 之前我们需要确认一下当前文件系统是否支持 Posix 接口,否则会抛出运行时异常
        if (FileSystems.getDefault().supportedFileAttributeViews().contains("posix")) {
            say("PosixFilePermissions", Files.getPosixFilePermissions(p));
        }
    }
}

输出:

Windows 10
Exists: true
Directory: false
Executable: true
Readable: true
RegularFile: true
Writable: true
notExists: false
Hidden: false
size: 1674
FileStore: 软件 (D:)
LastModified: : 2023-06-14T09:26:50.330762Z
Owner: T (User)
ContentType: null
SymbolicLink: false

推荐一个大佬写的文章:文件操作工具类Files

1.3 Paths的增减修改

我们必须能通过对 Path 对象增加或者删除一部分来构造一个新的 Path 对象。我们使用 relativize() 移除 Path 的根路径,使用 resolve() 添加 Path 的尾路径(不一定是“可发现”的名称)。

2. 目录

Files 工具类包含大部分我们需要的目录操作和文件操作方法。

3. 文件系统

使用静态的 FileSystems 工具类获取"默认"的文件系统,也可以在 Path 对象上调用 getFileSystem() 以获取创建该 Path 的文件系统。你可以获得给定 URI 的文件系统,还可以构建新的文件系统(对于支持它的操作系统)。

import java.nio.file.FileStore;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;

public class FileSystemDemo {
    static void show(String id, Object o) {
        System.out.println(id + ": " + o);
    }

    public static void main(String[] args) {
        System.out.println(System.getProperty("os.name"));
        FileSystem fsys = FileSystems.getDefault();
        for (FileStore fs : fsys.getFileStores()) { // 获取系统盘
            show("File Store", fs);
        }
        for (Path rd : fsys.getRootDirectories()) { // 根目录
            show("Root Directory", rd);
        }
        show("Separator", fsys.getSeparator()); // 分隔符
        // 返回此文件系统的可选操作
        show("UserPrincipalLookupService", fsys.getUserPrincipalLookupService());
        show("isOpen", fsys.isOpen());
        show("isReadOnly", fsys.isReadOnly());
        // 返回创建此文件系统的提供程序
        show("FileSystemProvider", fsys.provider());
        // 返回文件系统支持的文件属性视图名称
        show("File Attribute Views", fsys.supportedFileAttributeViews());
    }
}

输出:

Windows 10
File Store: 系统 (C:)
File Store: 软件 (D:)
Root Directory: C:\
Root Directory: D:\
Separator: \
UserPrincipalLookupService: sun.nio.fs.WindowsFileSystem$LookupService$1@4eec7777
isOpen: true
isReadOnly: false
FileSystemProvider: sun.nio.fs.WindowsFileSystemProvider@3b07d329
File Attribute Views: [owner, dos, acl, basic, user]

4. 路径监听

通过 WatchService 可以设置一个进程对目录中的更改做出响应。在这个例子中,delTxtFiles() 作为一个单独的任务执行,该任务将遍历整个目录并删除以 .txt 结尾的所有文件,WatchService 会对文件删除操作做出反应:

import java.io.IOException;
import java.nio.file.*;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;

public class PathWatcher {
    static Path test = Paths.get("D:\\demo\\src\\test");

    static void delTxtFiles() {
        try {
            Files.walk(test).filter(f -> f.toString().endsWith(".txt"))
                    .forEach(f -> {
                        System.out.println("deleting " + f);
                        try {
                            Files.delete(f);
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    });
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        // PS:在 D:\demo\src\test 下 创建 Hello.txt
        Files.createFile(test.resolve("Hello.txt"));

        // PS:获取一个路径监听
        WatchService watcher = FileSystems.getDefault().newWatchService();

        // PS:将路径监听放在 test 上,监听事件为 delete
        // 可以选择 ENTRY_CREATE,ENTRY_DELETE 或 ENTRY_MODIFY(其中创建 和删除不属于修改)
        test.register(watcher, ENTRY_DELETE);

        // PS:过调用 Executors.newSingleThreadScheduledExecutor() 产生一个 ScheduledExecutorService 对象,
        // 然后调用 schedule() 方法传递所需函数的方法引用,并且设置在运行之前应该等待的时间
        Executors.newSingleThreadScheduledExecutor().schedule(PathWatcher::delTxtFiles, 250, TimeUnit.MILLISECONDS);

        // PS:当目标事件发生时,会返回一个包含 WatchEvent 的 Watchkey 对象。
        // 展示的这三种方法是能对 WatchEvent 执行的全部操作。
        WatchKey key = watcher.take();
        for (WatchEvent evt : key.pollEvents()) {
            System.out.println("evt.context(): " + evt.context() + "\nevt.count(): " + evt.count() + "\nevt.kind(): " + evt.kind());
            // PS:status 为 0:表示正常退出程序,也就是结束当前正在运行中的java虚拟机。
            // status 为 1 或 -1 或 任何其他非零值 :表示非正常退出当前程序。
            // 正常退出 是指如果当前程序还有在执行的任务,则等待所有任务执行完成以后再退出;
            // 非正常退出 是只要时间到了,立刻停止程序运行,不管是否还有任务在执行。
            System.exit(0);
        }

输出:

deleting D:\demo\src\test\Hello.txt
evt.context(): Hello.txt
evt.count(): 1
evt.kind(): ENTRY_DELETE

如果说"监视这个目录",自然会包含整个目录和下面子目录,但实际上 的:只会监视给定的目录,而不是下面的所有内容。如果需要监视整个树目录,必须在整个树的每个子目录上放置一个 Watchservice

5. 文件查找

到目前为止,为了找到文件,我们一直使用相当粗糙的方法,在 path 上调用 toString() ,然后使用 string 操作查看结果。事实证明, java.nio.file 有更好的解决方案:通过在 FileSystem 对象上调用 getPathMatcher() 获得一个 PathMatcher ,然后传入您感兴趣的模式。模式有两个选项: glob 和 regex 。

在这里,我们使用 glob 查找以 .tmp 或 .txt 结尾的所有 Path :

import java.io.IOException;
import java.nio.file.*;

public class Find {
    public static void main(String[] args) throws IOException {
        Path test = Paths.get("D:\\demo\\src\\test");
        Files.createDirectories(test.resolve("dir.tmp"));

        // PS: **/ 表示“当前目录及所有子目录”
        // 单 * 表示“任何东西”,然后是一个点,然后大括号表示正在寻找以 .tmp 或 .txt 结尾的东西
        PathMatcher matcher = FileSystems.getDefault()
                .getPathMatcher("glob:**/*.{tmp,txt}");

        Files.walk(test).filter(matcher::matches)
                .forEach(System.out::println);
    }
}

输出:

D:\demo\src\test\a\1.tmp
D:\demo\src\test\a\2.tmp
D:\demo\src\test\b\3.txt
D:\demo\src\test\b\4.txt
D:\demo\src\test\dir.tmp

—PS:先在 test 目录下面手动创建了几个文件,dir.tmp 是代码运行创建

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F67S2k2y-1686903589097)(img/161.png)]

6. 文件读写

Files.readAllLines() 一次读取整个文件(因此,“小”文件很有必要),产生一

个 List 。

先在 test 目录下,创建 Cheese.dat 内容为:

// streams/Cheese.dat
Not much of a cheese shop really, is it?
Finest in the district, sir.
And what leads you to that conclusion?
Well, it's so clean.
It's certainly uncontaminated by cheese.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eUIGSuZb-1686903589098)(img/172.png)]

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class ListOfLines {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("D:\\demo\\src\\test\\Cheese.dat").toAbsolutePath();
        
        // PS:Files.readAllLines 入参可以是 path 返回一个 list
        Files.readAllLines(path).stream()
                .filter(line -> !line.startsWith("//"))
                .map(line -> line.substring(0, line.length() / 2))
                .forEach(System.out::println);
    }
}

输出:

Not much of a cheese
Finest in the 
And what leads you 
Well, it's
It's certainly uncon

Files.write() 被重载以写入 byte 数组或任何 Iterable 对象(它也有 Charset 选项):

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Random;

public class Writing {
    static Random rand = new Random(47);
    static final int SIZE = 1000;

    public static void main(String[] args) throws IOException {
        byte[] bytes = new byte[SIZE];

        // PS:生成随机字节并将它们放入用户提供的字节数组中。生成的随机字节数等于字节数组的长度
        rand.nextBytes(bytes);

        Path path = Paths.get("D:\\demo\\src\\test\\bytes.dat");

        // PS:Files.write 放入 byte 数组
        Files.write(path, bytes);
        System.out.println("bytes.dat: " + Files.size(path));

        Path path2 = Paths.get("D:\\demo\\src\\test\\Cheese.dat");
        Path path3 = Paths.get("D:\\demo\\src\\test\\Cheese.txt");
        List<String> stringList = Files.readAllLines(path2);

        // PS:Files.write 放入任何 Iterable 对象
        Files.write(path3,stringList);
        System.out.println("Cheese.txt: " + Files.size(path3));
    }
}

输出:

bytes.dat: 1000
Cheese.txt: 199

—PS:运行完代码在 test 下会自动创建 bytes.dat 和 Cheese.txt

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FFH3vFJs-1686903589099)(img/173.png)]

如果文件大小有问题怎么办? 比如说:

  1. 文件太大,如果你一次性读完整个文件,你可能会耗尽内存。

  2. 您只需要在文件的中途工作以获得所需的结果,因此读取整个文件会浪费时间。

Files.lines() 方便地将文件转换为行的 Stream :

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class ReadLineStream {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("D:\\demo\\src\\test\\Cheese.dat");
        Files.lines(path).skip(2)
                .findFirst()
                .ifPresent(System.out::println);
    }
}

输出:

Finest in the district, sir.

7. 本章小结

虽然本章对文件和目录操作做了相当全面的介绍,但是仍然有没被介绍的类库中的功能——一定要研究 java.nio.file 的 Javadocs,尤其是 java.nio.file.Files 这个类。

Java 7 和 8 对于处理文件和目录的类库做了大量改进。使用文件现在很简单,甚至很有趣,这是你以前永远想不到的。

推荐一篇文章:详解 File、Path、Paths、Files 四个类,Java操作文件不再难

自我学习总结:

  1. Path 表示一个文件或者目录的路径,通过工具类 Paths.get() 获取
  2. Path 实现了 Iterable 接口,可以通过增强 for 循环遍历,里面的元素是全路径的层级
  3. Files 静态工具类,包含了一系列完整的方法用于获得 Path 相关的信息
  4. Path 对象的 relativize() 可以移除根路径,resolve() 可以添加路径,例如在一个目录的 Path 下面加个 test.txt path.resolve(“test.txt”)
  5. 使用静态的 FileSystems 工具类获取"默认"的文件系统,也可以在 Path 对象上调用 getFileSystem() 以获取创建该 Path 的文件系统。
  6. 通过 FileSystems.getDefault().newWatchService() 获取一个路径监听,Path 对象通过 .register(watcher, ENTRY_DELETE) 放置监听,监听事件有 ENTRY_CREATE,ENTRY_DELETE 或 ENTRY_MODIFY
  7. 通过在 FileSystem 对象上调用 getPathMatcher() 获得一个 PathMatcher ,用于文件查找
  8. 通过 Files.readAllLines() 对小文件进行读操作,.stream() 可生成流
  9. Files.lines() 对大文件进行读操作,返回的是个流
  10. Files.write() 可以进行文件写操作,第一个入参为 Path 对象,第二个参数可以是 byte 数组或者 Iterable 对象
  11. 本章关键词 Path Paths Files FileSystems
    在这里插入图片描述
    (图网,侵删)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值