Java 编程问题:六、Java I/O 路径、文件、缓冲区、扫描和格式化5

简介: Java 编程问题:六、Java I/O 路径、文件、缓冲区、扫描和格式化

创建临时文件夹/文件


使用Path createTempDirectory(Path dir, String prefix, FileAttribute<?>... attrs)可以创建临时文件夹。这是Files类中的static方法,可以按如下方式使用:


  • 让我们在操作系统的默认位置创建一个没有前缀的临时文件夹:
// C:\Users\Anghel\AppData\Local\Temp\8083202661590940905
Path tmpNoPrefix = Files.createTempDirectory(null);


让我们在操作系统的默认位置创建一个带有自定义前缀的临时文件夹:

// C:\Users\Anghel\AppData\Local\Temp\logs_5825861687219258744
String customDirPrefix = "logs_";
Path tmpCustomPrefix 
  = Files.createTempDirectory(customDirPrefix);

让我们在自定义位置创建一个带有自定义前缀的临时文件夹:

// D:\tmp\logs_10153083118282372419
Path customBaseDir 
  = FileSystems.getDefault().getPath("D:/tmp");
String customDirPrefix = "logs_";
Path tmpCustomLocationAndPrefix 
  = Files.createTempDirectory(customBaseDir, customDirPrefix);


创建临时文件可以通过Path createTempFile(Path dir, String prefix, String suffix, FileAttribute<?>... attrs)完成。这是Files类中的static方法,可以按如下方式使用:


   让我们在操作系统的默认位置创建一个没有前缀和后缀的临时文件:

// C:\Users\Anghel\AppData\Local\Temp\16106384687161465188.tmp
Path tmpNoPrefixSuffix = Files.createTempFile(null, null);


让我们在操作系统的默认位置创建一个带有自定义前缀和后缀的临时文件:

// C:\Users\Anghel\AppData\Local\Temp\log_402507375350226.txt
String customFilePrefix = "log_";
String customFileSuffix = ".txt";
Path tmpCustomPrefixAndSuffix 
  = Files.createTempFile(customFilePrefix, customFileSuffix);


让我们在自定义位置创建一个带有自定义前缀和后缀的临时文件:

// D:\tmp\log_13299365648984256372.txt
Path customBaseDir 
  = FileSystems.getDefault().getPath("D:/tmp");
String customFilePrefix = "log_";
String customFileSuffix = ".txt";
Path tmpCustomLocationPrefixSuffix = Files.createTempFile(
  customBaseDir, customFilePrefix, customFileSuffix);

在下面的部分中,我们将研究删除临时文件夹/文件的不同方法。



通过关闭挂钩删除临时文件夹/文件


删除临时文件夹/文件是一项可以由操作系统或专用工具完成的任务。然而,有时,我们需要通过编程来控制这一点,并基于不同的设计考虑删除一个文件夹/文件。


这个问题的解决依赖于关闭挂钩机制,可以通过Runtime.getRuntime().addShutdownHook()方法来实现。当我们需要在 JVM 关闭之前完成某些任务(例如,清理任务)时,这种机制非常有用。它被实现为一个 Java 线程,当 JVM 在关闭时执行关闭挂钩时调用其run()方法。如下代码所示:


Path customBaseDir = FileSystems.getDefault().getPath("D:/tmp");
String customDirPrefix = "logs_";
String customFilePrefix = "log_";
String customFileSuffix = ".txt";
try {
  Path tmpDir = Files.createTempDirectory(
    customBaseDir, customDirPrefix);
  Path tmpFile1 = Files.createTempFile(
    tmpDir, customFilePrefix, customFileSuffix);
  Path tmpFile2 = Files.createTempFile(
    tmpDir, customFilePrefix, customFileSuffix);
  Runtime.getRuntime().addShutdownHook(new Thread() {
    @Override
    public void run() {
      try (DirectoryStream<Path> ds 
          = Files.newDirectoryStream(tmpDir)) {
        for (Path file: ds) {
          Files.delete(file);
        }
        Files.delete(tmpDir);
      } catch (IOException e) {
        ...
      }
    }
  });
  //simulate some operations with temp file until delete it
  Thread.sleep(10000);
} catch (IOException | InterruptedException e) {
  ...
}

在异常/强制终止的情况下(例如 JVM 崩溃、触发终端操作等),将不执行关闭挂钩。当所有线程完成或调用System.exit(0)时,它运行。建议快速运行,因为如果出现问题(例如,操作系统关闭),可以在完成之前强制停止它们。通过编程,关闭挂钩只能由Runtime.halt()停止。




通过deleteOnExit()删除临时文件夹/文件

另一个删除临时文件夹/文件的解决方案依赖于File.deleteOnExit()方法。通过调用此方法,我们可以注册删除文件夹/文件。删除操作在 JVM 关闭时发生:

Path customBaseDir = FileSystems.getDefault().getPath("D:/tmp");
String customDirPrefix = "logs_";
String customFilePrefix = "log_";
String customFileSuffix = ".txt";
try {
  Path tmpDir = Files.createTempDirectory(
    customBaseDir, customDirPrefix);
  System.out.println("Created temp folder as: " + tmpDir);
  Path tmpFile1 = Files.createTempFile(
    tmpDir, customFilePrefix, customFileSuffix);
  Path tmpFile2 = Files.createTempFile(
    tmpDir, customFilePrefix, customFileSuffix);
  try (DirectoryStream<Path> ds = Files.newDirectoryStream(tmpDir)) {
    tmpDir.toFile().deleteOnExit();
    for (Path file: ds) {
      file.toFile().deleteOnExit();
    }
  } catch (IOException e) {
    ...
  }
  // simulate some operations with temp file until delete it
  Thread.sleep(10000);
} catch (IOException | InterruptedException e) {
  ...
}

当应用管理少量临时文件夹/文件时,建议仅依赖此方法(deleteOnExit())。此方法可能会消耗大量内存(它会为每个注册删除的临时资源消耗内存),并且在 JVM 终止之前,此内存可能不会被释放。请注意,因为需要调用此方法才能注册每个临时资源,而删除的顺序与注册的顺序相反(例如,我们必须先注册临时文件夹,然后再注册其内容)。



通过DELETE_ON_CLOSE删除临时文件


当涉及到删除临时文件时,另一个解决方案依赖于StandardOpenOption.DELETE_ON_CLOSE(这会在流关闭时删除文件)。例如,下面的代码通过createTempFile()方法创建一个临时文件,并为其打开一个缓冲的写入器流,其中明确指定了DELETE_ON_CLOSE:


Path customBaseDir = FileSystems.getDefault().getPath("D:/tmp");
String customFilePrefix = "log_";
String customFileSuffix = ".txt";
Path tmpFile = null;
try {
  tmpFile = Files.createTempFile(
    customBaseDir, customFilePrefix, customFileSuffix);
} catch (IOException e) {
  ...
}
try (BufferedWriter bw = Files.newBufferedWriter(tmpFile,
    StandardCharsets.UTF_8, StandardOpenOption.DELETE_ON_CLOSE)) {
  //simulate some operations with temp file until delete it
  Thread.sleep(10000);
} catch (IOException | InterruptedException e) {
  ...
}


此解决方案可用于任何文件。它不是针对临时资源的。



143 过滤文件

Path中过滤文件是一项非常常见的任务。例如,我们可能只需要特定类型的文件、具有特定名称模式的文件、今天修改的文件等等。



通过Files.newDirectoryStream()过滤


不需要任何类型的过滤器,我们可以通过Files.newDirectoryStream(Path dir)方法轻松地循环文件夹的内容(一层深)。此方法返回一个DirectoryStream<Path>,这是一个对象,我们可以使用它来迭代目录中的条目:

Path path = Paths.get("D:/learning/books/spring");
try (DirectoryStream<Path> ds = Files.newDirectoryStream(path)) {
  for (Path file: ds) {
    System.out.println(file.getFileName());
  }
}


如果我们想用过滤器丰富这段代码,那么我们至少有两种解决方案。一种解决方案依赖于另一种口味的newDirectoryStream()方法newDirectoryStream(Path dir, String glob)。除了Path之外,该方法还使用 glob 语法接收一个过滤器。例如,我们可以在D:/learning/books/spring文件夹中过滤 PNG、JPG 和 BMP 类型的文件:


try (DirectoryStream<Path> ds =
    Files.newDirectoryStream(path, "*.{png,jpg,bmp}")) {
  for (Path file: ds) {
    System.out.println(file.getFileName());
  }
}

当 glob 语法不能再帮助我们时,是时候使用另一种风格的newDirectoryStream()来获得Filter,即newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter)。首先,让我们为大于 10 MB 的文件定义一个过滤器:


DirectoryStream.Filter<Path> sizeFilter 
    = new DirectoryStream.Filter<>() {
  @Override
  public boolean accept(Path path) throws IOException {
    return (Files.size(path) > 1024 * 1024 * 10);
  }
};


我们也可以在函数式风格上做到这一点:

DirectoryStream.Filter<Path> sizeFilter 
  = p -> (Files.size(p) > 1024 * 1024 * 10);


现在,我们可以这样应用这个过滤器:

try (DirectoryStream<Path> ds =
    Files.newDirectoryStream(path, sizeFilter)) {
  for (Path file: ds) {
    System.out.println(file.getFileName() + " " +
      Files.readAttributes(file, BasicFileAttributes.class).size() 
        + " bytes");
  }
}


让我们再看几个可以用于此技术的过滤器:

  • 以下是文件夹的用户定义过滤器:
DirectoryStream.Filter<Path> folderFilter 
    = new DirectoryStream.Filter<>() {
  @Override
  public boolean accept(Path path) throws IOException {
    return (Files.isDirectory(path, NOFOLLOW_LINKS));
  }
};


以下是今天修改的文件的用户定义过滤器:

DirectoryStream.Filter<Path> todayFilter 
    = new DirectoryStream.Filter<>() {
  @Override
  public boolean accept(Path path) throws IOException {
    FileTime lastModified = Files.readAttributes(path,
      BasicFileAttributes.class).lastModifiedTime();
    LocalDate lastModifiedDate = lastModified.toInstant()
      .atOffset(ZoneOffset.UTC).toLocalDate();
    LocalDate todayDate = Instant.now()
      .atOffset(ZoneOffset.UTC).toLocalDate();
    return lastModifiedDate.equals(todayDate);
  }
};


以下是隐藏文件/文件夹的用户定义过滤器:

DirectoryStream.Filter<Path> hiddenFilter 
    = new DirectoryStream.Filter<>() {
  @Override
  public boolean accept(Path path) throws IOException {
    return (Files.isHidden(path));
  }
};

在下面的部分中,我们将研究过滤文件的不同方法。



144 发现两个文件之间的不匹配


此问题的解决方案是比较两个文件的内容(逐字节比较),直到发现第一个不匹配或达到 EOF。

让我们考虑以下四个文本文件:


只有前两个文件(file1.txt和file2.txt相同。任何其他比较都应显示至少存在一个不匹配。


一种解决方法是使用MappedByteBuffer。这个解决方案是超级快速和易于实现。我们只需打开两个FileChannels(每个文件一个),然后逐字节进行比较,直到找到第一个不匹配或 EOF。如果文件的字节长度不同,则我们假设文件不相同,并立即返回:

private static final int MAP_SIZE = 5242880; // 5 MB in bytes
public static boolean haveMismatches(Path p1, Path p2) 
    throws IOException {
  try (FileChannel channel1 = (FileChannel.open(p1,
      EnumSet.of(StandardOpenOption.READ)))) {
    try (FileChannel channel2 = (FileChannel.open(p2,
        EnumSet.of(StandardOpenOption.READ)))) {
      long length1 = channel1.size();
      long length2 = channel2.size();
      if (length1 != length2) {
        return true;
      }
      int position = 0;
      while (position < length1) {
        long remaining = length1 - position;
        int bytestomap = (int) Math.min(MAP_SIZE, remaining);
        MappedByteBuffer mbBuffer1 = channel1.map(
          MapMode.READ_ONLY, position, bytestomap);
        MappedByteBuffer mbBuffer2 = channel2.map(
          MapMode.READ_ONLY, position, bytestomap);
        while (mbBuffer1.hasRemaining()) {
          if (mbBuffer1.get() != mbBuffer2.get()) {
            return true;
          }
        }
        position += bytestomap;
      }
    }
  }
  return false;
}


JDK-13 准备发布非易失性MappedByteBuffers。敬请期待!

从 JDK12 开始,Files类通过一种新方法得到了丰富,该方法专门用于指出两个文件之间的不匹配。此方法具有以下签名:


public static long mismatch(Path path, Path path2) throws IOException


此方法查找并返回两个文件内容中第一个不匹配字节的位置。如果没有错配,则返回-1

long mismatches12 = Files.mismatch(file1, file2); // -1
long mismatches13 = Files.mismatch(file1, file3); // 51
long mismatches14 = Files.mismatch(file1, file4); // 60



通过FilenameFilter过滤


FilenameFilter函数式接口也可以用来过滤文件夹中的文件。首先,我们需要定义一个过滤器(例如,下面是 PDF 类型文件的过滤器):

String[] files = path.toFile().list(new FilenameFilter() {
  @Override
  public boolean accept(File folder, String fileName) {
    return fileName.endsWith(".pdf");
  }
});


我们可以在函数式风格上做同样的事情:

FilenameFilter filter = (File folder, String fileName) 
  -> fileName.endsWith(".pdf");


让我们更简洁一点:

FilenameFilter filter = (f, n) -> n.endsWith(".pdf");


为了使用这个过滤器,我们需要将它传递给过载的File.list(FilenameFilter filter)或

File.listFiles(FilenameFilter filter)方法:
String[] files = path.toFile().list(filter);


文件数组将只包含 PDF 文件的名称。

为了将结果取为File[],我们应该调用listFiles()而不是list()

相关文章
|
8天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
10天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
39 2
|
14天前
|
JSON Java Apache
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
|
7天前
|
Java 开发者
Java多线程编程的艺术与实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的技术文档,本文以实战为导向,通过生动的实例和详尽的代码解析,引领读者领略多线程编程的魅力,掌握其在提升应用性能、优化资源利用方面的关键作用。无论你是Java初学者还是有一定经验的开发者,本文都将为你打开多线程编程的新视角。 ####
|
6天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
9天前
|
安全 Java 开发者
Java多线程编程中的常见问题与解决方案
本文深入探讨了Java多线程编程中常见的问题,包括线程安全问题、死锁、竞态条件等,并提供了相应的解决策略。文章首先介绍了多线程的基础知识,随后详细分析了每个问题的产生原因和典型场景,最后提出了实用的解决方案,旨在帮助开发者提高多线程程序的稳定性和性能。
|
12天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
21天前
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
32 1
|
27天前
|
Java 数据处理 开发者
Java多线程编程的艺术:从入门到精通####
【10月更文挑战第21天】 本文将深入探讨Java多线程编程的核心概念,通过生动实例和实用技巧,引导读者从基础认知迈向高效并发编程的殿堂。我们将一起揭开线程管理的神秘面纱,掌握同步机制的精髓,并学习如何在实际项目中灵活运用这些知识,以提升应用性能与响应速度。 ####
44 3
|
29天前
|
Java
Java中的多线程编程:从入门到精通
本文将带你深入了解Java中的多线程编程。我们将从基础概念开始,逐步深入探讨线程的创建、启动、同步和通信等关键知识点。通过阅读本文,你将能够掌握Java多线程编程的基本技能,为进一步学习和应用打下坚实的基础。
下一篇
无影云桌面