Недостаточно памяти при кодировании файла в base64 - PullRequest
15 голосов
/ 06 марта 2012

Использование Base64 от Apache Commons

public byte[] encode(File file) throws FileNotFoundException, IOException {
        byte[] encoded;
        try (FileInputStream fin = new FileInputStream(file)) {
            byte fileContent[] = new byte[(int) file.length()];
            fin.read(fileContent);
            encoded = Base64.encodeBase64(fileContent);
        }
        return encoded;   
}


Exception in thread "AWT-EventQueue-0" java.lang.OutOfMemoryError: Java heap space
    at org.apache.commons.codec.binary.BaseNCodec.encode(BaseNCodec.java:342)
    at org.apache.commons.codec.binary.Base64.encodeBase64(Base64.java:657)
    at org.apache.commons.codec.binary.Base64.encodeBase64(Base64.java:622)
    at org.apache.commons.codec.binary.Base64.encodeBase64(Base64.java:604)

Я делаю небольшое приложение для мобильных устройств.

Ответы [ 8 ]

31 голосов
/ 06 марта 2012

Вы не можете просто загрузить весь файл в память, как здесь:

byte fileContent[] = new byte[(int) file.length()];
fin.read(fileContent);

Вместо этого загрузите файл по частям и закодируйте его по частям. Base64 - это простая кодировка, достаточно загрузить 3 байта и кодировать их одновременно (это даст 4 байта после кодирования). Из соображений производительности рассмотрим загрузку, кратную 3 байтам, например, 3000 байтов - должно быть просто отлично. Также рассмотрите возможность буферизации входного файла.

Пример:

byte fileContent[] = new byte[3000];
try (FileInputStream fin = new FileInputStream(file)) {
    while(fin.read(fileContent) >= 0) {
         Base64.encodeBase64(fileContent);
    }
}

Обратите внимание, что вы не можете просто добавить результаты массива Base64.encodeBase64() в encoded байт. На самом деле, он не загружает файл, а кодирует его в Base64, вызывая проблему нехватки памяти. Это понятно, потому что версия Base64 больше (и у вас уже есть файл, занимающий много памяти).

Попробуйте изменить свой метод на:

public void encode(File file, OutputStream base64OutputStream)

и отправку данных в кодировке Base64 непосредственно в base64OutputStream вместо их возврата.

ОБНОВЛЕНИЕ: Благодаря @ StephenC я разработал гораздо более простую версию:

public void encode(File file, OutputStream base64OutputStream) {
  InputStream is = new FileInputStream(file);
  OutputStream out = new Base64OutputStream(base64OutputStream)
  IOUtils.copy(is, out);
  is.close();
  out.close();
}

Используется Base64OutputStream, который переводит ввод в Base64 на лету и IOUtils класс из Apache Commons IO .

Примечание: вы должны явно закрыть FileInputStream и Base64OutputStream, чтобы напечатать =, если требуется, но буферизация обрабатывается IOUtils.copy().

5 голосов
/ 06 марта 2012

Либо файл слишком большой, либо ваша куча слишком мала, либо у вас утечка памяти.

  • Если это происходит только с действительно большими файлами, поместите что-нибудь вкод для проверки размера файла и отклонения файлов, которые неоправданно велики.

  • Если это происходит с небольшими файлами, увеличьте размер кучи с помощью параметра командной строки -Xmx при запускеJVM.(Если это находится в веб-контейнере или какой-либо другой среде, проверьте документацию о том, как это сделать.)

  • Если файл повторяется, особенно с небольшими файлами, есть вероятность, что выпроизошла утечка памяти.


Другой момент, который следует учитывать, заключается в том, что ваш текущий подход предполагает хранение двух полных копий файла в памяти.Вы должны быть в состоянии уменьшить использование памяти, хотя для этого обычно требуется кодировщик Base64 на основе потока.(Это зависит от того, какую разновидность кодировки base64 вы используете ...)

Эта страница описывает потоковую библиотеку кодировщика / декодера Base64 и включает в себя lnks для некоторых альтернатив.

4 голосов
/ 06 марта 2012

Ну, не делайте этого для всего файла сразу.

Base64 работает с 3 байтами за раз, так что вы можете читать ваш файл в пакетах по «несколько из 3» байтов, кодировать их и повторять до тех пор, пока не закончите файл:

// the base64 encoding - acceptable estimation of encoded size
StringBuilder sb = new StringBuilder(file.length() / 3 * 4);

FileInputStream fin = null;
try {
    fin = new FileInputStream("some.file");
    // Max size of buffer
    int bSize = 3 * 512;
    // Buffer
    byte[] buf = new byte[bSize];
    // Actual size of buffer
    int len = 0;

    while((len = fin.read(buf)) != -1) {
        byte[] encoded = Base64.encodeBase64(buf);

        // Although you might want to write the encoded bytes to another 
        // stream, otherwise you'll run into the same problem again.
        sb.append(new String(buf, 0, len));
    }
} catch(IOException e) {
    if(null != fin) {
        fin.close();
    }
}

String base64EncodedFile = sb.toString();
1 голос
/ 01 августа 2015

Это лучший код для загрузки изображения большего размера

bitmap=Bitmap.createScaledBitmap(bitmap, 100, 100, true);

ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); //compress to which format you want.
byte [] byte_arr = stream.toByteArray();  
String image_str = Base64.encodeBytes(byte_arr);
1 голос
/ 06 марта 2012
  1. Вы не читаете весь файл, только первые несколько килобайт. Метод read возвращает количество прочитанных байтов. Вы должны вызывать read в цикле, пока он не вернет -1, чтобы убедиться, что вы все прочитали.

  2. Файл слишком велик для него и его кодировки base64, чтобы поместиться в памяти. Либо

    • обработать файл небольшими кусочками или
    • увеличить объем памяти, доступной для JVM, с помощью переключателя -Xmx, например,

      java -Xmx1024M YourProgram
      
0 голосов
/ 12 декабря 2018

Java 8 добавил Base64 методы, поэтому Apache Commons больше не требуется для кодирования больших файлов.

public static void encodeFileToBase64(String inputFile, String outputFile) {
    try (OutputStream out = Base64.getEncoder().wrap(new FileOutputStream(outputFile))) {
        Files.copy(Paths.get(inputFile), out);
    } catch (IOException e) {
        throw new UncheckedIOException(e);
    }
}
0 голосов
/ 29 июля 2015

В манифесте в теге applcation напишите следующее android: largeHeap = "true"

у меня сработало

0 голосов
/ 06 марта 2012

Похоже, ваш файл слишком велик, чтобы одновременно хранить несколько копий, необходимых для кодирования Base64 в памяти, в доступной памяти кучи. Учитывая, что это для мобильного устройства, вероятно, невозможно увеличить кучу, поэтому у вас есть два варианта:

  • сделать файл меньше (намного меньше)
  • Сделайте это на основе stram, чтобы вы читали из InputStream одной небольшой части файла за раз, кодировали ее и записывали в OutputStream, не сохраняя файл enitre в память.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...