创建临时文件夹/文件
使用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()
。