La carga de Android a BASE64 solicita java.lang.OutOfMemoryError
También me he encontrado con este problema recientemente, pero no he encontrado ninguna solución directa relevante y efectiva en línea.
Más tarde vi un artículo que explicaba el principio de codificación base64 y lo resolví después de investigar un poco.
Generalmente, cuando se encuentra este problema, se trata del problema de "carga de archivos grandes". Además de la posibilidad de OOM durante la codificación base64 durante el proceso de "carga de archivos grandes", en realidad existen otros problemas. aunque no se mencionaron en la pregunta, tal vez porque este problema aún no se ha resuelto, por lo que todavía no he encontrado otros problemas, por lo que resolveré este problema en torno a la "carga de archivos grandes".
(Podéis ver claramente el turno de preguntas más abajo)
——————————————————————
Hacer Durante el proyecto, encontré un requisito:
En el cliente Java, use blogs.com/luguo3000/p/3940197.html" target="_blank">blogs.com/luguo3000/ p/3940197.html
Supongamos que hay un archivo A (archivo txt, que contiene contenido de texto "ABCDEFG"), después de convertirlo a InputStream->byte[]
Sus códigos ASIIC corresponden a 65, 66, 67, 68, 69, 70, 71
La representación binaria es:
1000001 1000010 1000011 1000100 1000101 1000110 1000111
Agregue ceros a los bits altos Después:
01000001 01000010 01000011 01000100 01000101 01000110 01000111
Representación real en memoria:
01000001010000100100001101000 1000 10001010100011001000111
Para la codificación base64, utilice Los caracteres incluyen (A-Z, a-z, 0-9, +, /, =) estos caracteres legibles normales. La razón y el propósito de utilizar la codificación base64 es convertir algunos caracteres confusos y ilegibles en caracteres legibles normales. p>
(Debido a que el protocolo de comunicación subyacente de Java u otros protocolos de comunicación se utiliza en comunicaciones remotas en muchos lugares, no admite la transmisión de algunos caracteres confusos, por lo que es necesario convertir los caracteres confusos. en caracteres legibles normales para la transmisión)
Por ejemplo, para el carácter 'kan', el conjunto de codificación de algunos protocolos de transmisión no lo reconoce, por lo que no se puede transmitir directamente y se debe transcodificar en base64.
p>
El UTF del valor codificado 'kan' -8 es 30681 y la representación binaria es 111011111011001->(0)111011111011001
Se necesitan dos bytes para almacenar 01110111 11011001
codificación base64 únicamente (A-Z, a-z, 0 -9, +, /, =). Debe enfatizarse que en la especificación de codificación base64, el carácter 'A' no es igual a 65, ni 'B' es 66. .. ) es el siguiente:
En otras palabras, el carácter normal 'A'=65=01000001; y el carácter base64 'A'=0=00000000;
El binario representado por el carácter base64. El valor no puede representar directamente el carácter 'kan', porque el rango de valores del carácter base64 está entre 0~63 (el valor binario está entre (00)000000~(00)111111).
¿Cómo representar 01110111 11011001 a través de los valores entre (00)000000~(00)111111?
Este es el algoritmo de codificación de base64
El valor binario de un carácter base64 está entre (00)000000~(00)111111, lo que significa que puede representar entre 000000~111111 A número binario entre, los dígitos efectivos de un carácter base64 son los últimos 6 dígitos.
¿Cómo representar bytes regulares en 8 bits mediante caracteres base64 en 6 bits?
El mínimo común múltiplo de 6 y 8 es 24, es decir, cada 4 caracteres base64 pueden representar 3 bytes normales;
Vuelva al archivo A hace un momento, el proceso de codificación :
(Archivo inicial A)->"ABCDEFG"
(Convertir a código UTF-8 int)->65 66 67 68 69 70 71
("ABCDEFG" Representación binaria; 7 bytes)->1000001 1000010 1000011 1000100 1000101 1000110 1000111
(relleno de ceros de bit alto)->01000001 01000010 01000011 01000100 100010 1 01000110 01000111
( Escritura continua)->01000001010000100100001101000100010001010100011001000111
(Divida todos los bits en unidades de 6 bits; obtenga 10 bytes)->010000 010100 001001 000011 010001 000100 010101 000110 010001 11
(6 bits*4=8 bits * La relación correspondiente de 3 se divide nuevamente; se obtienen 3 grupos de 6*4 bytes)->(010000 010100 001001 000011) (010001 000100 010101 000110) (010001 11)
(El bit alto se llena con 2 ceros; el último Los bits bajos también se rellenan con ceros)->(00010000 00010100 00001001 00000011) (00010001 00000100 00010101 00000110) (00010001 00110000)
(Valor binario convertido a decimal)->( 16 20 9 3) (17 4 21 6 ) (17 48)
(De acuerdo con la tabla de correspondencia valor-carácter codificada en base64, se obtiene el carácter base64 correspondiente al valor decimal anterior)->( Q U J D) (R E V G) (R w)
(Cada grupo de caracteres base64 debe ser 4 y la posición en blanco se llena con caracteres '=')->(Q U J D) (R E V G) (R w = =)
(La conversión final del archivo A Code resulta)->QUJDREVGRw==
Aquí usamos un archivo de texto como demostración, porque el archivo de texto es automático. legible y legible por humanos, en situaciones reales, el archivo de destino para la transcodificación a menudo no es un archivo de texto, por lo que no se puede expresar en forma de una cadena legible, sino que se expresará directamente en formato binario
La razón del aumento de tamaño es que 3 bytes = 24 bits = se dividen en 4 bits de 6 bits, y los 4 bits de 6 bits de alto son Después del relleno con ceros, se obtienen 4 bytes
Es decir, 3 bits regulares bytes generará 4 bytes base64 después de la codificación base64. Esto significa que el tamaño del archivo aumentará en 1/ después de la transcodificación base64.
——————————
.Se explica el principio de codificación base64, veamos ahora la codificación segmentada
ByteArrayOutputStream os1 = new ByteArrayOutputStream();
InputStream file1 = new FileInputStream(path);
byte[] buf1 = nuevo byte[1024];
int cuenta1;
mientras((cuenta1 = archivo1.read(buf1)) != -1 )
{
os1.escribir(
Base64.encodeBase64(buf1), 0, count1); // Puede encontrar un problema: después de la codificación Base64.encodeBase64(buf1), el volumen aumentará en 1/3, por lo que aquí está la longitud real después de Base64.encodeBase64(buf1). ) conversión de codificación No es igual a count1, por lo que la cantidad de caracteres base64 realmente escritos en os1 es solo 3/4 de la cantidad de caracteres generados por la codificación Base64.encodeBase64 (buf1)
os1.flush( );
}
file1.close();
System.out.println(os1.toString());
Después de la modificación:
ByteArrayOutputStream os1 = nuevo ByteArrayOutputStream();
InputStream file1 = nuevo FileInputStream(ruta);
byte[] byteBuf = nuevo byte[1024 ];
byte[] base64ByteBuf;
while(file1.read(byteBuf) != -1)
{
base64ByteBuf = Base64.encodeBase64(byteBuf );
os1.write(base64ByteBuf, 0, base64ByteBuf.length);
os1.flush();
}
file1.close();
System.out.println(os1.toString());
——————————
Después de la modificación, descubrí que el resultado de la codificación segmentada ha cambiado y es diferente al anterior
Pero aún no es el resultado correcto
La razón es que la unidad básica de caracteres base64 es (6 bits* 4=4 bytes), y 3 bytes regulares (8 bits*3) pueden producir exactamente 4 bytes base64
La razón fundamental es que si el número de bytes regulares para la codificación no es múltiplo de 3. Al final, quedarán 1 o 2 bytes y el resultado de la codificación de estos 1 o 2 bytes producirá el carácter '=";
Cuando se utiliza 1024 como el búfer de codificación segmentada, el resultado de la codificación es 3+3+3+...+1
Es decir, quedará 1 byte cada vez
Cuando la codificación segmentada no es utilizado, cuando se codifica el byte 1024. Cuando, el 1 byte "restante" será continuo con los siguientes bytes y no se generará el carácter '="
(Cuando base64 codifica un carácter de byte, no nunca habrá un carácter '=" en el medio del carácter =', porque solo pueden quedar 1 o 2 bytes al final, por lo que al codificar un carácter de byte, se pueden generar 1 o 2 caracteres de finalización '=" solo al final )
——————————
La solución es utilizar un múltiplo común de 3 como tamaño del búfer
Después de la modificación:
ByteArrayOutputStream os1 = nuevo ByteArrayOutputStream();
InputStream file1 = nuevo FileInputStream(ruta);
byte[] byteBuf = nuevo byte[3*1000];
byte[ ] base64ByteBuf;
while(file1.read(byteBuf) != -1)
{
base64Byt
eBuf = Base64.encodeBase64(byteBuf);
os1.write(base64ByteBuf, 0, base64ByteBuf.length);
os1.flush();
}
file1.close();
System.out.println(os1.toString());
Los resultados de la prueba han cambiado nuevamente
Ya no hay un carácter '=" en el medio, porque cada vez que la sección central se codifica con 3 bytes y 3 bytes, no quedan bytes adicionales
Después de la comparación, se encontró que el resultado de la sección intermedia ha sido Es normal
——————————
Sin embargo, se descubrió que los dos resultados de transcodificación al final son ligeramente diferente
La razón es que, suponiendo que la longitud del archivo A es de 3001 bytes;
Durante la lectura del segundo ciclo, solo se lee 1 byte válido y los 2999 bytes restantes de byteBuf son bytes no válidos, pero al codificar en este momento, los 2999 bytes no válidos adicionales también se codifican
(Esto no sucederá si se trata de una transcodificación no segmentada)
Solución:
ByteArrayOutputStream os1 = nuevo ByteArrayOutputStream();
InputStream file1 = nuevo FileInputStream(ruta);
byte[] byteBuf = nuevo byte[3 *1000];
byte[] base64ByteBuf;
int count1; //El número de bytes válidos leídos del archivo cada vez
while( (count1=file1.read (byteBuf)) != -1)
{
if(count1!=byteBuf.length) //Si el número de bytes válidos no es 3* 1000, significa que el archivo se ha leído hasta el final y no hay suficiente para llenar el byteBuf.
{
byte[] copy = Arrays.copyOf(byteBuf, count1); Desde byteBuf Intercepta el segmento de bytes que contiene el número de bytes válidos
base64ByteBuf = Base64.encodeBase64(copy); //Codifica el segmento de bytes válido
}
else
{
base64ByteBuf = Base64.encodeBase64(byteBuf);
}
os1.write(base64ByteBuf, 0, base64ByteBuf. longitud);
os1.flush();
}
file1.close();
System.out.println( os1.toString());
En este punto, la codificación segmentada base64 está completa. El código central para cargar archivos grandes está completo.
De hecho, es muy sencillo cambiar el código, pero si no conoces el motivo y el principio, no puedes hacer algo de la nada.
Para mí Originalmente solo quería responderlo de manera casual, pero no esperaba responderlo. En el proceso, descubrí que había muchos peligros que no había descubierto. En el proceso de respuesta, también mejoré los errores que no entendí y no encontré. Sin mencionar que tienes que llegar al fondo de cada punto de conocimiento que encuentres, pero en el desarrollo real, cada problema práctico que puedas encontrar personalmente es una excelente oportunidad para entrenarte. Esta oportunidad de tocar y resolver problemas a corta distancia es rara. .
Aunque hay muchos otros problemas en desarrollo que también son importantes, no puedes comentarlos a menos que los hayas encontrado tú mismo. Por lo tanto, si encuentra un problema durante el desarrollo, se recomienda determinar aproximadamente la causa.
Después de aclarar el principio, incluso si aparecen otras "variantes" de este problema en el futuro, puede encontrar la causa y resolverla usted mismo, pero simplemente pegar y copiar no puede lograrlo.