读取格式错误的文件时 StreamDecoder 与 InputStreamReader

Eli*_*sky 5 java encoding nio malformed

我在 Java 8 中读取文件时遇到了一些奇怪的行为,我想知道是否有人能理解它。

\n\n

设想:

\n\n

读取格式错误的文本文件。我所说的格式错误是指它包含不映射到任何 unicode 代码点的字节。

\n\n

我用来创建这样一个文件的代码如下:

\n\n
byte[] text = new byte[1];\nchar k = (char) -60;\ntext[0] = (byte) k;\nFileUtils.writeByteArrayToFile(new File("/tmp/malformed.log"), text);\n
Run Code Online (Sandbox Code Playgroud)\n\n

此代码生成一个仅包含一个字节的文件,该文件不是 ASCII 表(也不是扩展表)的一部分。

\n\n

尝试cat此文件会产生以下输出:

\n\n
\xef\xbf\xbd\n
Run Code Online (Sandbox Code Playgroud)\n\n

这是UNICODE 替换字符。这是有道理的,因为 UTF-8 需要 2 个字节才能解码非 ascii 字符,但我们只有一个字节。这也是我对 Java 代码的期望。

\n\n

贴一些常用代码:

\n\n
private void read(Reader reader) throws IOException {\n\n    CharBuffer buffer = CharBuffer.allocate(8910);\n\n    buffer.flip();\n\n    // move existing data to the front of the buffer\n    buffer.compact();\n\n    // pull in as much data as we can from the socket\n    int charsRead = reader.read(buffer);\n\n    // flip so the data can be consumed\n    buffer.flip();\n\n    ByteBuffer encode = Charset.forName("UTF-8").encode(buffer);\n    byte[] body = new byte[encode.remaining()];\n    encode.get(body);\n\n    System.out.println(new String(body));\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

这是我使用的第一种方法nio

\n\n
FileInputStream inputStream = new FileInputStream(new File("/tmp/malformed.log"));\nread(Channels.newReader(inputStream.getChannel(), "UTF-8");\n
Run Code Online (Sandbox Code Playgroud)\n\n

这会产生以下异常:

\n\n
java.nio.charset.MalformedInputException: Input length = 1\n\n    at java.nio.charset.CoderResult.throwException(CoderResult.java:281)\n    at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:339)\n    at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)\n    at java.io.Reader.read(Reader.java:100)\n
Run Code Online (Sandbox Code Playgroud)\n\n

这不是我所期望的,但也是有道理的,因为这实际上是一个损坏的非法文件,并且异常基本上告诉我们它期望读取更多字节。

\n\n

我的第二个(使用常规java.io):

\n\n
FileInputStream inputStream = new FileInputStream(new File("/tmp/malformed.log"));\nread(new InputStreamReader(inputStream, "UTF-8"));\n
Run Code Online (Sandbox Code Playgroud)\n\n

这不会失败并产生与以下完全相同的输出cat

\n\n
\xef\xbf\xbd\n
Run Code Online (Sandbox Code Playgroud)\n\n

这也是有道理的。

\n\n

所以我的问题是:

\n\n
    \n
  1. 在这种情况下,Java 应用程序的预期行为是什么?
  2. \n
  3. Channels.newReader为什么使用(返回 a StreamDecoder) 和简单使用常规之间有区别InputStreamReader?我的阅读方式有问题吗?
  4. \n
\n\n

任何澄清将不胜感激。

\n\n

谢谢 :)

\n

Kay*_*man 6

行为之间的差异实际上直接归因于StreamDecoder 和 Charset 类。从中获取InputStreamReader一个错误替换CharsetDecoderStreamDecoder.forInputStreamReader(..)

StreamDecoder(InputStream in, Object lock, Charset cs) {
    this(in, lock,
    cs.newDecoder()
    .onMalformedInput(CodingErrorAction.REPLACE)
    .onUnmappableCharacter(CodingErrorAction.REPLACE));
}
Run Code Online (Sandbox Code Playgroud)

Channels.newReader(..)使用默认设置创建解码器(即报告而不是替换,这会导致进一步的异常)

public static Reader newReader(ReadableByteChannel ch,
                               String csName) {
    checkNotNull(csName, "csName");
    return newReader(ch, Charset.forName(csName).newDecoder(), -1);
}
Run Code Online (Sandbox Code Playgroud)

因此它们的工作方式不同,但文档中没有任何关于差异的指示。这是记录得很差的,但我他们改变了功能,因为你宁愿得到一个异常,也不愿让你的数据默默地损坏。

处理字符编码时要小心!