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()

相关文章
|
3天前
|
IDE Java 编译器
Java:如何确定编译和运行时类路径是否一致
类路径(Classpath)是JVM用于查找类文件的路径列表,对编译和运行Java程序至关重要。编译时通过`javac -classpath`指定,运行时通过`java -classpath`指定。IDE如Eclipse和IntelliJ IDEA也提供界面管理类路径。确保编译和运行时类路径一致,特别是外部库和项目内部类的路径设置。
|
4天前
|
缓存 Java UED
Java中的多线程编程:从基础到实践
【10月更文挑战第13天】 Java作为一门跨平台的编程语言,其强大的多线程能力一直是其核心优势之一。本文将从最基础的概念讲起,逐步深入探讨Java多线程的实现方式及其应用场景,通过实例讲解帮助读者更好地理解和应用这一技术。
22 3
|
4天前
|
Java 开发者
在Java编程中,正确的命名规范不仅能提升代码的可读性和可维护性,还能有效避免命名冲突。
【10月更文挑战第13天】在Java编程中,正确的命名规范不仅能提升代码的可读性和可维护性,还能有效避免命名冲突。本文将带你深入了解Java命名规则,包括标识符的基本规则、变量和方法的命名方式、常量的命名习惯以及如何避免关键字冲突,通过实例解析,助你写出更规范、优雅的代码。
26 3
|
4天前
|
Java 程序员
在Java编程中,关键字不仅是简单的词汇,更是赋予代码强大功能的“魔法咒语”。
【10月更文挑战第13天】在Java编程中,关键字不仅是简单的词汇,更是赋予代码强大功能的“魔法咒语”。本文介绍了Java关键字的基本概念及其重要性,并通过定义类和对象、控制流程、访问修饰符等示例,展示了关键字的实际应用。掌握这些关键字,是成为优秀Java程序员的基础。
12 3
|
4天前
|
Java 程序员 编译器
在Java编程中,保留字(如class、int、for等)是具有特定语法意义的预定义词汇,被语言本身占用,不能用作变量名、方法名或类名。
在Java编程中,保留字(如class、int、for等)是具有特定语法意义的预定义词汇,被语言本身占用,不能用作变量名、方法名或类名。本文通过示例详细解析了保留字的定义、作用及与自定义标识符的区别,帮助开发者避免因误用保留字而导致的编译错误,确保代码的正确性和可读性。
16 3
|
4天前
|
Java
Java开发如何实现文件的移动,但是在移动结束后才进行读取?
【10月更文挑战第13天】Java开发如何实现文件的移动,但是在移动结束后才进行读取?
15 2
|
3天前
|
存储 安全 Java
了解final关键字在Java并发编程领域的作用吗?
在Java并发编程中,`final`关键字不仅用于修饰变量、方法和类,还在多线程环境中确保对象状态的可见性和不变性。本文深入探讨了`final`关键字的作用,特别是其在final域重排序规则中的应用,以及如何防止对象的“部分创建”问题,确保线程安全。通过具体示例,文章详细解析了final域的写入和读取操作的重排序规则,以及这些规则在不同处理器上的实现差异。
了解final关键字在Java并发编程领域的作用吗?
|
4天前
|
Java Apache Maven
Java将word文档转换成pdf文件的方法?
【10月更文挑战第13天】Java将word文档转换成pdf文件的方法?
15 1
|
4天前
|
监控 Java
Java定时扫码一个文件夹下的文件,如何保证文件写入完成后才进行处理?
【10月更文挑战第13天】Java定时扫码一个文件夹下的文件,如何保证文件写入完成后才进行处理?
17 1