проблема с использованием кодера base64 и InputStreamReader - PullRequest
7 голосов
/ 30 мая 2010

У меня есть несколько столбцов 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, который будет читать из потока файлов, и все должно быть счастливым.

Ответы [ 3 ]

14 голосов
/ 30 мая 2010

Это ошибка в 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. Он возвращает 0 для любого файла, кратного 3 байтам.

4 голосов
/ 30 мая 2010

Интересно, что я провел здесь несколько тестов, и это действительно выдает это исключение, когда вы читаете Base64InputStream, используя InputStreamReader, независимо от источника потока, но оно работает безупречно, когда вы читаете его как двоичный поток. Как упомянул Trashgod, кодировка Base64 оформлена. InputStreamReader на самом деле должен был бы вызвать flush() на Base64InputStream еще раз, чтобы проверить, не возвращает ли он больше данных.

Я не вижу других способов исправить это, кроме реализации ваших собственных Base64InputStreamReader или Base64Reader. На самом деле это ошибка, см. Ответ Кита.

В качестве обходного пути вы также можете просто сохранить его в BLOB вместо CLOB в БД и использовать вместо него PreparedStatement#setBinaryStream(). Неважно, хранятся ли они как двоичные данные или нет. Вы не хотите, чтобы такие большие данные Base64 были индексируемыми или доступными для поиска в любом случае.


Обновление : поскольку это не вариант, и если парни из Apache Commons Codec исправят ошибку Base64InputStream, о которой я сообщал как CODEC-101 , это может занять некоторое время, вы можете рассмотреть возможность использования другого стороннего Base64 API. Я нашел один здесь (общественное достояние, так что вы можете делать с ним все, что захотите, даже поместить в свой собственный пакет), я протестировал его здесь, и он отлично работает.

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

Обновление 2 : парень с кодеками общего пользования исправил довольно скоро.

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

Я попробовал это здесь, и оно отлично работает.

0 голосов
/ 30 мая 2010

"Для максимальной эффективности рассмотрите возможность упаковки InputStreamReader в BufferedReader. Например:"

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

Добавление: поскольку Base64 дополняется кратным 4 символам, убедитесь, что источник не обрезан. A flush() может потребоваться.

...