1. 概述
有时,我们需要从文件中读取原始文本,并清理混乱的内容,例如移除换行符。
在这个教程中,我们将探讨在Java中从文件中移除换行符的不同方法。
2. 换行符的简要说明
在深入代码之前,让我们快速了解一下我们想要移除的目标对象:换行符。
乍看之下,这很简单。换行符就是一个分隔行的字符。然而,实际上有不同类型的换行符。如果我们处理不当,可能会遇到陷阱。一个例子可以解释清楚:
假设我们有两个文本文件,multiple-line-1.txt
和 multiple-line-2.txt
。我们称它们为 file1
和 file2
。如果我们在IDE(如 IntelliJ)的编辑器中打开它们,看起来是这样的:
A,
B,
C,
D,
E,
F
我们可以看到,每个文件都有六行,从第二行开始,每行都有一个空格字符。 所以我们认为 file1
和 file2
包含完全相同的文本。
但是,现在我们使用 cat
命令(带有 -n
显示行号和 -e
显示非打印字符选项)打印文件内容:
$ cat -ne multiple-line-1.txt
1 A,$
2 B,$
3 C,$
4 D,$
5 E,$
6 F$
file1
的输出与我们在IntelliJ编辑器中看到的一样。但 file2
的显示却大不相同:
$ cat -ne multiple-line-2.txt
1 A,^M B,$
2 C,$
3 D,^M E,$
4 F$
这是因为存在三种不同的换行符:
-
\r
(回车),Mac OS X之前的行结束符 -
\n
(换行),在nix和Mac OS中使用的行结束符 -
\r\n
(CRLF),Windows中的行结束符
cat -e
显示CRLF为*^M*
。所以,我们看到 file2
包含CRLF。可能这个文件是在Windows上创建的。根据需求,我们可能希望移除所有类型的换行符,或者只移除当前系统的换行符。
接下来,我们将用这两个文件作为示例,看看如何读取它们的内容并移除换行符。为了简化,我们将创建两个辅助方法来返回每个文件的 [Path](/java-path-vs-file#javaniofilepath-class)
:
Path file1Path() throws Exception {
return Paths.get(this.getClass().getClassLoader().getResource("multiple-line-1.txt").toURI());
}
Path file2Path() throws Exception {
return Paths.get(this.getClass().getClassLoader().getResource("multiple-line-2.txt").toURI());
}
请注意,本文中使用的策略需要将整个文本读入内存,因此请考虑处理非常大的文件。
3. 使用 line.separator
替换空字符串
系统属性 line.separator
存储了特定于当前操作系统的行分隔符。 因此,如果我们只想移除特定于当前系统的换行符,我们可以将 line.separator
替换为空字符串。例如,在Linux机器上,这种方法可以移除 file1
中的所有换行符:
String content = Files.readString(file1Path(), StandardCharsets.UTF_8);
String result = content.replace(System.getProperty("line.separator"), "");
assertEquals("A, B, C, D, E, F", result);
我们使用 Files
类的 readString()
方法将文件内容加载到字符串中,然后通过 replace()
方法应用替换。
然而,同样的方法无法移除 file2
中的所有换行符,因为它包含CRLF换行符:
String content = Files.readString(file2Path(), StandardCharsets.UTF_8);
String result = content.replace(System.getProperty("line.separator"), "");
assertNotEquals("A, B, C, D, E, F", result); // <-- NOT equals assertion!
接下来,我们将查看是否可以独立于系统地移除所有换行符。
4. 使用正则表达式替换\n
和 \r
我们已经了解到三种不同的换行符涵盖了\n
和\r
字符。因此,如果我们想独立于系统地移除所有换行符,我们可以将\n
和\r
替换为空字符串:
String content1 = Files.readString(file1Path(), StandardCharsets.UTF_8);
// file contains CRLF
String content2 = Files.readString(file2Path(), StandardCharsets.UTF_8);
String result1 = content1.replace("\r", "").replace("\n", "");
String result2 = content2.replace("\r", "").replace("\n", "");
assertEquals("A, B, C, D, E, F", result1);
assertEquals("A, B, C, D, E, F", result2);
当然,我们也可以使用基于正则表达式的 replaceAll()
方法实现同样的目标。以 file2
为例,看看它是如何工作的:
String resultReplaceAll = content2.replaceAll("[\\n\\r]", "");
assertEquals("A, B, C, D, E, F", resultReplaceAll);
5. 使用 readAllLines()
后再使用 join()
回顾目前为止学到的方法,我们首先从文件中读取全部内容,然后替换 line.separator
系统属性或\n
和\r
字符为空。这些方法的共同点是我们自己手动管理行分隔符。
Files
类提供了 readAllLines()
方法,将文件内容读取为行并返回一个字符串列表。值得注意的是,readAllLines()
将所有提到的三种换行符视为一行分隔符。换句话说,这个方法会移除输入中的所有换行符。我们需要做的是连接返回列表中的元素。
join()
方法非常方便,可以连接列表或字符串数组:
List<String> lines1 = Files.readAllLines(file1Path(), StandardCharsets.UTF_8);
// file contains CRLF
List<String> lines2 = Files.readAllLines(file2Path(), StandardCharsets.UTF_8);
String result1 = String.join("", lines1);
String result2 = String.join("", lines2);
assertEquals("A, B, C, D, E, F", result1);
assertEquals("A, B, C, D, E, F", result2);
6. 总结
在这篇文章中,我们首先讨论了不同类型的换行符。然后,我们探索了从文件中移除换行符的不同方法。
一如既往,示例的完整源代码可以在GitHub上找到。