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

时间:2017-08-01 12:05:08

标签: java encoding nio malformed

我在阅读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表的一部分(也不是扩展表)。

尝试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完全相同的输出:

这也是有道理的。

所以我的问题是:

  1. 在这种情况下,Java应用程序的预期行为是什么?
  2. 为什么使用Channels.newReader(返回StreamDecoder)和仅使用常规InputStreamReader之间存在差异?我是怎么读错的?
  3. 非常感谢任何澄清。

    谢谢:)

1 个答案:

答案 0 :(得分:2)

行为之间的差异实际上直到StreamDecoder and Charset classesInputStreamReaderCharsetDecoder获得StreamDecoder.forInputStreamReader(..),并在错误时替换

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);
}

所以他们的工作方式不同,但文档中没有迹象表明存在差异。这个文档很难记录,但我假设他们改变了功能,因为你宁愿得到一个例外,而不是让你的数据无声地被破坏。

处理角色编码时要小心!