¿Cómo convertir la matriz uint8 a una cadena codificada en base64?

Respuestas:

15

Todas las soluciones ya propuestas tienen graves problemas. Algunas soluciones no funcionan en matrices grandes, algunas proporcionan una salida incorrecta, algunas arrojan un error en la llamada btoa si una cadena intermedia contiene caracteres de varios bytes, algunas consumen más memoria de la necesaria.

Así que implementé una función de conversión directa que simplemente funciona independientemente de la entrada. Convierte alrededor de 5 millones de bytes por segundo en mi máquina.

https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727

Egor Nepomnyaschih
fuente
¿Tener base64abc como una matriz de cadenas es más rápido que simplemente convertirlo en una cadena? "ABCDEFG..."?
Garr Godfrey
161

Si sus datos pueden contener secuencias de varios bytes (no una secuencia ASCII simple) y su navegador tiene TextDecoder , entonces debe usar eso para decodificar sus datos (especifique la codificación requerida para TextDecoder):

var u8 = new Uint8Array([65, 66, 67, 68]);
var decoder = new TextDecoder('utf8');
var b64encoded = btoa(decoder.decode(u8));

Si necesita admitir navegadores que no tienen TextDecoder (actualmente solo IE y Edge), entonces la mejor opción es usar un polyfill TextDecoder .

Si sus datos contienen ASCII simple (no Unicode / UTF-8 multibyte), existe una alternativa simple String.fromCharCodeque debería ser compatible de forma bastante universal:

var ascii = new Uint8Array([65, 66, 67, 68]);
var b64encoded = btoa(String.fromCharCode.apply(null, ascii));

Y para decodificar la cadena base64 de nuevo a un Uint8Array:

var u8_2 = new Uint8Array(atob(b64encoded).split("").map(function(c) {
    return c.charCodeAt(0); }));

Si tiene búferes de matriz muy grandes, la aplicación puede fallar y es posible que deba fragmentar el búfer (según el publicado por @RohitSengar). Nuevamente, tenga en cuenta que esto solo es correcto si su búfer solo contiene caracteres ASCII que no son multibyte:

function Uint8ToString(u8a){
  var CHUNK_SZ = 0x8000;
  var c = [];
  for (var i=0; i < u8a.length; i+=CHUNK_SZ) {
    c.push(String.fromCharCode.apply(null, u8a.subarray(i, i+CHUNK_SZ)));
  }
  return c.join("");
}
// Usage
var u8 = new Uint8Array([65, 66, 67, 68]);
var b64encoded = btoa(Uint8ToString(u8));
canaca
fuente
4
Esto me funciona en Firefox, pero Chrome se ahoga con "Error de rango no detectado: se excedió el tamaño máximo de la pila de llamadas" (haciendo el btoa).
Michael Paulukonis
3
@MichaelPaulukonis mi conjetura es que en realidad es String.fromCharCode.apply lo que está causando que se exceda el tamaño de la pila. Si tiene un Uint8Array muy grande, probablemente necesitará construir iterativamente la cadena en lugar de usar la aplicación para hacerlo. La llamada apply () está pasando cada elemento de su matriz como parámetro a fromCharCode, por lo que si la matriz tiene 128000 bytes de longitud, entonces estaría intentando realizar una llamada de función con 128000 parámetros que probablemente volarán la pila.
kanaka
4
Gracias. Todo lo que necesitaba erabtoa(String.fromCharCode.apply(null, myArray))
Glen Little
29
Esto no funciona si la matriz de bytes no es Unicode válido.
Melab
11
No hay caracteres multibyte en una cadena base64 o en formato Uint8Array. TextDecoderes absolutamente incorrecto usar aquí, porque si Uint8Arraytiene bytes en el rango 128..255, el decodificador de texto los convertirá erróneamente en caracteres Unicode, lo que romperá el convertidor base64.
riv
26

¡Solución muy simple y prueba para JavaScript!

ToBase64 = function (u8) {
    return btoa(String.fromCharCode.apply(null, u8));
}

FromBase64 = function (str) {
    return atob(str).split('').map(function (c) { return c.charCodeAt(0); });
}

var u8 = new Uint8Array(256);
for (var i = 0; i < 256; i++)
    u8[i] = i;

var b64 = ToBase64(u8);
console.debug(b64);
console.debug(FromBase64(b64));
impactro
fuente
4
¡La solución más limpia!
realappie
Solución perfecta
Haris ur Rehman
2
falla en datos grandes (como imágenes) conRangeError: Maximum call stack size exceeded
Maxim Khokhryakov
18
function Uint8ToBase64(u8Arr){
  var CHUNK_SIZE = 0x8000; //arbitrary number
  var index = 0;
  var length = u8Arr.length;
  var result = '';
  var slice;
  while (index < length) {
    slice = u8Arr.subarray(index, Math.min(index + CHUNK_SIZE, length)); 
    result += String.fromCharCode.apply(null, slice);
    index += CHUNK_SIZE;
  }
  return btoa(result);
}

Puede utilizar esta función si tiene un Uint8Array muy grande. Esto es para Javascript, puede ser útil en el caso de FileReader readAsArrayBuffer.

Rohit Singh Sengar
fuente
2
Curiosamente, en Chrome cronometré esto en un búfer de más de 300 kb y descubrí que hacerlo en trozos como si fuera un poco más lento que hacerlo byte a byte. Esto me sorprendió.
Matt
@ Matt interesante. Es posible que, mientras tanto, Chrome haya detectado esta conversión y tenga una optimización específica para ella y fragmentar los datos puede reducir su eficiencia.
kanaka
2
Esto no es seguro, ¿verdad? Si el límite de mi fragmento atraviesa un carácter codificado en UTF8 de varios bytes, entonces fromCharCode () no podría crear caracteres sensibles a partir de los bytes en ambos lados del límite, ¿verdad?
Jens
2
Los String.fromCharCode.apply()métodos @Jens no pueden reproducir UTF-8: los caracteres UTF-8 pueden variar en longitud de un byte a cuatro bytes, sin embargo, String.fromCharCode.apply()examina un UInt8Array en segmentos de UInt8, por lo que asume erróneamente que cada carácter tiene exactamente un byte de largo e independiente del vecino unos. Si todos los caracteres codificados en la entrada UInt8Array están en el rango ASCII (de un solo byte), funcionará por casualidad, pero no puede reproducir UTF-8 completo. Necesita TextDecoder o un algoritmo similar para eso.
Jamie Birch
1
@Jens, ¿qué caracteres codificados en UTF8 multibyte en una matriz de datos binarios? No estamos tratando con cadenas Unicode aquí, sino con datos binarios arbitrarios, que NO deben tratarse como puntos de código utf-8.
riv
15

Si está utilizando Node.js, puede usar este código para convertir Uint8Array a base64

var b64 = Buffer.from(u8).toString('base64');
Fiach Reid
fuente
4
Esta es una mejor respuesta que las funciones enrolladas a mano anteriores en términos de rendimiento.
Ben Liyanage
2
¡Increíble! Gracias. La mejor respuesta de la historia
Alan
2
¡¡Perfecto!! ¡Esta será la respuesta aceptada!
m4l490n
1
Esta es la respuesta correcta
Pablo Yabo
0

Aquí hay una función JS para esto:

Esta función es necesaria porque Chrome no acepta una cadena codificada en base64 como valor para applicationServerKey en pushManager.subscribe todavía https://bugs.chromium.org/p/chromium/issues/detail?id=802280

function urlBase64ToUint8Array(base64String) {
  var padding = '='.repeat((4 - base64String.length % 4) % 4);
  var base64 = (base64String + padding)
    .replace(/\-/g, '+')
    .replace(/_/g, '/');

  var rawData = window.atob(base64);
  var outputArray = new Uint8Array(rawData.length);

  for (var i = 0; i < rawData.length; ++i) {
    outputArray[i] = rawData.charCodeAt(i);
  }
  return outputArray;
}
lucss
fuente
3
Esto convierte base64 en Uint8Array. Pero la pregunta es cómo convertir Uint8Array a base64
Barry Michael Doyle
0

Pure JS - sin cadena middlestep (sin btoa)

En la siguiente solución, omito la conversión a cadena. IDEA está siguiendo:

  • une 3 bytes (3 elementos de matriz) y obtienes 24 bits
  • dividir 24 bits en cuatro números de 6 bits (que toman valores de 0 a 63)
  • usa esos números como índice en el alfabeto base64
  • caso de esquina: cuando la matriz de bytes de entrada, la longitud no se divide por 3, luego agregue =o ==al resultado

La siguiente solución funciona en fragmentos de 3 bytes, por lo que es buena para matrices grandes. Una solución similar para convertir base64 a una matriz binaria (sin atob) está AQUÍ

Kamil Kiełczewski
fuente
Me gusta la compacidad, pero convertir a cadenas que representan un número binario y luego volver es mucho más lento que la solución aceptada.
Garr Godfrey
0

Utilice lo siguiente para convertir la matriz uint8 en una cadena codificada en base64

function arrayBufferToBase64(buffer) {
            var binary = '';
            var bytes = [].slice.call(new Uint8Array(buffer));
            bytes.forEach((b) => binary += String.fromCharCode(b));
            return window.btoa(binary);
        };
KARTHIKEYAN.A
fuente
-1

En el sitio web de Mozilla Developer Network se muestra un enfoque muy bueno para esto :

function btoaUTF16 (sString) {
    var aUTF16CodeUnits = new Uint16Array(sString.length);
    Array.prototype.forEach.call(aUTF16CodeUnits, function (el, idx, arr) { arr[idx] = sString.charCodeAt(idx); });
    return btoa(String.fromCharCode.apply(null, new Uint8Array(aUTF16CodeUnits.buffer)));
}

function atobUTF16 (sBase64) {
    var sBinaryString = atob(sBase64), aBinaryView = new Uint8Array(sBinaryString.length);
    Array.prototype.forEach.call(aBinaryView, function (el, idx, arr) { arr[idx] = sBinaryString.charCodeAt(idx); });
    return String.fromCharCode.apply(null, new Uint16Array(aBinaryView.buffer));
}

var myString = "☸☹☺☻☼☾☿";

var sUTF16Base64 = btoaUTF16(myString);
console.log(sUTF16Base64);    // Shows "OCY5JjomOyY8Jj4mPyY="

var sDecodedString = atobUTF16(sUTF16Base64);
console.log(sDecodedString);  // Shows "☸☹☺☻☼☾☿"

Rosberg Linhares
fuente
-3

Si todo lo que desea es una implementación JS de un codificador base64, para poder enviar datos de vuelta, puede probar la btoafunción.

b64enc = btoa(uint);

Un par de notas rápidas sobre btoa: no es estándar, por lo que los navegadores no están obligados a admitirlo. Sin embargo, la mayoría de los navegadores lo hacen. Los grandes, al menos. atobes la conversión opuesta.

Si necesita una implementación diferente, o encuentra un caso de borde en el que el navegador no tiene idea de lo que está hablando, buscar un codificador base64 para JS no sería demasiado difícil.

Creo que hay 3 de ellos en el sitio web de mi empresa, por alguna razón ...

Norguard
fuente
Gracias, no lo probé antes.
Caio Keto
10
Un par de notas. btoa y atob son en realidad parte del proceso de estandarización de HTML5 y la mayoría de los navegadores ya los admiten casi de la misma manera. En segundo lugar, btoa y atob funcionan solo con cadenas. La ejecución de btoa en Uint8Array primero convertirá el búfer en una cadena usando toString (). Esto da como resultado la cadena "[objeto Uint8Array]". Probablemente eso no sea lo que se pretende.
kanaka
1
@CaioKeto, es posible que desee considerar cambiar la respuesta seleccionada. Esta respuesta no es correcta.
Kanaka
-4

npm install google-closures-library --save

require("google-closure-library");
goog.require('goog.crypt.base64');

var result =goog.crypt.base64.encodeByteArray(Uint8Array.of(1,83,27,99,102,66));
console.log(result);

$node index.jsescribiría AVMbY2Y = en la consola.

mancini0
fuente
1
Es curioso que -vese acepte una respuesta votada en lugar de una muy alta +ve.
Vishnudev