Java 8 RandomAccessFile 读取 UTF-8
问题结论
为什么 RandomAccessFile 的 readLine() 读 UTF-8 文件是乱码?
RandomAccessFile 的函数 readLine() 使用 ISO-8859-1
解码文件,所以读取 UTF-8 的文件会造成乱码。解决方式就是再使用ISO-8859-1
编码得到原先的byte[]数组,再用这个数组重新构造 String 即可。
但是使用ISO-8859-1
解码并没有在文档中提及,这个隐藏特性的来源是什么呢?
源码之下,了无秘密
首先查看 readLine() 的源码
public final String readLine() throws IOException {
StringBuffer input = new StringBuffer();
int c = -1;
boolean eol = false;
while (!eol) {
switch (c = read()) {
case -1:
case '\n':
eol = true;
break;
case '\r':
eol = true;
long cur = getFilePointer();
if ((read()) != '\n') {
seek(cur);
}
break;
default:
input.append((char)c);
break;
}
}
if ((c == -1) && (input.length() == 0)) {
return null;
}
return input.toString();
}
readLine 实际上就是通过迭代 read() 函数读取单个字节,并把每个字节转化成 char 类型依次装入 input 中,在遇到换行符之后停止操作。这里我们可以注意到有趣的一点,就是他对于不同协议中的两种换行符都做了考虑。
所以我们继续探索 read() 函数
read() 源码
public int read() throws IOException {
return read0();
}
read() 函数非常的简单,通过注释我了解到,read() 的作用就是读取一个字节的内容,并把这个字节装入 int 中返回。
思考
再回头看 readLine(),这两个函数都非常的简单,无论是代码还是注释根本就没有提什么 ISO-8859-1
标准,那这一行是怎么莫名其妙的被以 ISO-8859-1
解码的呢。
这是因为,Java 的 char 类型使用的是Unicode
。而ISO-8859-1
是一位定长的字符集,Unicode
的前256位和ISO-8859-1
是重合的。换句话说,Unicode
的前256位就是ISO-8859-1
。所以在 readLine() 中对每一字节进行读取并立即转化成 char 类型的过程,就相当于完成了 ISO-8859-1
解码。
而我们要想得到原始的 byte 串怎么办呢?有两种方法,一种就是把 readLine() 读出的 String 用 ISO-8859-1
编码转回 编码前的 byte[] 数组。
import java.io.IOException;
import java.io.RandomAccessFile;
public class LearnBytes {
public static void main(String[] args) throws IOException {
String path = "files/t.txt";
RandomAccessFile rf = new RandomAccessFile(path, "rw");
String buffer = rf.readLine();
byte[] originalBytes = buffer.getBytes("ISO-8859-1”); //反编码回文件中本原始的字节流
String utf8 = new String(originalBytes); //String 构造函数默认接受 UTF-8 编码
}
}
另一种方法就是通过 read() 自己写一个readLine()函数,返回值是byte[]类型。
总结
It’s not a bug. It’s an undocumented feature.
参考及补充
严格来讲,ISO-8859-1 和 Java char 使用的 Unicode 是字符集。而 UTF-8 是基于 Unicode 的编码方式。