如何用 Java 读取文件
1. 概述
在本教程中,我们将探索用 Java 读取文件的不同方法。
首先,我们将学习如何使用标准 Java 类从类路径、URL 或 JAR 文件加载文件。
其次,我们将了解如何使用 BufferedReader、Scanner、StreamTokenizer、DataInputStream、 读取内容SequenceInputStream、 和 FileChannel。我们还将讨论如何读取 UTF-8 编码的文件。
最后,我们将探索在 Java 7 和 Java 8 中加载和读取文件的新技术。
本文是“Java – 回归基础”系列的一部分。
2. 设置
2.1.输入文件
在本文的大多数示例中,我们将读取文件名为 fileTest.txt 的文本文件,其中包含一行:
Hello, world!
对于几个示例,我们将使用不同的文件;在这些情况下,我们将明确提及该文件及其内容。
2.2.辅助方法
我们将使用一组仅包含核心 Java 类的测试示例,并且在测试中,我们将使用带有 Hamcrest 匹配器的断言。
Tests 将共享一个通用的 readFromInputStream 方法,该方法可将 InputStream 转换为 字符串, 以便更轻松地断言结果:
private String readFromInputStream(InputStream inputStream)
throws IOException {
StringBuilder resultStringBuilder = new StringBuilder();
try (BufferedReader br
= new BufferedReader(new InputStreamReader(inputStream))) {
String line;
while ((line = br.readLine()) != null) {
resultStringBuilder.append(line).append("\n");
}
}
return resultStringBuilder.toString();
}
请注意,还有其他方法可以实现相同的结果。我们可以参考这篇文章 一些替代方案。
3. 从类路径读取文件
3.1.使用标准 Java
本节介绍如何读取类路径上可用的文件。我们将读取“fileTest.txt”可用在src/main/resources:下
@Test
public void givenFileNameAsAbsolutePath_whenUsingClasspath_thenFileData() {
String expectedData = "Hello, world!";
Class clazz = FileOperationsTest.class;
InputStream inputStream = clazz.getResourceAsStream("/fileTest.txt");
String data = readFromInputStream(inputStream);
Assert.assertThat(data, containsString(expectedData));
}
在上面的代码片段中,我们使用当前类通过getResourceAsStream方法加载文件,并传递要加载的文件的绝对路径。
同样的方法也可用于 ClassLoader实例:
ClassLoader classLoader = getClass().getClassLoader();
InputStream inputStream = classLoader.getResourceAsStream("fileTest.txt");
String data = readFromInputStream(inputStream);
我们使用getClass().getClassLoader() 获取当前类的 classLoader 。
主要区别在于,在 ClassLoader 实例上使用 getResourceAsStream 时,路径被视为绝对路径,从 classpath 的根开始。
在针对 Class 实例使用时,路径可以相对于包或绝对路径,前导斜线会对此进行提示。
当然,请注意,在实践中,打开的流应该始终关闭,例如我们示例中的InputStream:
InputStream inputStream = null;
try {
File file = new File(classLoader.getResource("fileTest.txt").getFile());
inputStream = new FileInputStream(file);
//...
}
finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.2.使用commons-io库
另一个常见的选项是使用 commons-io 包的 FileUtils 类:
@Test
public void givenFileName_whenUsingFileUtils_thenFileData() {
String expectedData = "Hello, world!";
ClassLoader classLoader = getClass().getClassLoader();
File file = new File(classLoader.getResource("fileTest.txt").getFile());
String data = FileUtils.readFileToString(file, "UTF-8");
assertEquals(expectedData, data.trim());
}
这里我们将File对象传递给FileUtils类的readFileToString()方法。该实用程序类无需编写任何样板代码即可加载内容来创建InputStream实例并读取数据。
同一个库还提供了IOUtils 类:
@Test
public void givenFileName_whenUsingIOUtils_thenFileData() {
String expectedData = "Hello, world!";
FileInputStream fis = new FileInputStream("src/test/resources/fileTest.txt");
String data = IOUtils.toString(fis, "UTF-8");
assertEquals(expectedData, data.trim());
}
这里我们将FileInputStream对象传递给IOUtils类的toString()方法。该实用程序类的行为方式与前一个实用程序类相同,用于创建 InputStream 实例并读取数据。
4. 使用BufferedReader读取
现在让我们关注解析文件内容的不同方法。
我们将使用 BufferedReader,从文件读取的简单方法开始:
@Test
public void whenReadWithBufferedReader_thenCorrect()
throws IOException {
String expected_value = "Hello, world!";
String file ="src/test/resources/fileTest.txt";
BufferedReader reader = new BufferedReader(new FileReader(file));
String currentLine = reader.readLine();
reader.close();
assertEquals(expected_value, currentLine);
}
请注意,当到达文件末尾时,readLine() 将返回 null。
5. 使用 Java NIO 读取文件
在JDK7中,NIO包进行了重大更新。
让我们来看一个使用Files类和readAllLines方法的示例。 readAllLines方法接受Path。
Path 类可以被视为 java.io.File 的升级,并具有一些附加操作。
5.1.读取小文件
以下代码演示了如何使用新的 Files 类读取小文件:
@Test
public void whenReadSmallFileJava7_thenCorrect()
throws IOException {
String expected_value = "Hello, world!";
Path path = Paths.get("src/test/resources/fileTest.txt");
String read = Files.readAllLines(path).get(0);
assertEquals(expected_value, read);
}
请注意,如果我们需要二进制数据,我们也可以使用 readAllBytes() 方法。
5.2.读取大文件
如果我们想使用Files类读取大文件,我们可以使用BufferedReader。
以下代码使用新的 Files 类和 BufferedReader 读取文件:
@Test
public void whenReadLargeFileJava7_thenCorrect()
throws IOException {
String expected_value = "Hello, world!";
Path path = Paths.get("src/test/resources/fileTest.txt");
BufferedReader reader = Files.newBufferedReader(path);
String line = reader.readLine();
assertEquals(expected_value, line);
}
5.3.使用Files.lines()读取文件
JDK8 在 Files 类中提供了 lines() 方法。它返回一个 String 元素的Stream。
让我们看一个示例,了解如何将数据读入字节并使用 UTF-8 字符集对其进行解码。
以下代码使用新的 Files.lines() 读取文件:
@Test
public void givenFilePath_whenUsingFilesLines_thenFileData() {
String expectedData = "Hello, world!";
Path path = Paths.get(getClass().getClassLoader()
.getResource("fileTest.txt").toURI());
Stream<String> lines = Files.lines(path);
String data = lines.collect(Collectors.joining("\n"));
lines.close();
Assert.assertEquals(expectedData, data.trim());
}
将 Stream 与文件操作等 IO 通道结合使用,我们需要使用 close() 方法显式关闭流。
正如我们所看到的,Files API 提供了另一种将文件内容读入字符串的简单方法。
在接下来的部分中,我们将了解在某些情况下可能适用的其他不太常见的读取文件的方法。
6. 使用Scanner读取
接下来让我们使用Scanner来读取文件。这里我们将使用空格作为分隔符:
@Test
public void whenReadWithScanner_thenCorrect()
throws IOException {
String file = "src/test/resources/fileTest.txt";
Scanner scanner = new Scanner(new File(file));
scanner.useDelimiter(" ");
assertTrue(scanner.hasNext());
assertEquals("Hello,", scanner.next());
assertEquals("world!", scanner.next());
scanner.close();
}
请注意,默认分隔符是空格,但Scanner可以使用多个分隔符。
Scanner 类在从控制台读取内容时很有用,或者当内容包含带有已知分隔符的原始值(例如:列表由空格分隔的整数)。
7. 使用 StreamTokenizer 读取
现在,让我们使用 StreamTokenizer 将文本文件读入标记。
分词器的工作原理是首先找出下一个标记是什么,字符串或数字。我们通过查看 tokenizer.ttype 字段来做到这一点。
然后我们将根据这种类型读取实际的标记:
- tokenizer.nval – 如果类型是数字
- tokenizer.sval – 如果类型是字符串
在此示例中,我们将使用不同的输入文件,其中仅包含:
Hello 1
以下代码从文件中读取字符串和数字:
@Test
public void whenReadWithStreamTokenizer_thenCorrectTokens()
throws IOException {
String file = "src/test/resources/fileTestTokenizer.txt";
FileReader reader = new FileReader(file);
StreamTokenizer tokenizer = new StreamTokenizer(reader);
// token 1
tokenizer.nextToken();
assertEquals(StreamTokenizer.TT_WORD, tokenizer.ttype);
assertEquals("Hello", tokenizer.sval);
// token 2
tokenizer.nextToken();
assertEquals(StreamTokenizer.TT_NUMBER, tokenizer.ttype);
assertEquals(1, tokenizer.nval, 0.0000001);
// token 3
tokenizer.nextToken();
assertEquals(StreamTokenizer.TT_EOF, tokenizer.ttype);
reader.close();
}
请注意最后如何使用文件结束标记。
这种方法对于将输入流解析为标记非常有用。
8. 使用DataInputStream读取
我们可以使用 DataInputStream 从文件中读取二进制或原始数据类型。
以下测试使用 DataInputStream 读取文件:
@Test
public void whenReadWithDataInputStream_thenCorrect() throws IOException {
String expectedValue = "Hello, world!";
String file ="src/test/resources/fileTest.txt";
String result = null;
DataInputStream reader = new DataInputStream(new FileInputStream(file));
int nBytesToRead = reader.available();
if(nBytesToRead > 0) {
byte[] bytes = new byte[nBytesToRead];
reader.read(bytes);
result = new String(bytes);
}
assertEquals(expectedValue, result);
}
9. 使用FileChannel读取
如果我们正在读取一个大文件,FileChannel 可以比标准 IO 更快。
以下代码使用 FileChannel 和 RandomAccessFile 从文件中读取数据字节:
@Test
public void whenReadWithFileChannel_thenCorrect()
throws IOException {
String expected_value = "Hello, world!";
String file = "src/test/resources/fileTest.txt";
RandomAccessFile reader = new RandomAccessFile(file, "r");
FileChannel channel = reader.getChannel();
int bufferSize = 1024;
if (bufferSize > channel.size()) {
bufferSize = (int) channel.size();
}
ByteBuffer buff = ByteBuffer.allocate(bufferSize);
channel.read(buff);
buff.flip();
assertEquals(expected_value, new String(buff.array()));
channel.close();
reader.close();
}
10.读取UTF-8编码文件
现在让我们看看如何使用BufferedReader读取UTF-8编码的文件。 在这个例子中,我们将读取一个包含汉字的文件:
@Test
public void whenReadUTFEncodedFile_thenCorrect()
throws IOException {
String expected_value = "青空";
String file = "src/test/resources/fileTestUtf8.txt";
BufferedReader reader = new BufferedReader
(new InputStreamReader(new FileInputStream(file), "UTF-8"));
String currentLine = reader.readLine();
reader.close();
assertEquals(expected_value, currentLine);
}
11.从URL读取内容
要从 URL 读取内容,我们将在示例中使用“/”URL:
@Test
public void givenURLName_whenUsingURL_thenFileData() {
String expectedData = "Baeldung";
URL urlObject = new URL("/");
URLConnection urlConnection = urlObject.openConnection();
InputStream inputStream = urlConnection.getInputStream();
String data = readFromInputStream(inputStream);
Assert.assertThat(data, containsString(expectedData));
}
还有其他连接 URL 的方法。这里我们使用了 URL 和 URLConnection标准 SDK 中提供的类。
12. 从 JAR 中读取文件
要读取位于 JAR 文件内的文件,我们需要一个其中包含文件的 JAR。对于我们的示例,我们将从“hamcrest-library-1.3.jar”文件中读取“LICENSE.txt”:
@Test
public void givenFileName_whenUsingJarFile_thenFileData() {
String expectedData = "BSD License";
Class clazz = Matchers.class;
InputStream inputStream = clazz.getResourceAsStream("/LICENSE.txt");
String data = readFromInputStream(inputStream);
Assert.assertThat(data, containsString(expectedData));
}
这里我们要加载位于 Hamcrest 库中的 LICENSE.txt,因此我们将使用 Matcher 的类来帮助获取资源。也可以使用类加载器加载相同的文件。
13. 结论
正如我们所看到的,使用纯 Java 加载文件并从中读取数据的可能性有很多。
我们可以从不同位置加载文件,例如类路径、URL 或 jar 文件。
然后我们可以使用BufferedReader逐行读取,Scanner使用不同的分隔符进行读取,StreamTokenizer将文件读取为令牌, DataInputStream用于读取二进制数据和原始数据类型,SequenceInput Stream用于将多个文件连接到一个流中,FileChannel用于更快地从大文件中读取,等等。
我们可以在下面找到本文的源代码GitHub 存储库。