Подтвердить что ты не робот

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

Использование Base64 из общих ресурсов Apache

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)

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

4b9b3361

Ответ 1

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

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 array. Фактически, он не загружает файл, а кодирует его на Base64, вызывая проблему с памятью. Это понятно, потому что версия Base64 больше (и у вас уже есть файл, занимающий много памяти).

Рассмотрите возможность изменения метода:

public void encode(File file, OutputStream base64OutputStream)

и отправляя данные с кодировкой Base64 непосредственно в base64OutputStream, а не возвращая его.

UPDATE: Благодаря @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().

Ответ 2

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

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

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

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


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

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

Ответ 3

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

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

  • Вы не читаете весь файл, а только первые несколько килобайт. Метод read возвращает количество байтов, которые были фактически прочитаны. Вы должны вызвать read в цикле, пока он не вернет -1, чтобы убедиться, что вы все прочитали.

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

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

      java -Xmx1024M YourProgram
      

Ответ 5

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

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

Ответ 6

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

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

Ответ 7

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

Это сработало для меня

Ответ 8

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