nio 读取目录所有文件
在先前的文章中,我讨论了文件和目录的创建( 创建文件和目录 )以及选择( 列出和过滤目录内容 )。 采取的最后一个合乎逻辑的步骤是探索我们如何使用它们以及如何使用它们。 这是库的一部分,已进行了很大的重新设计。 这方面的更新包括保证某些操作的原子性,API改进,性能优化以及引入适当的异常层次结构,这些层次结构取代了IO库以前版本中的boolean
返回方法。
开启档案
在开始阅读和写入文件之前,我们需要介绍这些操作的一个共同基础-文件的打开方式。 文件的打开方式直接影响这些操作的结果及其性能。 让我们看一下打开枚举java.nio.file.StandardOpenOption
包含的文件的标准选项:
值 | 描述 |
---|---|
APPEND | 如果打开文件以进行WRITE访问,则字节将被写入文件的末尾而不是开始。 |
CREATE | 如果不存在,请创建一个新文件。 |
CREATE_NEW | 创建一个新文件,如果文件已经存在则失败。 |
DELETE_ON_CLOSE | 关闭删除。 |
DSYNC | 要求对文件内容的每次更新都同步写入基础存储设备。 |
READ | 打开以进行读取访问。 |
SPARSE | 稀疏文件。 |
SYNC | 要求对文件内容或元数据的每次更新都同步写入基础存储设备。 |
TRUNCATE_EXISTING | 如果该文件已经存在并且已打开以进行WRITE访问,则其长度将被截断为0。 |
WRITE | 打开以进行写访问。 |
这些都是开发人员您可能需要正确处理文件打开(无论是读取还是写入)的所有标准选项。
读取文件
在读取文件时,NIO.2提供了几种方法-每种都有其优缺点。 这些方法如下:
- 将文件读入字节数组
- 使用无缓冲流
- 使用缓冲流
让我们来看看第一个选项。 类Files
提供了方法readAllBytes
来做到这一点。 将文件读入字节数组似乎很简单,但这可能仅适用于非常有限的文件范围。 由于我们将整个文件放入内存中,因此必须注意该文件的大小。 仅当我们尝试读取小文件并且可以立即完成时,使用此方法才是合理的。 如以下代码段所示,这是非常简单的操作:
Path filePath = Paths.get("C:", "a.txt");if (Files.exists(filePath)) {try {byte[] bytes = Files.readAllBytes(filePath);String text = new String(bytes, StandardCharsets.UTF_8);System.out.println(text);} catch (IOException e) {throw new RuntimeException(e);}
}
上面的代码首先将文件读取到字节数组中,然后使用以下输出构造包含该文件内容的字符串对象:
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam sit amet justo nec leo euismod porttitor. Vestibulum id sagittis nulla, eu posuere sem. Cras commodo, massa sed semper elementum, ligula orci malesuada tortor, sed iaculis ligula ligula et ipsum.
当我们需要以字符串形式读取文件的内容时,可以使用上面的代码。 但是,这种解决方案不是很干净,我们可以使用类Files
readAllLines
来避免这种尴尬的构造。 当需要逐行读取人类可读的输出时,此方法可作为读取文件的便捷解决方案。 此方法的使用再次非常简单,并且与前面的示例非常相似(有相同的限制):
Path filePath = Paths.get("C:", "b.txt");if (Files.exists(filePath)) {try {List<String> lines = Files.readAllLines(filePath, StandardCharsets.UTF_8);for (String line : lines) {System.out.println(line);}} catch (IOException e) {throw new RuntimeException(e);}
}
具有以下输出:
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Aliquam sit amet justo nec leo euismod porttitor.
Vestibulum id sagittis nulla, eu posuere sem.
Cras commodo, massa sed semper elementum, ligula orci malesuada tortor, sed iaculis ligula ligula et ipsum.
使用流读取文件
继续使用更复杂的方法,我们总是可以使用良好的旧流,就像我们以前使用该库的先前版本一样。 由于这是众所周知的基础,因此我将仅展示如何获取这些流的实例。 首先,我们可以通过调用newInputStream
方法从类Files
检索InputStream
实例。 像往常一样,可以进一步使用装饰器模式,并从中输出缓冲流。 为了方便起见,请使用newBufferedReader
方法。 这两个方法都返回一个流实例,该实例是普通的旧java.io
对象。
Path filePath1 = Paths.get("C:", "a.txt");
Path filePath2 = Paths.get("C:", "b.txt");InputStream is = Files.newInputStream(filePath1);
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);BufferedReader reader = Files.newBufferedReader(filePath2, StandardCharsets.UTF_8);
写入文件
写入文件与NIO.2库提供的一系列工具中的读取过程相似,因此只需回顾一下:
- 将字节数组写入文件
- 使用无缓冲流
- 使用缓冲流
再次让我们首先探索字节数组选项。 毫不奇怪, Files
类支持两种方法write
。 我们正在从数组或文本行中写入字节,在这里我们需要关注StandardOpenOptions
,因为这两种方法都可能受到这些修饰符的自定义选择的影响。 默认情况下,如果没有将StandardOpenOption
传递给该方法,则write
方法的行为就像存在CREATE
, TRUNCATE_EXISTING
和WRITE
选项一样(如Javadoc中所述)。 话虽如此,请注意不要使用默认(无打开选项)版本的write
方法,因为它要么创建一个新文件,要么最初将现有文件截断为零大小。 写入完成后,文件会自动关闭-成功写入后会引发异常。 对于文件大小,适用与readAllBytes
相同的限制。
下面的示例演示如何将字节数组写入文件。 请注意,由于write
方法的默认行为,因此没有任何检查方法。 该示例可以多次运行,并具有两个不同的结果。 第一次运行将创建一个文件,打开该文件进行写入,然后将数组bytes
写入此文件。 此代码的任何后续调用都将擦除文件,并将bytes
数组的内容写入此空文件。 两次运行都将导致文本为“ Hello world!”的封闭文件。 写在第一行。
Path newFilePath = Paths.get("/home/jstas/a.txt");
byte[] bytes = new byte[] {0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21};try {Files.write(newFilePath, bytes);
} catch(IOException e) {throw new RuntimeException(e);
}
当我们需要写行而不是字节时,我们可以将字符串转换为字节数组,但是,还有一种更方便的方法。 只需准备一行列表,然后将其传递给write
方法即可。 请注意以下示例中两个StandardOpenOption
的使用。 通过使用这些选项,我可以确保存在一个文件(如果不存在,则会创建该文件),以及将数据追加到该文件的方式(因此不会丢失任何先前写入的数据)。 整个例子很简单,看一下:
Path filePath = Paths.get("/home/jstas/b.txt");List<String> lines = new ArrayList<>();
lines.add("Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
lines.add("Aliquam sit amet justo nec leo euismod porttitor.");
lines.add("Vestibulum id sagittis nulla, eu posuere sem.");
lines.add("Cras commodo, massa sed semper elementum, ligula orci malesuada tortor, sed iaculis ligula ligula et ipsum.");try {Files.write(filePath, lines, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.APPEND);
} catch (IOException e) {throw new RuntimeException(e);
}
使用流写入文件
对于较大的文件,使用字节数组可能不是一个好主意。 这是流进入的时间。类似于阅读本章,我将不解释流或如何使用它们。 我宁愿专注于检索其实例的方法。 类Files
提供了newOutputStream
方法,该方法接受StandardOpenOption
来自定义流行为。 默认情况下,当没有将StandardOpenOption
传递给该方法时,流write
方法的行为就像存在CREATE
, TRUNCATE_EXISTING
和WRITE
选项一样(如Javadoc中所述)。 该流没有被缓冲,但是通过一点装饰器魔术,您可以创建BufferedWriter
实例。 为了解决这种不便,NIO.2附带了newBufferWriter
方法,该方法可以立即创建缓冲流实例。 以下代码段显示了两种方式:
Path filePath1 = Paths.get("/home/jstas/c.txt");
Path filePath2 = Paths.get("/home/jstas/d.txt");OutputStream os = Files.newOutputStream(filePath1);
OutputStreamWriter osw = new OutputStreamWriter(os);
BufferedWriter bw = new BufferedWriter(osw);BufferedWriter writer = Files.newBufferedWriter(filePath2, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.APPEND);
复制和移动文件和目录
复制文件和目录
NIO.2最受欢迎的功能之一是更新了处理复制和移动文件和目录的方式。 为了使所有内容保持一致,设计人员决定在新文件系统API中引入两个父(标记)接口: OpenOption
和CopyOption
(包java.nio.file
两个接口)。 上一章提到的StandardOpenOption
枚举实现了OpenOption
接口。 CopyOption
接口有两个实现,其中一个已经在关于NIO.2中的Links的帖子中见过。 你们中有些人可能还记得LinkOption
枚举,它被称为实现指导方法,用于处理链接相关的操作。 但是,还有另一种实现–包java.nio.file
StandardCopyOption
枚举。 再次,我们将看到另一个枚举–用于指导复制操作。 因此,在深入研究任何代码之前,让我们回顾一下使用不同的复制选项可以实现的目标。
值 | 描述 |
---|---|
ATOMIC_MOVE | 将文件作为原子文件系统操作移动。 |
COPY_ATTRIBUTES | 将属性复制到新文件。 |
REPLACE_EXISTING | 替换现有文件(如果存在)。 |
使用这些选项来指导您的IO操作非常简单,也很简单。 由于我们正在尝试复制文件,因此ATOMIC_MOVE
使用意义不大(您仍然可以使用它,但最终会出现java.lang.UnsupportedOperationException: Unsupported copy option
)。 类Files
提供了三种copy
方法,可用于不同目的:
-
copy(InputStream in, Path target, CopyOption... options)
- 将所有字节从输入流复制到文件。
-
copy(Path source, OutputStream out)
- 将所有字节从文件复制到输出流。
-
copy(Path source, Path target, CopyOption... options)
- 将文件复制到目标文件。
在我们获得任何代码之前,我相信最好了解copy
方法的最重要的行为功能(上述三个方法中的最后一个)。 copy
方法的行为如下(基于Javadoc):
- 默认情况下,如果目标文件已经存在或为符号链接,则复制将失败。
- 如果源和目标是同一文件,则该方法将完成而不复制该文件。 (有关更多信息,请查看类
Files
方法isSameFile
) - 不需要将文件属性复制到目标文件。
- 如果源文件是目录,则它将在目标位置创建一个空目录(不复制目录中的条目)。
- 复制文件不是原子操作。
- 自定义实现可能会带来新的特定选项。
这些是copy
方法内部工作的核心原理。 现在是查看代码示例的好时机。 由于此方法非常易于使用,因此可以将其实际使用(使用最常见的copy
方法形式)。 如预期的那样,以下代码将复制源文件(并可能覆盖目标文件)并保留文件属性:
Path source = Paths.get("/home/jstas/a.txt");
Path target = Paths.get("/home/jstas/A/a.txt");try {Files.copy(source, target, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {throw new RuntimeException(e);
}
这里没有什么大的惊喜–代码复制带有文件属性的源文件。 如果您觉得我忘记了(不是空的)目录,请让我向我保证。 也可以使用NIO.2复制,移动或删除填充的目录,但这是我将在下一篇文章中介绍的内容,因此您将不得不等待几天。
移动文件和目录
当涉及到移动文件时,我们再次需要能够指定选项,以指导方法从Files
类move
的过程。 在这里,我们利用了上一章中提到的StandardCopyOptions
。 两个相关的选项是ATOMIC_MOVE
和REPLACE_EXISTING
。 首先,让我们从一些基本特征入手,然后继续进行代码示例:
- 默认情况下,如果目标文件已存在,则
move
方法将失败。 - 如果源和目标是同一文件,则该方法将完成而不移动文件。 (有关更多信息,请查看类
Files
方法isSameFile
) - 如果源是符号链接,则链接本身将被移动。
- 如果源文件是目录,则必须为空才能移动。
- 不需要移动文件属性。
- 可以将移动文件配置为原子操作,但不必这样做。
- 自定义实现可能会带来新的特定选项。
代码非常简单,因此让我们看下面的代码片段:
Path source = Paths.get("/home/jstas/b.txt");
Path target = Paths.get("/home/jstas/A/b.txt");try {Files.move(source, target, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
} catch(IOException e) {throw new RuntimeException(e);
}
如预期的那样,代码以原子操作方式移动源文件。
删除文件和目录
本文的最后一部分致力于删除文件和目录。 再次删除文件非常简单,可以使用两种可能的方法来调用(通常都从Files
类):
-
public static void delete(Path path)
-
public static boolean deleteIfExists(Path path)
两种方法使用相同的规则:
- 默认情况下,当文件是目录并且不为空时,删除方法将失败,并显示
DirectoryNotEmptyException
。 - 如果文件是符号链接,则链接本身将被删除。
- 删除文件可能不是原子操作。
- 如果文件已打开或被JVM或其他软件使用,则可能不会删除文件。
- 自定义实现可能会带来新的特定选项。
Path newFile = Paths.get("/home/jstas/c.txt");
Path nonExistingFile = Paths.get("/home/jstas/d.txt");try {Files.createFile(newFile);Files.delete(newFile);System.out.println("Any file deleted: " + Files.deleteIfExists(nonExistingFile));
} catch(IOException e) {throw new RuntimeException(e);
}
输出:
Any file deleted: false
翻译自: https://www.javacodegeeks.com/2014/06/working-with-files-and-directories-in-nio-2.html
nio 读取目录所有文件