【问题标题】:problem using base64 encoder and InputStreamReader使用 base64 编码器和 InputStreamReader 的问题
【发布时间】:2011-02-25 14:25:29
【问题描述】:

我在数据库中有一些 CLOB 列,我需要将 Base64 编码的二进制文件放入其中。 这些文件可能很大,所以我需要流式传输它们,我无法一次读取整个内容。

我正在使用org.apache.commons.codec.binary.Base64InputStream 进行编码,但遇到了问题。我的代码本质上是这样的

FileInputStream fis = new FileInputStream(file);
Base64InputStream b64is = new Base64InputStream(fis, true, -1, null);
BufferedReader reader = new BufferedReader(new InputStreamReader(b64is));

preparedStatement.setCharacterStream(1, reader);

当我运行上述代码时,我会在执行更新期间得到其中之一 java.io.IOException: Underlying input stream returned zero bytes,在 InputStreamReader 代码深处抛出。

为什么这不起作用?在我看来,reader 会尝试从 base 64 流中读取,该流将从文件流中读取,一切都应该是快乐的。

【问题讨论】:

    标签: java encoding jdbc base64 inputstreamreader


    【解决方案1】:

    “为了获得最高效率,请考虑将InputStreamReader 包装在BufferedReader 中。例如:”

    BufferedReader in = new BufferedReader(new InputStreamReader(b64is));
    

    附录:由于 Base64 被填充为 4 个字符的倍数,请确认源未被截断。可能需要flush()

    【讨论】:

    • 或许效率更高,但并不能解决问题
    • 您的流有可能被截断吗? IIRC,base64 被陷害。
    • 问题已更新。您能否详细说明“base64 is framed”是什么意思?流直接来自文件。
    • 编码流必须填充到“4个字符的整数倍”才能解码最后一个字节;如果流被截断,这将是一个问题。上面引用的参考资料。
    • @trashgod - “上面引用的参考资料。”。在哪里?
    【解决方案2】:

    这似乎是Base64InputStream 中的一个错误。你说得对。

    您应该将此情况报告给 Apache commons 编解码器项目。

    简单的测试用例:

    import java.io.*;
    import org.apache.commons.codec.binary.Base64InputStream;
    
    class tmp {
      public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream(args[0]);
        Base64InputStream b64is = new Base64InputStream(fis, true, -1, null);
    
        while (true) {
          byte[] c = new byte[1024];
          int n = b64is.read(c);
          if (n < 0) break;
          if (n == 0) throw new IOException("returned 0!");
          for (int i = 0; i < n; i++) {
            System.out.print((char)c[i]);
          }
        }
      }
    }
    

    InputStreamread(byte[]) 调用不允许返回 0。它确实在任何长度为 3 字节的倍数的文件上返回 0。

    【讨论】:

    • 是的,你是对的。这是 Base64InputStream 中的一个错误。 +1 用于确认这一点的测试用例。
    • 报告顺便说一句:issues.apache.org/jira/browse/CODEC-101 话虽如此,我仍然想知道我的测试文件确实是 3 个字节长的倍数 :o)
    • 哇,感谢您的确认,我必须说我很惊讶我发现了这样一个错误(尽管是无意的)。
    【解决方案3】:

    有趣的是,我在这里做了一些测试,当您使用InputStreamReader 读取Base64InputStream 时,它确实会抛出该异常,无论流的来源是什么,但是当您将其作为二进制流读取时它可以完美地工作。正如 Trashgod 提到的,Base64 编码是框架化的。 InputStreamReader 实际上应该在 Base64InputStream 上再次调用 flush() 以查看它是否不再返回任何数据。

    除了实现您自己的Base64InputStreamReaderBase64Reader 之外,我没有看到其他解决此问题的方法。 这实际上是一个错误,请参阅 Keith 的回答。

    作为一种解决方法,您也可以将其存储在 BLOB 中而不是数据库中的 CLOB 中,并改用 PreparedStatement#setBinaryStream()。它是否存储为二进制数据并不重要。无论如何,您都不希望有这么大的 Base64 数据可索引或可搜索。


    更新:因为这不是一个选项,并且让 Apache Commons Codec 人员修复我报告为 CODEC-101Base64InputStream 错误可能需要一些时间,您可以考虑使用另一个 3rd派对 Base64 API。我找到了一个here(公共领域,所以你可以随心所欲地使用它,甚至可以放在你自己的包中),我在这里测试过,它工作正常。

    InputStream base64 = new Base64.InputStream(input, Base64.ENCODE);
    

    更新 2:commons 编解码器的人很快就有了fixed

    Index: src/java/org/apache/commons/codec/binary/Base64InputStream.java
    ===================================================================
    --- src/java/org/apache/commons/codec/binary/Base64InputStream.java (revision 950817)
    +++ src/java/org/apache/commons/codec/binary/Base64InputStream.java (working copy)
    @@ -145,21 +145,41 @@
             } else if (len == 0) {
                 return 0;
             } else {
    -            if (!base64.hasData()) {
    -                byte[] buf = new byte[doEncode ? 4096 : 8192];
    -                int c = in.read(buf);
    -                // A little optimization to avoid System.arraycopy()
    -                // when possible.
    -                if (c > 0 && b.length == len) {
    -                    base64.setInitialBuffer(b, offset, len);
    +            int readLen = 0;
    +            /*
    +             Rationale for while-loop on (readLen == 0):
    +             -----
    +             Base64.readResults() usually returns > 0 or EOF (-1).  In the
    +             rare case where it returns 0, we just keep trying.
    +
    +             This is essentially an undocumented contract for InputStream
    +             implementors that want their code to work properly with
    +             java.io.InputStreamReader, since the latter hates it when
    +             InputStream.read(byte[]) returns a zero.  Unfortunately our
    +             readResults() call must return 0 if a large amount of the data
    +             being decoded was non-base64, so this while-loop enables proper
    +             interop with InputStreamReader for that scenario.
    +             -----
    +             This is a fix for CODEC-101
    +            */
    +            while (readLen == 0) {
    +                if (!base64.hasData()) {
    +                    byte[] buf = new byte[doEncode ? 4096 : 8192];
    +                    int c = in.read(buf);
    +                    // A little optimization to avoid System.arraycopy()
    +                    // when possible.
    +                    if (c > 0 && b.length == len) {
    +                        base64.setInitialBuffer(b, offset, len);
    +                    }
    +                    if (doEncode) {
    +                        base64.encode(buf, 0, c);
    +                    } else {
    +                        base64.decode(buf, 0, c);
    +                    }
                     }
    -                if (doEncode) {
    -                    base64.encode(buf, 0, c);
    -                } else {
    -                    base64.decode(buf, 0, c);
    -                }
    +                readLen = base64.readResults(b, offset, len);
                 }
    -            return base64.readResults(b, offset, len);
    +            return readLen;
             }
         }
    

    我在这里试过了,效果很好。

    【讨论】:

    • 很遗憾,我不能使用 BLOB,因为有时里面的数据会是文本
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-07-13
    • 2010-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-21
    • 2012-01-12
    相关资源
    最近更新 更多