7

我在数据库中有一些 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);

当我运行上面的代码时,我在 update 的执行过程中得到了其中一个 java.io.IOException: Underlying input stream returned zero bytes,它被扔进了 InputStreamReader 代码的深处。

为什么这行不通?在我看来,reader它将尝试从 base 64 流中读取,该流将从文件流中读取,并且一切都应该是愉快的。

4

3 回答 3

14

这似乎是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]);
      }
    }
  }
}

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

于 2010-05-30T03:34:54.603 回答
4

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

除了实施您自己的Base64InputStreamReaderBase64Reader. 这实际上是一个错误,请参阅基思的回答。

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


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

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

更新 2:commons 编解码器的人很快就修复了它。

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

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

于 2010-05-30T03:43:09.183 回答
0

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

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

附录:如Base64被填充为 4 个字符的倍数,请验证源未被截断。flush()可能需要一个。

于 2010-05-30T01:41:58.057 回答