我在数据库中有一些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
会尝试从基本64流中读取,这将从文件流中读取,并且一切都应该是满意的。
答案 0 :(得分: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。
答案 1 :(得分:4)
有趣的是,我在这里做了一些测试,当你使用Base64InputStream
读取InputStreamReader
时,它确实会抛出异常,无论流的来源如何,但当你把它读成二进制时它会完美无缺地运行流。正如Trashgod所提到的,Base64编码是框架式的。实际上,InputStreamReader
应该再次在flush()
上调用Base64InputStream
,以查看它是否不再返回任何数据。
我没有看到其他方法来解决这个问题,而不是实现自己的。 这实际上是一个错误,请参阅Keith的回答。 Base64InputStreamReader
或Base64Reader
作为一种解决方法,您也可以将其存储在数据库中的BLOB而不是CLOB中,并使用PreparedStatement#setBinaryStream()
代替。它是否存储为二进制数据并不重要。您不希望将这么大的Base64数据转换为可索引或可搜索的。
更新:由于这不是一个选项,让Apache Commons Codec人员修复Base64InputStream
我认为可能需要花费一些时间的InputStream base64 = new Base64.InputStream(input, Base64.ENCODE);
错误,您可以考虑使用另一个第三方Base64 API。我找到了一个CODEC-101(公共域名,所以你可以随心所欲地做任何事情,甚至放在你自己的包里),我在这里测试过它并且工作正常。
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;
}
}
更新2 :公共编解码器人很快就会here。
{{1}}
我在这里尝试过,它运行正常。
答案 2 :(得分:0)
“为了获得最高效率,请考虑在InputStreamReader
内包装BufferedReader
。例如:”
BufferedReader in = new BufferedReader(new InputStreamReader(b64is));
附录:当Base64
填充为4个字符的倍数时,请验证源是否未被截断。可能需要flush()
。