Memoria insuficiente al codificar el archivo a base64

Usando Base64 desde 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) 

Estoy haciendo una pequeña aplicación para dispositivo móvil.

No puedes simplemente cargar todo el archivo en la memoria, como aquí:

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

En su lugar, cargue el fragmento de archivo por fragmento y codifíquelo en partes. Base64 es una encoding simple, es suficiente para cargar 3 bytes y codificarlos a la vez (esto producirá 4 bytes después de la encoding). Por razones de rendimiento, considere cargar múltiplos de 3 bytes, por ejemplo, 3000 bytes, debería estar bien. Considere también el archivo de entrada de almacenamiento en búfer.

Un ejemplo:

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

Tenga en cuenta que no puede simplemente anexar los resultados de Base64.encodeBase64() a la matriz bbyte encoded . En realidad, no está cargando el archivo, sino que lo está codificando en Base64 causando el problema de falta de memoria. Esto es comprensible porque la versión de Base64 es más grande (y usted ya tiene un archivo ocupando mucha memoria).

Considera cambiar tu método a:

 public void encode(File file, OutputStream base64OutputStream) 

y enviar datos codificados en Base64 directamente a base64OutputStream lugar de devolverlos.

ACTUALIZACIÓN: Gracias a @StephenC desarrollé una versión mucho más fácil:

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

Utiliza Base64OutputStream que traduce la entrada a Base64 sobre la marcha y la clase IOUtils desde Apache Commons IO .

Nota: debe cerrar FileInputStream y Base64OutputStream explícitamente para imprimir = si es necesario, pero IOUtils.copy() maneja el almacenamiento en búfer.

O el archivo es demasiado grande, o su montón es demasiado pequeño, o tiene una pérdida de memoria.

  • Si esto solo sucede con archivos realmente grandes, coloque algo en su código para verificar el tamaño del archivo y rechazar los archivos que son irracionalmente grandes.

  • Si esto sucede con archivos pequeños, aumente el tamaño de almacenamiento dynamic mediante la opción de línea de comando -Xmx cuando inicie la JVM. (Si se trata de un contenedor web u otro marco, consulte la documentación sobre cómo hacerlo).

  • Si el archivo se repite, especialmente con archivos pequeños, es probable que tenga una pérdida de memoria.


El otro punto que debe hacerse es que su enfoque actual implica la celebración de dos copias completas del archivo en la memoria. Debería poder reducir el uso de memoria, aunque normalmente necesitará un codificador Base64 basado en secuencias para hacer esto. (Depende del sabor de la encoding base64 que está utilizando …)

Esta página describe una biblioteca de codificador / decodificador Base64 basada en flujo e incluye lnks para algunas alternativas.

Bueno, no lo hagas para todo el archivo a la vez.

Base64 funciona en 3 bytes a la vez, por lo que puede leer su archivo en lotes de “múltiples de 3” bytes, codificarlos y repetir hasta que termine el archivo:

 // 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. No estás leyendo todo el archivo, solo los primeros kb. El método de read devuelve cuántos bytes se leyeron realmente. Debería llamar a read in a loop hasta que devuelva -1 para asegurarse de haber leído todo.

  2. El archivo es demasiado grande para que tanto él como su encoding base64 quepan en la memoria. Ya sea

    • procesar el archivo en pedazos más pequeños o
    • boost la memoria disponible para la JVM con el conmutador -Xmx , por ejemplo

       java -Xmx1024M YourProgram 

Este es el mejor código para cargar imágenes de más tamaño

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

Bueno, parece que su archivo es demasiado grande para mantener las múltiples copias necesarias para una encoding Base64 en memoria en la memoria de almacenamiento dynamic disponible al mismo tiempo. Dado que esto es para un dispositivo móvil, probablemente no sea posible boost el montón, por lo que tiene dos opciones:

  • hacer el archivo más pequeño (mucho más pequeño)
  • Hágalo de una manera basada en stram para que esté leyendo desde un InputStream una pequeña parte del archivo a la vez, codifíquelo y escríbalo en un OutputStream , sin tener nunca el archivo de enitre en la memoria.

En Manifiesto en la etiqueta de aplicación escriba siguiente android: largeHeap = “true”

Funcionó para mí