it-swarm.com.ru

Недостаточно памяти при кодировании файла в base64

Использование 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)

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

14
Ivan Ivanovich

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

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 bbyte. На самом деле, он не загружает файл, а кодирует его в 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 class from Apache Commons IO .

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

29
Tomasz Nurkiewicz

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

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

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

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


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

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

5
Stephen C

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

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();
4
Sorin

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

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

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

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

      Java -Xmx1024M YourProgram
      
1
Joni

Добавлены 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
jaco0646

В манифесте в теге приложения напишите следующее Android: largeHeap = "истина" 

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

0
rajlaxmi_jagdale

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

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