读取格式错误的文件时,StreamDecoder 与 InputStreamReader
StreamDecoder vs InputStreamReader when reading malformed files
我在 Java 8 中阅读文件时遇到了一些奇怪的行为,我想知道是否有人能理解它。
场景:
正在读取格式错误的文本文件。我所说的格式错误是指它包含不映射到任何 unicode 代码点的字节。
我用来创建这样一个文件的代码如下:
byte[] text = new byte[1];
char k = (char) -60;
text[0] = (byte) k;
FileUtils.writeByteArrayToFile(new File("/tmp/malformed.log"), text);
此代码生成的文件恰好包含一个字节,该字节不是 ASCII table(也不是扩展字节)的一部分。
尝试 cat
此文件会产生以下输出:
�
哪个是UNICODE Replacement Character。这是有道理的,因为 UTF-8 需要 2 个字节来解码非 ascii 字符,但我们只有一个。这也是我对 Java 代码的期望。
粘贴一些常用代码:
private void read(Reader reader) throws IOException {
CharBuffer buffer = CharBuffer.allocate(8910);
buffer.flip();
// move existing data to the front of the buffer
buffer.compact();
// pull in as much data as we can from the socket
int charsRead = reader.read(buffer);
// flip so the data can be consumed
buffer.flip();
ByteBuffer encode = Charset.forName("UTF-8").encode(buffer);
byte[] body = new byte[encode.remaining()];
encode.get(body);
System.out.println(new String(body));
}
这是我使用 nio
的第一种方法:
FileInputStream inputStream = new FileInputStream(new File("/tmp/malformed.log"));
read(Channels.newReader(inputStream.getChannel(), "UTF-8");
这会产生以下异常:
java.nio.charset.MalformedInputException: Input length = 1
at java.nio.charset.CoderResult.throwException(CoderResult.java:281)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:339)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
at java.io.Reader.read(Reader.java:100)
这不是我所期望的,但也有点道理,因为这实际上是一个损坏的非法文件,异常基本上告诉我们它需要读取更多字节。
我的第二个(使用常规java.io
):
FileInputStream inputStream = new FileInputStream(new File("/tmp/malformed.log"));
read(new InputStreamReader(inputStream, "UTF-8"));
这不会失败并产生与 cat
完全相同的输出:
�
这也有道理。
所以我的问题是:
- 在这种情况下,Java 应用程序的预期行为是什么?
- 为什么使用
Channels.newReader
(其中 returns 和 StreamDecoder
)与仅使用常规 InputStreamReader
之间存在差异?我的阅读方式有问题吗?
如有任何说明,我们将不胜感激。
谢谢:)
行为之间的差异实际上可以追溯到 StreamDecoder and Charset classes。 InputStreamReader
从 StreamDecoder.forInputStreamReader(..)
得到一个 CharsetDecoder
,它在出错时进行替换
StreamDecoder(InputStream in, Object lock, Charset cs) {
this(in, lock,
cs.newDecoder()
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE));
}
而 Channels.newReader(..)
使用默认设置创建解码器(即报告而不是替换,这会进一步导致异常)
public static Reader newReader(ReadableByteChannel ch,
String csName) {
checkNotNull(csName, "csName");
return newReader(ch, Charset.forName(csName).newDecoder(), -1);
}
所以它们的工作方式不同,但文档中没有任何关于差异的指示。这被记录得很糟糕,但我假设他们改变了功能,因为你宁愿得到一个例外,也不愿让你的数据默默地损坏。
处理字符编码时要小心!
我在 Java 8 中阅读文件时遇到了一些奇怪的行为,我想知道是否有人能理解它。
场景:
正在读取格式错误的文本文件。我所说的格式错误是指它包含不映射到任何 unicode 代码点的字节。
我用来创建这样一个文件的代码如下:
byte[] text = new byte[1];
char k = (char) -60;
text[0] = (byte) k;
FileUtils.writeByteArrayToFile(new File("/tmp/malformed.log"), text);
此代码生成的文件恰好包含一个字节,该字节不是 ASCII table(也不是扩展字节)的一部分。
尝试 cat
此文件会产生以下输出:
�
哪个是UNICODE Replacement Character。这是有道理的,因为 UTF-8 需要 2 个字节来解码非 ascii 字符,但我们只有一个。这也是我对 Java 代码的期望。
粘贴一些常用代码:
private void read(Reader reader) throws IOException {
CharBuffer buffer = CharBuffer.allocate(8910);
buffer.flip();
// move existing data to the front of the buffer
buffer.compact();
// pull in as much data as we can from the socket
int charsRead = reader.read(buffer);
// flip so the data can be consumed
buffer.flip();
ByteBuffer encode = Charset.forName("UTF-8").encode(buffer);
byte[] body = new byte[encode.remaining()];
encode.get(body);
System.out.println(new String(body));
}
这是我使用 nio
的第一种方法:
FileInputStream inputStream = new FileInputStream(new File("/tmp/malformed.log"));
read(Channels.newReader(inputStream.getChannel(), "UTF-8");
这会产生以下异常:
java.nio.charset.MalformedInputException: Input length = 1
at java.nio.charset.CoderResult.throwException(CoderResult.java:281)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:339)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
at java.io.Reader.read(Reader.java:100)
这不是我所期望的,但也有点道理,因为这实际上是一个损坏的非法文件,异常基本上告诉我们它需要读取更多字节。
我的第二个(使用常规java.io
):
FileInputStream inputStream = new FileInputStream(new File("/tmp/malformed.log"));
read(new InputStreamReader(inputStream, "UTF-8"));
这不会失败并产生与 cat
完全相同的输出:
�
这也有道理。
所以我的问题是:
- 在这种情况下,Java 应用程序的预期行为是什么?
- 为什么使用
Channels.newReader
(其中 returns 和StreamDecoder
)与仅使用常规InputStreamReader
之间存在差异?我的阅读方式有问题吗?
如有任何说明,我们将不胜感激。
谢谢:)
行为之间的差异实际上可以追溯到 StreamDecoder and Charset classes。 InputStreamReader
从 StreamDecoder.forInputStreamReader(..)
得到一个 CharsetDecoder
,它在出错时进行替换
StreamDecoder(InputStream in, Object lock, Charset cs) {
this(in, lock,
cs.newDecoder()
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE));
}
而 Channels.newReader(..)
使用默认设置创建解码器(即报告而不是替换,这会进一步导致异常)
public static Reader newReader(ReadableByteChannel ch,
String csName) {
checkNotNull(csName, "csName");
return newReader(ch, Charset.forName(csName).newDecoder(), -1);
}
所以它们的工作方式不同,但文档中没有任何关于差异的指示。这被记录得很糟糕,但我假设他们改变了功能,因为你宁愿得到一个例外,也不愿让你的数据默默地损坏。
处理字符编码时要小心!