您可以使用本指南了解如何通过 Path API 使用 Java 中的文件。从读取和写入文件,到查看目录和使用内存文件系统。
Java 的文件 API
Java 有两个文件 API。
-
原始
java.io.File
API,自 Java 1.0 (1996) 起可用。 -
较新的
java.nio.file.Path
API,自 Java 1.7 (2011) 起可用。
文件 API 和路径 API 之间有什么区别?
旧文件 API 用于大量旧项目、框架和库。尽管它很老,但它并没有被弃用(并且可能永远不会被弃用)并且您仍然可以将它与任何最新的 Java 版本一起使用。
尽管如此,java.nio.file.Path
一切都java.io.File
可以,但通常以更好的方式和更多。几个例子:
-
文件特性:新类支持符号链接、正确的文件属性和元数据支持(想想:PosixFileAttributes)、ACL 等。
-
更好的用法:例如,在删除文件时,您会收到带有有意义的错误消息(没有此类文件、文件已锁定等)的异常,而不是简单的布尔值
false
。 -
解耦:启用对内存文件系统的支持,我们将在后面介绍。
(有关两个 API 之间差异的完整列表,请查看这篇文章:https : //www.oracle.com/technical-resources/articles/javase/nio.html)
我应该使用哪个文件 API?
由于上述原因,如果您正在启动一个新的 Java 项目,强烈建议使用Paths
API 而不是File
API。(即使file读取比path好得多,不是吗?)
因此,我们将在本文中仅关注Paths
API。
路径 API
要在 Java 中处理文件,您首先需要对文件的引用(大惊喜!)。正如我们刚刚提到的,从 Java 7 开始,您将使用 Paths API 来引用文件,所以这一切都从构造Path
对象开始。
让我们看看一些代码。
public static void main(String[] args) throws URISyntaxException {
// Java11+ : Path.of()
Path path = Path.of("c:\\dev\\licenses\\windows\\readme.txt");
System.out.println(path);
path = Path.of("c:/dev/licenses/windows/readme.txt");
System.out.println(path);
path = Path.of("c:" , "dev", "licenses", "windows", "readme.txt");
System.out.println(path);
path = Path.of("c:" , "dev", "licenses", "windows").resolve("readme.txt"); // resolve == getChild()
System.out.println(path);
path = Path.of(new URI("file:///c:/dev/licenses/windows/readme.txt"));
System.out.println(path);
// Java < 11 equivalent: Paths.get()
path = Paths.get("c:/dev/licenses/windows/readme.txt");
System.out.println(path);
// etc...
}
让我们分解一下:
Path path = Path.of("c:\\dev\\licenses\\windows\\readme.txt");
System.out.println(path);
path = Path.of("c:/dev/licenses/windows/readme.txt");
System.out.println(path);
从 Java 11 开始,您应该使用静态Path.of
方法来构造路径(我们将在稍后介绍 Java7-10 等价物)。
如果您在 Windows 上使用正斜杠并不重要,因为 Path API 足够智能,可以构建正确的路径,独立于操作系统和任何正反斜杠问题。
因此,在运行 main 方法时,上面的两行都将返回以下结果。
c:\dev\licenses\windows\readme.txt
c:\dev\licenses\windows\readme.txt
在构建路径时,您有更多选择:您不必将完整路径指定为一个长字符串:
path = Path.of("c:" , "dev", "licenses", "windows", "readme.txt");
System.out.println(path);
path = Path.of("c:" , "dev", "licenses", "windows").resolve("readme.txt"); // resolve == getChild()
System.out.println(path);
相反,您可以将一系列字符串传递给该Path.of
方法,或者构造父目录并使用它来获取子文件 ( .resolve(child)
)。
同样,输出将与以前相同。
c:\dev\licenses\windows\readme.txt
c:\dev\licenses\windows\readme.txt
最后但并非最不重要的一点是,您还可以将 URI 传递到Path.of
调用中。
path = Path.of(new URI("file:///c:/dev/licenses/windows/readme.txt"));
System.out.println(path);
这听起来像是一个破纪录,但输出....将是相同的。
c:\dev\licenses\windows\readme.txt
因此,您有多种选择来构建 Path 对象。
不过有两点很重要:
-
构造路径对象或解析子对象并不意味着文件或目录实际存在。路径只是对潜在文件的引用。因此,您必须单独验证它的存在。
-
在 Java-11 之前,
Path.of
被称为Paths.get
,如果您坚持使用较旧的 Java 版本或构建需要一些向后兼容性的库,则需要使用它。从 Java 11 开始,Paths.get
内部重定向到Path.of
.// Java < 11 equivalent: Paths.get() path = Paths.get("c:/dev/licenses/windows/readme.txt"); System.out.println(path);
一旦你有了一个路径对象,你终于可以用它做一些事情了。让我们看看下一节中的内容和方式。
常见文件操作
处理文件或路径时,您可能会使用java.nio.file.Files
该类。它包含大量常见且有用的静态方法,可对文件和目录进行操作。
将此部分用作快速备忘单,标题不言自明。
如何检查文件是否存在
Path path = Path.of("c:\\dev\\licenses\\windows\\readme.txt");
boolean exists = Files.exists(path);
System.out.println("exists = " + exists);
检查文件或目录是否存在。还允许您指定其他参数,以定义符号链接的处理方式,即是否遵循(默认)。
运行此代码段时,您将返回一个简单的布尔标志。
exists = true
如何获取文件的最后修改日期
Path path = Path.of("c:\\dev\\licenses\\windows\\readme.txt");
FileTime lastModifiedTime = Files.getLastModifiedTime(path);
System.out.println("lastModifiedTime = " + lastModifiedTime);
不言自明。返回文件作为FileTime
对象的最后修改日期。
lastModifiedTime = 2020-05-20T08:41:30.905176Z
如何比较文件 (Java12+)
Path path = Path.of("c:\\dev\\licenses\\windows\\readme.txt");
long mismatchIndex = Files.mismatch(path, Paths.get("c:\\dev\\whatever.txt"));
System.out.println("mismatch = " + mismatchIndex);
这是 Java 的一个相对较新的补充,自 Java 12 起可用。它比较两个文件的大小和字节,并返回第一个(字节)不匹配的位置。或者,如果没有不匹配,则为 -1L。
因此,如果您正在比较两个完全不同的文件,您将得到它作为控制台输出:第一个字节已经不匹配,因此不匹配是位置零。
mismatch = 0
如何获取文件的所有者
Path path = Path.of("c:\\dev\\licenses\\windows\\readme.txt");
UserPrincipal owner = Files.getOwner(path);
System.out.println("owner = " + owner);
不言自明。返回文件或目录的所有者UserPrincipal
(从 扩展Principal
)。在 Windows 上,这将是一个 WindowsUserPrincipal,其中包含用户的帐户名(如下所示),以及他的sid
,他在您的 Windows 机器上的唯一安全标识符。
owner = DESKTOP-168M0IF\marco_local (User)
如何创建临时文件
Path tempFile1 = Files.createTempFile("somePrefixOrNull", ".jpg");
System.out.println("tempFile1 = " + tempFile1);
Path tempFile2 = Files.createTempFile(path.getParent(), "somePrefixOrNull", ".jpg");
System.out.println("tempFile2 = " + tempFile2);
Path tmpDirectory = Files.createTempDirectory("prefix");
System.out.println("tmpDirectory = " + tmpDirectory);
让我们分解一下。
Path tempFile1 = Files.createTempFile("somePrefixOrNull", ".jpg");
System.out.println("tempFile1 = " + tempFile1);
创建临时文件时,您可以指定前缀(第一个参数)和后缀(第二个参数)。两者都可以为空。
前缀将作为临时文件名的前缀(废话!),后缀本质上是文件扩展名,如果您省略它,将使用默认扩展名“.tmp”。
该文件将在默认的临时文件目录中创建。
Path tempFile2 = Files.createTempFile(path.getParent(), "somePrefixOrNull", ".jpg");
System.out.println("tempFile2 = " + tempFile2);
除了默认的临时目录,您还可以指定自己的目录,您希望在其中创建临时文件。
Path tmpDirectory = Files.createTempDirectory("prefix");
System.out.println("tmpDirectory = " + tmpDirectory);
除了文件,您还可以创建临时目录。由于您在创建目录时不需要 suffix 参数,因此您只需选择指定前缀参数。
从上面运行代码片段时,您将获得以下(或类似的)输出:
tempFile1 = C:\Users\marco\AppData\Local\Temp\somePrefixOrNull8747488053128491901.jpg
tempFile2 = c:\dev\licenses\windows\somePrefixOrNull11086918945318459411.jpg
tmpDirectory = C:\Users\marco\AppData\Local\Temp\prefix9583768274092262832
注意:与流行的看法相反,临时文件不会自行删除。在单元测试中创建它们或在生产中运行时,您必须确保明确删除它们。
如何创建文件和目录
您已经了解了如何创建临时文件,这与普通文件和目录完全相同。您只需调用不同的方法:
Path newDirectory = Files.createDirectories(path.getParent().resolve("some/new/dir"));
System.out.println("newDirectory = " + newDirectory);
Path newFile = Files.createFile(newDirectory.resolve("emptyFile.txt"));
System.out.println("newFile = " + newFile);
有些人对此感到困惑:.resolve
调用不会创建文件,它只是返回对您将要创建的(子)文件的引用。
从上面运行代码片段时,您将获得以下(或类似的)输出:
newDirectory = c:\dev\licenses\windows\some\new\dir
newFile = c:\dev\licenses\windows\some\new\dir\emptyFile.txt
如何获取文件的 Posix 权限
如果您在类 Unix 系统(包括 Linux 和 MacOS)上运行 Java 程序,您可以获得文件的 Posix 权限。想想:“-rw-rw-rw-”或“-rwxrwxrwx”等。
Path path = Path.of("c:\\dev\\licenses\\windows\\readme.txt");
try {
Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(path);
System.out.println("permissions = " + permissions);
} catch (UnsupportedOperationException e) {
System.err.println("Looks like you're not running on a posix file system");
}
在 Linux 或 MacOS 上运行它,你会得到这样的输出:
OWNER_WRITE
OWNER_READ
GROUP_WRITE
OTHERS_READ
...
写入和读取文件
如何将字符串写入文件
我们还没有谈到文件处理的核心:写入和读取文件。
让我们看看如何做到这一点:
Path utfFile = Files.createTempFile("some", ".txt");
Files.writeString(utfFile, "this is my string ää öö üü"); // UTF 8
System.out.println("utfFile = " + utfFile);
Path iso88591File = Files.createTempFile("some", ".txt");
Files.writeString(iso88591File, "this is my string ää öö üü", StandardCharsets.ISO_8859_1); // otherwise == utf8
System.out.println("iso88591File = " + iso88591File);
从 Java 11 开始(更具体地说是 11.0.2/12.0,因为在以前的版本中存在错误),您应该使用该Files.writeString
方法将字符串内容写入文件。默认情况下,它会写入一个 UTF-8 文件,但是您可以通过指定不同的编码来覆盖该文件。
如何将字节写入文件
Path anotherIso88591File = Files.createTempFile("some", ".txt");
Files.write(anotherIso88591File, "this is my string ää öö üü".getBytes(StandardCharsets.ISO_8859_1));
System.out.println("anotherIso88591File = " + anotherIso88591File);
如果您想将字节写入文件(并且在较旧的 Java 版本 < 11 中,您必须使用相同的 API 来编写字符串),您需要调用Files.write
.
写入文件时的选项
Path anotherUtf8File = Files.createTempFile("some", ".txt");
Files.writeString(anotherUtf8File, "this is my string ää öö üü", StandardCharsets.UTF_8,
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
System.out.println("anotherUtf8File = " + anotherUtf8File);
Path oneMoreUtf8File = Files.createTempFile("some", ".txt");
Files.write(oneMoreUtf8File, "this is my string ää öö üü".getBytes(StandardCharsets.UTF_8),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
System.out.println("oneMoreUtf8File = " + oneMoreUtf8File);
调用任一write
方法时,将自动创建文件(如果已存在则截断)。这意味着,我们不必像上面那样创建明确的临时文件。
如果您不希望出现这种行为(即如果文件已经存在则失败)并获得相应的异常,您需要传入另一个 OpenOption。
使用写入器和输出流
try (BufferedWriter bufferedWriter = Files.newBufferedWriter(utfFile)) {
// handle reader
}
try (OutputStream os = Files.newOutputStream(utfFile)) {
// handle outputstream
}
最后但并非最不重要的一点是,如果您想直接使用编写器或输出流,请确保调用相应的Files
方法而不是手动构造编写器或流。
如何从文件中读取字符串
读取文件与写入非常相似:
String s = Files.readString(utfFile);// UTF 8
System.out.println("s = " + s);
s = Files.readString(utfFile, StandardCharsets.ISO_8859_1); // otherwise == utf8
System.out.println("s = " + s);
在 Java11+ 上,您应该使用该Files.readString
方法从文件中读取字符串。确保传入适当的文件编码;默认情况下,Java 将使用 UTF-8 编码来读入文件。
如何从文件中读取字节
s = new String(Files.readAllBytes(utfFile), StandardCharsets.UTF_8);
System.out.println("s = " + s);
如果您想从文件中读取字节(并且在较旧的 Java 版本 < 11 中,您必须使用相同的 API 来读取字符串),您需要调用Files.readAllBytes
.
如果最终结果应该是一个字符串,那么您必须自己构建它,并使用适当的编码。
使用读取器和输入流
try (BufferedReader bufferedReader = Files.newBufferedReader(utfFile)) {
// handle reader
}
try (InputStream is = Files.newInputStream(utfFile)) {
// handle inputstream
}
与往常一样,您可以直接使用阅读器或输入流。为此,请使用相应的Files
方法。
友情提示:文件编码
我在前面的章节中多次提到它:
无论何时创建、写入或读取文件,您绝对应该使用显式编码,尽管新的 Java 11 方法默认为 UTF-8 而不是特定于平台的编码有很大帮助。
移动、删除和列出文件
在移动或删除文件时,您需要注意几件事。让我们看看一些代码:
如何移动文件
Path utfFile = Files.createTempFile("some", ".txt");
try {
Files.move(utfFile, Path.of("c:\\dev")); // this is wrong!
} catch (FileAlreadyExistsException e) {
// welp, that din't work!
}
有一种Files.move
方法,但它不会将文件移动到指定目录(您可能会期望)。
-
test.jpg → c:\temp 不起作用。
-
test.jpg → c:\temp\test.jpg 有效。
Files.move(utfFile, Path.of("c:\\dev").resolve(utfFile.getFileName().toString()));
因此,您不会将文件移动到文件夹,而是将它们“移动”到它们的全新路径,包括文件名和扩展名。
文件移动选项
Path utfFile2 = Files.createTempFile("some", ".txt");
Files.move(utfFile2, Path.of("c:\\dev").resolve(utfFile.getFileName().toString()), StandardCopyOption.REPLACE_EXISTING);
Path utfFile3 = Files.createTempFile("some", ".txt");
Files.move(utfFile3, Path.of("c:\\dev").resolve(utfFile.getFileName().toString()), StandardCopyOption.ATOMIC_MOVE);
移动文件时,您还可以根据底层文件系统的功能指定移动方式。
-
默认情况下,如果目标文件已经存在,
FileAlreadyExistsException
将抛出 a 。 -
如果指定该
StandardCopyOption.REPLACE_EXISTING
选项,目标文件将被覆盖。 -
如果指定该
StandardCopyOption.ATOMIC_MOVE
选项,则可以将文件移动到目录中,并保证任何监视目录的进程都访问完整文件而不仅仅是部分文件。
如何删除文件
删除文件和文件夹是 Java Path API 稍有不足的领域。让我们看看为什么:
try {
Files.delete(tmpDir);
} catch (DirectoryNotEmptyException e) {
e.printStackTrace();
}
有一种Files.delete
方法,它允许您删除文件和目录,但仅当目录为空时才删除。
不幸的是,没有标志可以清除非空目录,您只会得到一个DirectoryNotEmptyException
.
如何删除非空目录
有一些 3rd-party helper 库可以解决这个问题,但是如果您想使用纯 Java 版本删除非空目录树,这就是您想要做的:
try (Stream<Path> walk = Files.walk(tmpDir)) {
walk.sorted(Comparator.reverseOrder()).forEach(path -> {
try {
Files.delete(path);
} catch (IOException e) {
// something could not be deleted..
e.printStackTrace();
}
});
}
Files.walk
将从您指定的目录开始,以深度优先方式遍历文件树。该reverseOrder
比较器将确保您删除所有儿童,删除实际目录之前。
不幸的是,Files.delete
在forEach
消费者内部使用时,您还需要捕获 IOException 。删除非空目录的大量代码,不是吗?
这将我们带到了列出文件的主题:
如何列出同一目录中的文件
有多种方法可以列出给定目录中的所有文件。如果只想列出与目录相同级别的文件(不是递归更深),可以使用以下两种方法:
try (var files = Files.list(tmpDirectory)) {
files.forEach(System.out::println);
}
try (var files = Files.newDirectoryStream(tmpDirectory, "*.txt")) {
files.forEach(System.out::println);
}
请注意,newDirectoryStream
(相对于Files.list
)不会返回java.util.stream.Stream
. 相反,它返回一个DirectoryStream
,这是在 Java 8 中发布 Streams API 之前在 Java 1.7 中引入的一个类。
然而,它确实允许您指定一个glob
模式(如 *.txt),它可以完成简单列表的工作,并且可能比摸索真正的 Streams 和相应的过滤器方法更容易阅读。
还要注意,这两种方法返回的流也必须关闭(例如使用 try-with-resources 语句),否则 JVM 将保持目录上的文件句柄打开,这(在 Windows 上)有效地锁定它。
如何递归列出文件
如果要递归列出文件树的所有文件,则需要使用我们用于删除目录的方法:Files.walk
.
try (var files = Files.walk(tmpDirectory)) {
files.forEach(System.out::println);
}
注意,返回的流Files.walk
也必须关闭(例如使用 try-with-resources 语句),否则 JVM 将保持目录上的文件句柄打开,这(在 Windows 上)有效地锁定它。
绝对、相对和规范文件
让我们快速讨论绝对路径、相对路径和规范路径的概念。最好用一些代码示例来演示:
相对路径
Path p = Paths.get("./src/main/java/../resources/some.properties");
System.out.println("p.isAbsolute() = " + p.isAbsolute());
在这里,您正在构建一个基于当前目录 (.) 的新路径,甚至在某些时候包括 (..)。因此,路径是relative
您当前的目录,path.isAbsolute
并将返回 false。
p.isAbsolute() = false
绝对路径
Path p2 = p.toAbsolutePath();
System.out.println("p2 = " + p2);
System.out.println("p2.isAbsolute() = " + p2.isAbsolute());
当你调用toAbsolutePath
路径时,它会被转换为一个..well...绝对路径,在我的例子中包含C:\dev\java-files
. 注意,绝对路径仍然包含点,对于当前目录和上级目录!
p2 = C:\dev\java-file-article\.\src\main\java\..\resources\some.properties
p2.isAbsolute() = true
标准化路径
怎么去掉点滴?您需要致电normalize
.
Path p3 = p.normalize().toAbsolutePath();
System.out.println("p3 = " + p3);
System.out.println("p3.isAbsolute() = " + p3.isAbsolute());
这个标准化的绝对路径,也就是你可以称之为规范路径的东西。
相对化路径
p3 = C:\dev\java-file-article\src\main\resources\some.properties
p3.isAbsolute() = true
最后但并非最不重要的是,你也可以走另一条路。您可以将绝对路径设为相对,而不是将相对路径设为绝对路径。
Path relativizedPath = Paths.get("C:\\dev\\java-file-article\\").relativize(p3);
System.out.println("relativizedPath = " + relativizedPath);
您基本上是在说,给定某个基本路径,我当前(绝对)路径的相对路径是什么。您将获得以下输出:
<span style="color:#212529"><span style="background-color:#ffffff"><span style="color:#000000"><span style="background-color:rgba(230, 235, 241, 0.5)"><code>relativizedPath = src\main\resources\some.properties</code></span></span></span></span>
查看文件和目录
一些项目需要查看新创建(认为:上传)文件的目录并对其进行处理。在 Java 中监视目录中的更改时,您有两种流行的选择。
Java 的 WatchService
在 Java 7 中,Java 是其WatchService。这是一种监视指定目录中更改的低级方法。
WatchService 将收到有关本机文件事件(Windows、Linux)的通知,但 MacOS 是一个明显的例外,它会回退到轮询目录以进行更改 - 这几乎是所有其他监视库默认情况下所做的(请参阅下一节) .
这里有一些代码,你不应该盲目地复制和粘贴,但它会让你了解 WatchService 的样子。
public static void main(String[] args) throws IOException {
WatchService watcher = FileSystems.getDefault().newWatchService();
Path dir = Path.of("c:\\someDir\\");
dir.register(watcher,
ENTRY_CREATE,
ENTRY_DELETE,
ENTRY_MODIFY);
for (;;) {
WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException x) {
return;
}
for (WatchEvent<?> event: key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
if (kind == OVERFLOW) {
continue;
}
WatchEvent<Path> ev = (WatchEvent<Path>)event;
Path filename = ev.context();
Path changedFile = dir.resolve(filename);
// do something with the file
}
boolean valid = key.reset();
if (!valid) {
break;
}
}
}
在这里讨论完整的 WatchService 实现并不真正适合本文的范围,但请注意:
使用 WatchService 时有几件事需要注意(没有双关语):
-
您可能会假设每次更新文件时都会收到一个事件,但这很容易导致两个事件:一个用于更新内容,另一个用于更新最后修改的时间戳,在短时间内发生。
-
像 IntelliJ 这样的复杂 IDE 甚至像 Notepad++ 这样更小的文本编辑器不会一次性保存文件及其内容。他们将内容复制到 tmp 文件,删除它们,然后将内容保存到您的实际文件等。同样,同一个甚至多个文件可能会发生多次更新,而您作为最终用户,理想情况下希望只有一个更新的事件。
-
因此,您需要应用一些解决方法。
Thread.sleep
过去,有 40 多个赞成票 ( )的未接受答案对我来说有些可靠)。
最后但并非最不重要的一点是,您可能想看看这篇精彩的文章,其中讨论了 Java 的 WatchService、容器和绑定挂载问题。
Apache Commons-IO
还有另一个库可以让您查看目录中的传入更改:Commons IO。从使用角度来看,它具有更简单的 API,但与 WatchService 有两个不同之处:
-
它仅适用于
java.io.Files
,不适用于java.nio.file.Paths
。 -
它使用轮询,即它调用 File 类的 listFiles() 方法并将输出与前一次迭代的 listFiles() 输出进行比较,以查看发生了什么变化。
下面是代码的大致样子,你不应该盲目地复制和粘贴:
public static void main(String[] args) throws IOException {
FileAlterationObserver observer = new FileAlterationObserver(folder);
FileAlterationMonitor monitor =
new FileAlterationMonitor(pollingInterval);
FileAlterationListener listener = new FileAlterationListenerAdaptor() {
// Is triggered when a file is created in the monitored folder
@Override
public void onFileCreate(File file) {
// do something
}
// Is triggered when a file is deleted from the monitored folder
@Override
public void onFileDelete(File file) {
// do something
}
};
}
内存文件系统
一些开发人员认为处理文件总是意味着您实际上必须将它们写入磁盘。
在测试期间,这会导致创建大量临时文件和目录,然后必须确保再次删除它们。
但是,使用 Java 的Path
-API,有一个更好的方法:内存中文件系统。
它们让您可以完全在内存中写入和读取文件,而无需访问磁盘。超快,非常适合测试(只要你没有用完内存,呃......)。
有两个 Java 内存文件系统值得一看。
内存文件系统
一种选择是Memory File System。让我们看看如何用它创建一个内存文件系统。
package com.marcobehler.files;
import com.github.marschall.memoryfilesystem.MemoryFileSystemBuilder;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
public class MemoryFileSystem {
public static void main(String[] args) throws IOException {
try (FileSystem fileSystem = MemoryFileSystemBuilder.newMacOs().build()) {
Path inMemoryFile = fileSystem.getPath("/somefile.txt");
Files.writeString(inMemoryFile, "Hello World");
System.out.println(Files.readString(inMemoryFile));
}
}
}
让我们分解一下。
try (FileSystem fileSystem = MemoryFileSystemBuilder.newMacOs().build()) {
唯一特定于内存文件系统的行是这一行。您需要创建一个FileSystem
稍后将用于创建和读取/写入您的Paths
.
通过调用newLinux()
或newWindows()
,newMacOs()
您可以控制创建的文件系统的语义。
Path inMemoryFile = fileSystem.getPath("/somefile.txt");
Files.writeString(inMemoryFile, "Hello World");
System.out.println(Files.readString(inMemoryFile));
您正在写入一个名为的文件somefile.txt
并在几行之后读取文件内容。
这是平原java.nio.file.Path-API
,有一个巨大的差异。你需要从让你的道路fileSystem
,不通过Path.of
或Paths.get
。
在查看 JimFS 之后,您会明白为什么会这样。
吉姆FS
另一个选择是JimFS。让我们看看如何用它创建一个内存文件系统。
package com.marcobehler.files;
import com.google.common.jimfs.Configuration;
import com.google.common.jimfs.Jimfs;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
public class JimFSSystem {
public static void main(String[] args) throws IOException {
try (FileSystem fileSystem = Jimfs.newFileSystem(Configuration.unix());) {
Path inMemoryFile = fileSystem.getPath("/tmp/somefile.txt");
Files.writeString(inMemoryFile, "Hello World");
System.out.println(Files.readString(inMemoryFile));
}
}
}
让我们分解一下。
try (FileSystem fileSystem = Jimfs.newFileSystem(Configuration.unix());) {
唯一特定于内存文件系统的行是这一行。您需要创建一个FileSystem
稍后将用于创建和读取/写入您的Paths
.
使用该Configuration.unix/windows/macOs
参数,您可以控制创建的文件系统的语义。
Path inMemoryFile = fileSystem.getPath("/tmp/somefile.txt");
Files.writeString(inMemoryFile, "Hello World");
System.out.println(Files.readString(inMemoryFile));
您正在写入一个名为的文件somefile.txt
并在几行之后读取文件内容。
这是平原java.nio.file.Path-API
,有一个巨大的差异。你需要从让你的道路fileSystem
,不通过Path.of
或Paths.get
。
现在让我们看看为什么会这样。
如何让你的应用程序使用内存文件系统:锚点
当您查看Path.of
or的实现时Paths.get
,您会看到:
public static Path of(String first, String... more) {
return FileSystems.getDefault().getPath(first, more);
}
所以,虽然这个方法(和其他人)都非常方便,使用起来会暗示你要访问您的default
文件系统,你的JVM上运行的一个(WindowsFileSystem,UnixFileSystem等),不是你的in-memory
文件系统。
因此,当想要确保您的代码适用于内存文件系统时,您必须确保永远不要调用这些辅助方法。相反,您应该始终使用 theFileSystem
或 aPath
作为锚点,就像您在上面的示例中所做的那样。
根据您的项目(想想:遗留),这是一个相当大的挑战。
鳍
到现在为止,您应该对如何在 Java 中处理文件有了一个很好的概述。
-
如何进行所有基本文件操作,从读取、写入、列出、移动和删除。
-
相对、绝对和规范路径如何工作。
-
如何查看目录和文件。
-
如何使用内存文件系统进行测试。
随时欢迎反馈、更正和随机输入!只需在下方发表评论。
谢谢阅读。