Java8 读取、写入、遍历、监控文件及目录

本文介绍Java8中文件操作的新特性,包括读取、写入文件,遍历目录及文件,以及使用WatchService监控目录变更等。通过具体示例展示如何使用这些API简化文件处理流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JavaDoc:https://javadocs.techempower.com/

一、读取文件

参考链接:https://howtodoinjava.com/java8/read-file-line-by-line-in-java-8-streams-of-lines-example/

在下面的实例中,将使用Stream来一行一行的读取文件。将要读取的文件 data.txt 内容如下:

Never
store
password
except
in mind.

将上面的文件一行一行的读取,如果有一行包含 password ,将这行打印出来

1. Java 8 Stream按行读取文件

利用Java 8 Stream按行读取文件内容,过滤包含 password 的行,并取出第一条打印出来

private static void readStreamOfLinesUsingFiles() throws IOException
{
    Stream<String> lines = Files.lines(Paths.get("c:/temp", "data.txt"));
 
    Optional<String> hasPassword = lines.filter(s -> s.contains("password")).findFirst();
 
    if(hasPassword.isPresent())
    {
        System.out.println(hasPassword.get());
    }
 
    //Close the stream and it's underlying file as well
    lines.close();
}

2. FileReader 按行读取文件

直到java7,可采用下面的方式来读取文件,实现方法有很多,但不是本文的重点,只是与上面的例子对比来看

private static void readLinesUsingFileReader() throws IOException
{
    File file = new File("c:/temp/data.txt");
 
    FileReader fr = new FileReader(file);
    BufferedReader br = new BufferedReader(fr);
 
    String line;
    while((line = br.readLine()) != null)
    {
        if(line.contains("password")){
            System.out.println(line);
        }
    }
    br.close();
    fr.close();
}

3. try-with-resources模式自动关闭流

第一个例子已经能够满足我们在应用程序中逐行读取文件的需要了,但如果你还想让它变得更好,那么我们可以使用 try-with-resources 的方式,这样会省去最后关闭流 close() 的操作。

private static void readStreamOfLinesUsingFilesWithTryBlock() throws IOException
{
    Path path = Paths.get("c:/temp", "data.txt");
 
    //The stream hence file will also be closed here
    try(Stream<String> lines = Files.lines(path))
    {
        Optional<String> hasPassword = lines.filter(s -> s.contains("password")).findFirst();
 
        if(hasPassword.isPresent()){
            System.out.println(hasPassword.get());
        }
    }
}

当一个Stream产生另外一个Stream的时候,close 方法会链式调用(它下面的Stream也会调用),可以采用下面的方法编写:

private static void readStreamOfLinesUsingFilesWithTryBlock() throws IOException
{
    Path path = Paths.get("c:/temp", "data.txt");
 
    //When filteredLines is closed, it closes underlying stream as well as underlying file.
    try(Stream<String> filteredLines = Files.lines(path).filter(s -> s.contains("password")))
    {
        Optional<String> hasPassword = filteredLines.findFirst();
 
        if(hasPassword.isPresent()){
            System.out.println(hasPassword.get());
        }
    }
}

你想测试一下它下面的Stream的有没有触发 close ,可以用 onClose 来测试一下

private static void readStreamOfLinesUsingFilesWithTryBlock() throws IOException
{
    Path path = Paths.get("c:/temp", "data.txt");
    //When filteredLines is closed, it closes underlying stream as well as underlying file.
    try(Stream<String> filteredLines = Files.lines(path)
                                    //test if file is closed or not
                                    .onClose(() -> System.out.println("File closed"))
                                    .filter(s -> s.contains("password"))){
        Optional<String> hasPassword = filteredLines.findFirst();
        if(hasPassword.isPresent()){
            System.out.println(hasPassword.get());
        }
    }
}

输出

password
File closed

二、写入文件

参考链接:https://howtodoinjava.com/java8/java-8-write-to-file-example/

1. Java 8使用BufferedWriter写入文件

BufferedWriter 用于将文本写入字符或字节流。在打印字符之前,它将字符存储在缓冲区中,并批量打印。如果没有缓冲,每次调用 print () 方法都会导致字符转换为字节,然后立即写入文件,效率会很低。

//Get the file reference
Path path = Paths.get("c:/output.txt");
 
//Use try-with-resource to get auto-closeable writer instance
try (BufferedWriter writer = Files.newBufferedWriter(path))
{
    writer.write("Hello World !!");
}

2. 使用Files.write()写入文件

使用 Files.write() 也很漂亮简洁.

String content = "Hello World !!";
 
Files.write(Paths.get("c:/output.txt"), content.getBytes());

三、遍历迭代目录

参考链接:https://howtodoinjava.com/java8/java-8-list-all-files-example/

学习使用Java 8 API,例如Files.list()DirectoryStream,并递归列出目录中存在的所有文件,包括隐藏文件。

对于使用外部迭代(用于循环),请使用DirectoryStream。要使用Stream API操作(映射,过滤,排序,收集),请Files.list()改用。

1. Files.list()–遍历所有文件和子目录

Files.list() 方法 遍历当前目录下所有的文件和子目录

Files.list(Paths.get("."))
        .forEach(System.out::println);
// 或者
List<File> files = Files.list(Paths.get(dirLocation))
                        .map(Path::toFile)
                        .collect(Collectors.toList());
files.forEach(System.out::println);
 Output:
 
.\filename1.txt
.\directory1
.\filename2.txt
.\Employee.java

2. Files.list()–仅列出不包括子目录的文件

使用过滤器表达式 Files::isRegularFile 检查文件是否是普通文件,来过滤掉子目录,保留文件并打印

Files.list(Paths.get("."))
        .filter(Files::isRegularFile)
        .forEach(System.out::println);
// 或者
List<File> files = Files.list(Paths.get("."))
                .filter(Files::isRegularFile)
                .map(Path::toFile)
                .collect(Collectors.toList());
files.forEach(System.out::println);
Output:

.\filename1.txt
.\filename2.txt
.\Employee.java

3. Files.newDirectoryStream()–列出所有文件和子目录

Java提供了一个更加灵活遍历目录内容的方式:Files.newDirectoryStream()

注意,如果对一个大的目录进行操作,使用 DirectoryStream 将提高代码的运行速度

Files.newDirectoryStream(Paths.get("."))
        .forEach(System.out::println);
Output:
.\filename1.txt
.\directory1
.\filename2.txt
.\Employee.java

4. Files.newDirectoryStream()–仅迭代不包含子目录的文件

只遍历文件,排除掉目录,通过path filter的第二个参数来控制

Files.newDirectoryStream(Paths.get("."), path -> path.toFile().isFile())
        .forEach(System.out::println);
Output:

.\filename1.txt
.\filename2.txt
.\Employee.java

5. 仅列出一定范围的所有文件

要仅获取某些扩展名的所有文件的列表,请同时使用两个谓词 Files::isRegularFilepath.toString().endsWith(".java")

使用上述谓词,我们将列出.java文件夹中的所有文件。

Files.newDirectoryStream(Paths.get("."),
        path -> path.toString().endsWith(".java"))
        .forEach(System.out::println);
// 或者
List<File> files = Files.list(Paths.get(dirLocation))
                                    .filter(Files::isRegularFile)
                                    .filter(path -> path.toString().endsWith(".java"))
                                    .map(Path::toFile)
                                    .collect(Collectors.toList());
files.forEach(System.out::println);
Output:

.\Employee.java

6. 查找目录中的所有隐藏文件

要查找所有隐藏文件,可以file -> file.isHidden()在上述任何示例中使用过滤器表达式

final File[] files = new File(".").listFiles(file -> file.isHidden());
// 或者 方法引用写法
final File[] files = new File(".").listFiles(File::isHidden);
// 或者 使用Files工具类
List<File> files = Files.list(Paths.get("."))
            .filter(path -> path.toFile().isHidden())
            .map(Path::toFile)
            .collect(Collectors.toList());

四、监视目录,子目录和文件中的更改

参考链接:https://howtodoinjava.com/java8/java-8-watchservice-api-tutorial/

在此将使用 Java 7 WatchServiceAPI观察目录及其中的所有子目录和文件。WatchService 是jdk1.7版本引进的,位于 java.nio.file 包下。WatchService 是基于本机操作系统实现对文件的监控。

1. 如何注册WatchService

要注册WatchService,请获取目录路径并使用path.register()方法。

Path path = Paths.get(".");
WatchService watchService =  path.getFileSystem().newWatchService();
path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);

2. 观察变化事件

要获取目录及其中文件的更改,请使用watchKey.pollEvents()方法,该方法以流的形式返回所有更改事件的集合。

WatchKey watchKey = null;
while (true) {
    watchKey = watchService.poll(10, TimeUnit.MINUTES);
    if(watchKey != null) {
        watchKey.pollEvents().stream().forEach(event -> System.out.println(event.context()));
    }
    watchKey.reset();
}

该密钥一直有效,直到:

  • 通过调用它的cancel方法显式地取消它,或者
  • 隐式取消,因为该对象不再可访问,或者
  • 通过关闭手表服务。

如果要在循环中多次重复使用同一键来获取更改事件,请不要忘记调用 watchKey.reset() 将键重新设置为就绪状态的方法。

请注意,诸如如何检测事件,其及时性以及是否保留其顺序之类的几件事高度依赖于底层操作系统。某些更改可能导致一个操作系统中的单个条目,而类似的更改可能导致另一操作系统中的多个事件。

3. 监视目录,子目录和文件中的更改示例

在此示例中,我们将看到一个观看目录的示例,该目录中包含所有子目录和文件。我们将维护监视键和目录的映射,Map<WatchKey, Path> keys以正确识别已修改的目录。

下面的方法将向观察者注册一个path,然后将pathkey存储在map中。

private void registerDirectory(Path dir) throws IOException 
{
    WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
    keys.put(key, dir);
}

在遍历目录结构并为遇到的每个目录调用此方法时,将递归调用此方法。

private void walkAndRegisterDirectories(final Path start) throws IOException {
    // register directory and sub-directories
    Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
        @Override
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
            registerDirectory(dir);
            return FileVisitResult.CONTINUE;
        }
    });
}

请注意,无论何时创建新目录,我们都会在 watchservice 中注册该目录,并将新key添加到map中。

WatchEvent.Kind kind = event.kind();
if (kind == ENTRY_CREATE) {
    try {
        if (Files.isDirectory(child)) {
            walkAndRegisterDirectories(child);
        }
    } catch (IOException x) {
        // do something useful
    }
}

将以上所有内容与处理事件的逻辑放在一起,完整的示例如下所示:

import static java.nio.file.StandardWatchEventKinds.*;
 
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.Map;
 
public class Java8WatchServiceExample {
 
    private final WatchService watcher;
    private final Map<WatchKey, Path> keys;
 
    /**
     * Creates a WatchService and registers the given directory
     */
    Java8WatchServiceExample(Path dir) throws IOException {
        this.watcher = FileSystems.getDefault().newWatchService();
        this.keys = new HashMap<WatchKey, Path>();
 
        walkAndRegisterDirectories(dir);
    }
 
    /**
     * Register the given directory with the WatchService; This function will be called by FileVisitor
     */
    private void registerDirectory(Path dir) throws IOException 
    {
        WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
        keys.put(key, dir);
    }
 
    /**
     * Register the given directory, and all its sub-directories, with the WatchService.
     */
    private void walkAndRegisterDirectories(final Path start) throws IOException {
        // register directory and sub-directories
        Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                registerDirectory(dir);
                return FileVisitResult.CONTINUE;
            }
        });
    }
 
    /**
     * Process all events for keys queued to the watcher
     */
    void processEvents() {
        for (;;) {
 
            // wait for key to be signalled
            WatchKey key;
            try {
                key = watcher.take();
            } catch (InterruptedException x) {
                return;
            }
 
            Path dir = keys.get(key);
            if (dir == null) {
                System.err.println("WatchKey not recognized!!");
                continue;
            }
 
            for (WatchEvent<?> event : key.pollEvents()) {
                @SuppressWarnings("rawtypes")
                WatchEvent.Kind kind = event.kind();
 
                // Context for directory entry event is the file name of entry
                @SuppressWarnings("unchecked")
                Path name = ((WatchEvent<Path>)event).context();
                Path child = dir.resolve(name);
 
                // print out event
                System.out.format("%s: %s\n", event.kind().name(), child);
 
                // if directory is created, and watching recursively, then register it and its sub-directories
                if (kind == ENTRY_CREATE) {
                    try {
                        if (Files.isDirectory(child)) {
                            walkAndRegisterDirectories(child);
                        }
                    } catch (IOException x) {
                        // do something useful
                    }
                }
            }
 
            // reset key and remove from set if directory no longer accessible
            boolean valid = key.reset();
            if (!valid) {
                keys.remove(key);
 
                // all directories are inaccessible
                if (keys.isEmpty()) {
                    break;
                }
            }
        }
    }
 
    public static void main(String[] args) throws IOException {
        Path dir = Paths.get("c:/temp");
        new Java8WatchServiceExample(dir).processEvents();
    }
}

运行该程序,并在给定输入目录中,新增更改文件或者目录后,您将在控制台中注意到捕获的事件。

输出:

ENTRY_CREATE:c:\ temp \ New文件夹
ENTRY_DELETE:c:\ temp \ New文件夹
ENTRY_CREATE:c:\ temp \ data
ENTRY_CREATE:c:\ temp \ data \ New Text Document.txt
ENTRY_MODIFY:c:\ temp \ data
ENTRY_DELETE:c:\ temp \ data \ New Text Document.txt
ENTRY_CREATE:c:\ temp \ data \ tempFile.txt
ENTRY_MODIFY:c:\ temp \ data
ENTRY_MODIFY:c:\ temp \ data \ tempFile.txt
ENTRY_MODIFY:c:\ temp \ data \ tempFile.txt
ENTRY_MODIFY:c:\ temp \ data \ tempFile.txt

WatchService 需要牢记两件事:

  1. WatchService 不会为监视目录的子目录拾取事件。
  2. 我们仍然需要轮询 WatchService 的事件,而不是接收异步通知。
  3. WatchService能立即监控到文件的改动。 如果事件是创建,删除或更新,并且该事件相对于监视目录,则 WatchEvent.context 方法将返回Path对象。 重要的是要知道,当收到事件时,不能保证执行该操作的程序已经完成,因此可能需要一定程度的协调,比如判断文件操作成功。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值