2010-05-30 14 views
7

Tengo algunas columnas CLOB en una base de datos en la que necesito poner archivos binarios codificados en Base64. Estos archivos pueden ser grandes, así que necesito transmitirlos, no puedo leer todo de una vezproblema usando el codificador base64 y InputStreamReader

Estoy usando org.apache.commons.codec.binary.Base64InputStream para hacer la codificación, y estoy teniendo un problema. Mi código es esencialmente este

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

preparedStatement.setCharacterStream(1, reader); 

Cuando ejecuto el código anterior, consigo uno de éstos durante la ejecución de la actualización java.io.IOException: Underlying input stream returned zero bytes, se tira de profundidad en el código InputStreamReader.

¿Por qué no funcionaría? Me parece que el reader intentaría leer de la secuencia base 64, que leería de la secuencia de archivos, y todo debería ser feliz.

Respuesta

14

Esto parece ser un error en Base64InputStream. Lo estás llamando correctamente.

Debe informar esto al proyecto de códec de Apache commons.

caso de prueba simple:

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

la read(byte[]) llamado de InputStream no se le permite volver 0. No devuelven 0 en cualquier archivo que es un múltiplo de 3 bytes de longitud.

+1

Sí, tienes razón. Este es un error en Base64InputStream. +1 para el caso de prueba que lo confirma. – BalusC

+2

Reportado por cierto: https://issues.apache.org/jira/browse/CODEC-101 Dicho esto, todavía me pregunto sobre la coincidencia de que mi archivo de prueba fue de hecho un múltiplo de 3 bytes de longitud: o) – BalusC

+1

Wow, gracias por confirmar eso, debo decir que estoy sorprendido de haber encontrado un error (sin querer). – karoberts

0

"Para mayor eficiencia, considere envolver un InputStreamReader dentro de un BufferedReader Por ejemplo:".

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

Adición: Como Base64 se rellena hasta un múltiplo de 4 caracteres, compruebe que la fuente no se trunca. Se puede requerir un flush().

+0

Quizás es más eficiente, pero no resuelve el problema – karoberts

+0

¿Hay alguna posibilidad de que la transmisión se trunque? IIRC, 'base64' está enmarcado. – trashgod

+0

Pregunta actualizada. ¿Puedes dar más detalles sobre lo que quieres decir con "base64 está enmarcado"? La transmisión proviene directamente del archivo. – karoberts

4

Interesante, hice algunas pruebas aquí y de hecho arroja esa excepción cuando lee el Base64InputStream usando un InputStreamReader, independientemente de la fuente de la secuencia, pero funciona sin problemas cuando lo lee como flujo binario. Como lo mencionó Trashgod, la codificación Base64 está enmarcada. El InputStreamReader debería haber invocado flush() en el Base64InputStream una vez más para ver si ya no devuelve más datos.

no veo otras maneras de solucionar esto que la implementación de su propia Base64InputStreamReader o Base64Reader . Esto es realmente un error, ver la respuesta de Keith.

Como solución, también puede almacenarlo en un BLOB en lugar de un CLOB en la base de datos y usar PreparedStatement#setBinaryStream() en su lugar. No importa si se almacena como datos binarios o no. No desea tener datos tan grandes de Base64 para poder indexarlos o buscarlos de todos modos.


actualización: puesto que no es una opción y tener los chicos Apache Commons Codec para solucionar el Base64InputStream error que me reportaron, como CODEC-101 podría tomar algún tiempo, se puede considerar utilizar otra tercera parte base 64 API. Encontré un here (dominio público, para que pueda hacer lo que quiera, incluso colocarlo en su propio paquete), lo probé aquí y funciona bien.

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

Actualización 2: el tipo de códec Commons tiene fixed que sea muy pronto.

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

Lo intenté aquí y funciona bien.

+0

+1 Buena solución. – trashgod

+0

Lamentablemente, no puedo usar BLOB porque a veces los datos que contiene serán texto – karoberts

+0

+1 Gracias, esa clase funcionará muy bien. – karoberts

Cuestiones relacionadas