Uint8Array in stringa in Javascript


122

Ho alcuni dati codificati UTF-8 che vivono in una gamma di elementi Uint8Array in Javascript. Esiste un modo efficiente per decodificarli in una normale stringa javascript (credo che Javascript utilizzi Unicode a 16 bit)? Non voglio aggiungere un carattere alla volta poiché la concaternazione di stringhe diventerebbe ad alta intensità di CPU.


Non sono sicuro che funzionerà, ma uso u8array.toString()durante la lettura di file da BrowserFS che espongono l'oggetto Uint8Array quando chiami fs.readFile.
jcubic

1
@jcubic per me, toStringsu Uint8Arrayrestituisce numeri separati da virgole come "91,50,48,49,57,45"(Chrome 79)
kolen

Risposte:


171

TextEncodere TextDecoderdallo standard Encoding , che è polyfilled dalla libreria stringencoding , converte tra stringhe e ArrayBuffer:

var uint8array = new TextEncoder("utf-8").encode("¢");
var string = new TextDecoder("utf-8").decode(uint8array);

40
Per chiunque pigri come me, npm install text-encoding, var textEncoding = require('text-encoding'); var TextDecoder = textEncoding.TextDecoder;. No grazie.
Evan Hu,

16
attenzione alla libreria di codifica del testo npm, l'analizzatore di bundle webpack mostra che la libreria è ENORME
wayofthefuture

3
I browser @VincentScheib hanno rimosso il supporto per qualsiasi altro formato eccetto utf-8. Quindi, l' TextEncoderargomento non è necessario!
tripulse

1
nodejs.org/api/string_decoder.html dall'esempio: const {StringDecoder} = require ('string_decoder'); const decoder = new StringDecoder ('utf8'); const cent = Buffer.from ([0xC2, 0xA2]); console.log (decoder.write (cento));
curista

4
Tieni presente che Node.js ha aggiunto le API TextEncoder/ TextDecodernella v11, quindi non è necessario installare alcun pacchetto aggiuntivo se scegli come target solo le versioni correnti di Node.
Loilo

42

Questo dovrebbe funzionare:

// http://www.onicos.com/staff/iz/amuse/javascript/expert/utf.txt

/* utf.js - UTF-8 <=> UTF-16 convertion
 *
 * Copyright (C) 1999 Masanao Izumo <iz@onicos.co.jp>
 * Version: 1.0
 * LastModified: Dec 25 1999
 * This library is free.  You can redistribute it and/or modify it.
 */

function Utf8ArrayToStr(array) {
    var out, i, len, c;
    var char2, char3;

    out = "";
    len = array.length;
    i = 0;
    while(i < len) {
    c = array[i++];
    switch(c >> 4)
    { 
      case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
        // 0xxxxxxx
        out += String.fromCharCode(c);
        break;
      case 12: case 13:
        // 110x xxxx   10xx xxxx
        char2 = array[i++];
        out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
        break;
      case 14:
        // 1110 xxxx  10xx xxxx  10xx xxxx
        char2 = array[i++];
        char3 = array[i++];
        out += String.fromCharCode(((c & 0x0F) << 12) |
                       ((char2 & 0x3F) << 6) |
                       ((char3 & 0x3F) << 0));
        break;
    }
    }

    return out;
}

È un po 'più pulito come le altre soluzioni perché non utilizza alcun hack né dipende dalle funzioni del browser JS, ad esempio funziona anche in altri ambienti JS.

Guarda la demo di JSFiddle .

Vedi anche le domande correlate: qui e qui


6
Sembra un po 'lento. Ma l'unico frammento nell'universo che ho trovato che funziona. Buona scoperta + adozione!
Redsandro

6
Non capisco perché questo non abbia più voti positivi. Sembra estremamente sensato passare alla convenzione UTF-8 per piccoli snippet. Async Blob + Filereader funziona alla grande per testi di grandi dimensioni come altri hanno indicato.
DanHorner

2
La domanda era come farlo senza concatenazione di stringhe
Jack Wester

5
Funziona alla grande, tranne che non gestisce sequenze di 4+ byte, ad esempio fromUTF8Array([240,159,154,133])risulta vuoto (mentre fromUTF8Array([226,152,131])→"☃")
unhammer

1
Perché i casi 8, 9, 10 e 11 sono esclusi? Qualche motivo particolare? E anche il caso 15 è possibile, giusto? 15 (1111) indicherà che vengono utilizzati 4 byte, non è vero?
RaR

31

Ecco cosa utilizzo:

var str = String.fromCharCode.apply(null, uint8Arr);

7
Dal documento , questo non sembra decodificare UTF8.
Albert

29
Questo genererà RangeErrortesti più grandi. "Dimensione massima dello stack di chiamate superata"
Redsandro

1
Se si sta convertendo grandi Uint8Arrays in stringhe binarie e stanno ottenendo RangeError, vedere la funzione Uint8ToString da stackoverflow.com/a/12713326/471341 .
yonran

IE 11 genera SCRIPT28: Out of stack spacequando lo inserisco 300 + k caratteri, o RangeErrorper Chrome 39. Firefox 33 è ok. 100 + k va bene con tutti e tre.
Sheepy

Questo non produce il risultato corretto dai caratteri Unicode di esempio su en.wikipedia.org/wiki/UTF-8 . es. String.fromCharCode.apply (null, new Uint8Array ([0xc2, 0xa2])) non produce ¢.
Vincent Scheib

16

Trovato in una delle applicazioni di esempio di Chrome, anche se questo è pensato per blocchi di dati più grandi in cui stai bene con una conversione asincrona.

/**
 * Converts an array buffer to a string
 *
 * @private
 * @param {ArrayBuffer} buf The buffer to convert
 * @param {Function} callback The function to call when conversion is complete
 */
function _arrayBufferToString(buf, callback) {
  var bb = new Blob([new Uint8Array(buf)]);
  var f = new FileReader();
  f.onload = function(e) {
    callback(e.target.result);
  };
  f.readAsText(bb);
}

2
Come hai detto, questo funzionerebbe terribilmente a meno che il buffer da convertire non sia davvero enorme. La conversione sincrona da UTF-8 a wchar di una stringa semplice (diciamo 10-40 byte) implementata, ad esempio, in V8 dovrebbe essere molto inferiore a un microsecondo, mentre immagino che il tuo codice richiederebbe centinaia di volte. Grazie lo stesso.
Jack Wester

15

In Node "le Bufferistanze sono anche Uint8Arrayistanze ", quindi buf.toString()funziona in questo caso.


Funziona alla grande per me. E così semplice! Ma in realtà Uint8Array ha il metodo toString ().
Doom

Semplice ed elegante, non era a conoscenza Bufferè anche Uint8Array. Grazie!
LeOn - Han Li

1
@doom Sul lato browser, Uint8Array.toString () non compilerà una stringa utf-8, ma elencherà i valori numerici nell'array. Quindi, se quello che hai è un Uint8Array da un'altra fonte che non è anche un Buffer, dovrai crearne uno per fare la magia:Buffer.from(uint8array).toString('utf-8')
Joachim Lous

12

La soluzione fornita da Albert funziona bene fintanto che la funzione fornita viene invocata di rado e viene utilizzata solo per array di dimensioni modeste, altrimenti è egregiamente inefficiente. Ecco una soluzione JavaScript vanilla migliorata che funziona sia per Node che per i browser e presenta i seguenti vantaggi:

• Funziona in modo efficiente per tutte le dimensioni di array di ottetti

• Non genera stringhe intermedie usa e getta

• Supporta caratteri a 4 byte sui moderni motori JS (altrimenti "?" Viene sostituito)

var utf8ArrayToStr = (function () {
    var charCache = new Array(128);  // Preallocate the cache for the common single byte chars
    var charFromCodePt = String.fromCodePoint || String.fromCharCode;
    var result = [];

    return function (array) {
        var codePt, byte1;
        var buffLen = array.length;

        result.length = 0;

        for (var i = 0; i < buffLen;) {
            byte1 = array[i++];

            if (byte1 <= 0x7F) {
                codePt = byte1;
            } else if (byte1 <= 0xDF) {
                codePt = ((byte1 & 0x1F) << 6) | (array[i++] & 0x3F);
            } else if (byte1 <= 0xEF) {
                codePt = ((byte1 & 0x0F) << 12) | ((array[i++] & 0x3F) << 6) | (array[i++] & 0x3F);
            } else if (String.fromCodePoint) {
                codePt = ((byte1 & 0x07) << 18) | ((array[i++] & 0x3F) << 12) | ((array[i++] & 0x3F) << 6) | (array[i++] & 0x3F);
            } else {
                codePt = 63;    // Cannot convert four byte code points, so use "?" instead
                i += 3;
            }

            result.push(charCache[codePt] || (charCache[codePt] = charFromCodePt(codePt)));
        }

        return result.join('');
    };
})();

2
La migliore soluzione qui, poiché gestisce anche caratteri a 4 byte (ad es. Emoji) Grazie!
Fiffy

1
e qual è il contrario di questo?
simbo1905

6

Fai quello che ha detto @Sudhir, quindi per ottenere una stringa dall'elenco di numeri separati da virgole usa:

for (var i=0; i<unitArr.byteLength; i++) {
            myString += String.fromCharCode(unitArr[i])
        }

Questo ti darà la stringa che desideri, se è ancora rilevante


Mi spiace, non ho notato l'ultima frase in cui hai detto che non vuoi aggiungere un personaggio alla volta. Spero che questo aiuti gli altri che non hanno problemi con l'utilizzo della CPU.
shuki

14
Questo non esegue la decodifica UTF8.
Albert

Ancora più breve: String.fromCharCode.apply(null, unitArr);. Come accennato, non gestisce la codifica UTF8, ma a volte è abbastanza semplice se hai solo bisogno del supporto ASCII ma non hai accesso a TextEncoder / TextDecoder.
Ravenstine

La risposta menziona un @Sudhir ma ho cercato nella pagina e ho trovato ora tale risposta. Quindi sarebbe meglio in linea tutto ciò che ha detto
Joakim

Ciò avrà prestazioni terribili su corde più lunghe. Non utilizzare l'operatore + sulle stringhe.
Max

3

Se non puoi utilizzare l' API TextDecoder perché non è supportata su IE :

  1. È possibile utilizzare il polyfill FastestSmallestTextEncoderDecoder consigliato dal sito Web di Mozilla Developer Network ;
  2. È possibile utilizzare questa funzione fornita anche sul sito Web di MDN :

function utf8ArrayToString(aBytes) {
    var sView = "";
    
    for (var nPart, nLen = aBytes.length, nIdx = 0; nIdx < nLen; nIdx++) {
        nPart = aBytes[nIdx];
        
        sView += String.fromCharCode(
            nPart > 251 && nPart < 254 && nIdx + 5 < nLen ? /* six bytes */
                /* (nPart - 252 << 30) may be not so safe in ECMAScript! So...: */
                (nPart - 252) * 1073741824 + (aBytes[++nIdx] - 128 << 24) + (aBytes[++nIdx] - 128 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128
            : nPart > 247 && nPart < 252 && nIdx + 4 < nLen ? /* five bytes */
                (nPart - 248 << 24) + (aBytes[++nIdx] - 128 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128
            : nPart > 239 && nPart < 248 && nIdx + 3 < nLen ? /* four bytes */
                (nPart - 240 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128
            : nPart > 223 && nPart < 240 && nIdx + 2 < nLen ? /* three bytes */
                (nPart - 224 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128
            : nPart > 191 && nPart < 224 && nIdx + 1 < nLen ? /* two bytes */
                (nPart - 192 << 6) + aBytes[++nIdx] - 128
            : /* nPart < 127 ? */ /* one byte */
                nPart
        );
    }
    
    return sView;
}

let str = utf8ArrayToString([50,72,226,130,130,32,43,32,79,226,130,130,32,226,135,140,32,50,72,226,130,130,79]);

// Must show 2H₂ + O₂ ⇌ 2H₂O
console.log(str);


2

Prova queste funzioni,

var JsonToArray = function(json)
{
    var str = JSON.stringify(json, null, 0);
    var ret = new Uint8Array(str.length);
    for (var i = 0; i < str.length; i++) {
        ret[i] = str.charCodeAt(i);
    }
    return ret
};

var binArrayToJson = function(binArray)
{
    var str = "";
    for (var i = 0; i < binArray.length; i++) {
        str += String.fromCharCode(parseInt(binArray[i]));
    }
    return JSON.parse(str)
}

fonte: https://gist.github.com/tomfa/706d10fed78c497731ac , complimenti a Tomfa


2

Ero frustrato nel vedere che le persone non mostravano come andare in entrambe le direzioni o mostravano che le cose non funzionano su stringhe UTF8 banali. Ho trovato un post su codereview.stackexchange.com che contiene del codice che funziona bene. L'ho usato per trasformare antiche rune in byte, per testare alcuni crypo sui byte, quindi riconvertire le cose in una stringa. Il codice funzionante è su GitHub qui . Ho rinominato i metodi per chiarezza:

// https://codereview.stackexchange.com/a/3589/75693
function bytesToSring(bytes) {
    var chars = [];
    for(var i = 0, n = bytes.length; i < n;) {
        chars.push(((bytes[i++] & 0xff) << 8) | (bytes[i++] & 0xff));
    }
    return String.fromCharCode.apply(null, chars);
}

// https://codereview.stackexchange.com/a/3589/75693
function stringToBytes(str) {
    var bytes = [];
    for(var i = 0, n = str.length; i < n; i++) {
        var char = str.charCodeAt(i);
        bytes.push(char >>> 8, char & 0xFF);
    }
    return bytes;
}

Lo unit test utilizza questa stringa UTF-8:

    // http://kermitproject.org/utf8.html
    // From the Anglo-Saxon Rune Poem (Rune version) 
    const secretUtf8 = `ᚠᛇᚻ᛫ᛒᛦᚦ᛫ᚠᚱᚩᚠᚢᚱ᛫ᚠᛁᚱᚪ᛫ᚷᛖᚻᚹᛦᛚᚳᚢᛗ
ᛋᚳᛖᚪᛚ᛫ᚦᛖᚪᚻ᛫ᛗᚪᚾᚾᚪ᛫ᚷᛖᚻᚹᛦᛚᚳ᛫ᛗᛁᚳᛚᚢᚾ᛫ᚻᛦᛏ᛫ᛞᚫᛚᚪᚾ
ᚷᛁᚠ᛫ᚻᛖ᛫ᚹᛁᛚᛖ᛫ᚠᚩᚱ᛫ᛞᚱᛁᚻᛏᚾᛖ᛫ᛞᚩᛗᛖᛋ᛫ᚻᛚᛇᛏᚪᚾ᛬`;

Si noti che la lunghezza della stringa è di soli 117 caratteri ma la lunghezza in byte, se codificata, è 234.

Se rimuovo il commento dalle righe console.log posso vedere che la stringa che viene decodificata è la stessa stringa che è stata codificata (con i byte passati attraverso l'algoritmo di condivisione segreta di Shamir!):

unit test che demo codifica e decodifica


String.fromCharCode.apply(null, chars)errore se charsè troppo grande.
Marc J. Schmidt

è ovunque o solo alcuni browser ed è documentato?
simbo1905

ad esempio qui developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… But beware: by using apply this way, you run the risk of exceeding the JavaScript engine's argument length limit. The consequences of applying a function with too many arguments (that is, more than tens of thousands of arguments) varies across engines. (The JavaScriptCore engine has hard-coded argument limit of 65536.
Marc J. Schmidt

1

In NodeJS, abbiamo Buffer disponibili e la conversione di stringhe con essi è davvero semplice. Meglio, è facile convertire un Uint8Array in un Buffer. Prova questo codice, ha funzionato per me in Node praticamente per qualsiasi conversione che coinvolge Uint8Arrays:

let str = Buffer.from(uint8arr.buffer).toString();

Stiamo solo estraendo ArrayBuffer da Uint8Array e quindi convertendolo in un buffer NodeJS appropriato. Quindi convertiamo il Buffer in una stringa (puoi inserire una codifica esadecimale o base64 se lo desideri).

Se vogliamo riconvertire in un Uint8Array da una stringa, allora dovremmo farlo:

let uint8arr = new Uint8Array(Buffer.from(str));

Tieni presente che se hai dichiarato una codifica come base64 durante la conversione in una stringa, dovresti usarla Buffer.from(str, "base64")se hai usato base64 o qualsiasi altra codifica che hai usato.

Questo non funzionerà nel browser senza un modulo! I buffer NodeJS semplicemente non esistono nel browser, quindi questo metodo non funzionerà a meno che non si aggiunga la funzionalità Buffer al browser. In realtà è abbastanza facile da fare, basta usare un modulo come questo , che è sia piccolo che veloce!


0
class UTF8{
static encode(str:string){return new UTF8().encode(str)}
static decode(data:Uint8Array){return new UTF8().decode(data)}

private EOF_byte:number = -1;
private EOF_code_point:number = -1;
private encoderError(code_point) {
    console.error("UTF8 encoderError",code_point)
}
private decoderError(fatal, opt_code_point?):number {
    if (fatal) console.error("UTF8 decoderError",opt_code_point)
    return opt_code_point || 0xFFFD;
}
private inRange(a:number, min:number, max:number) {
    return min <= a && a <= max;
}
private div(n:number, d:number) {
    return Math.floor(n / d);
}
private stringToCodePoints(string:string) {
    /** @type {Array.<number>} */
    let cps = [];
    // Based on http://www.w3.org/TR/WebIDL/#idl-DOMString
    let i = 0, n = string.length;
    while (i < string.length) {
        let c = string.charCodeAt(i);
        if (!this.inRange(c, 0xD800, 0xDFFF)) {
            cps.push(c);
        } else if (this.inRange(c, 0xDC00, 0xDFFF)) {
            cps.push(0xFFFD);
        } else { // (inRange(c, 0xD800, 0xDBFF))
            if (i == n - 1) {
                cps.push(0xFFFD);
            } else {
                let d = string.charCodeAt(i + 1);
                if (this.inRange(d, 0xDC00, 0xDFFF)) {
                    let a = c & 0x3FF;
                    let b = d & 0x3FF;
                    i += 1;
                    cps.push(0x10000 + (a << 10) + b);
                } else {
                    cps.push(0xFFFD);
                }
            }
        }
        i += 1;
    }
    return cps;
}

private encode(str:string):Uint8Array {
    let pos:number = 0;
    let codePoints = this.stringToCodePoints(str);
    let outputBytes = [];

    while (codePoints.length > pos) {
        let code_point:number = codePoints[pos++];

        if (this.inRange(code_point, 0xD800, 0xDFFF)) {
            this.encoderError(code_point);
        }
        else if (this.inRange(code_point, 0x0000, 0x007f)) {
            outputBytes.push(code_point);
        } else {
            let count = 0, offset = 0;
            if (this.inRange(code_point, 0x0080, 0x07FF)) {
                count = 1;
                offset = 0xC0;
            } else if (this.inRange(code_point, 0x0800, 0xFFFF)) {
                count = 2;
                offset = 0xE0;
            } else if (this.inRange(code_point, 0x10000, 0x10FFFF)) {
                count = 3;
                offset = 0xF0;
            }

            outputBytes.push(this.div(code_point, Math.pow(64, count)) + offset);

            while (count > 0) {
                let temp = this.div(code_point, Math.pow(64, count - 1));
                outputBytes.push(0x80 + (temp % 64));
                count -= 1;
            }
        }
    }
    return new Uint8Array(outputBytes);
}

private decode(data:Uint8Array):string {
    let fatal:boolean = false;
    let pos:number = 0;
    let result:string = "";
    let code_point:number;
    let utf8_code_point = 0;
    let utf8_bytes_needed = 0;
    let utf8_bytes_seen = 0;
    let utf8_lower_boundary = 0;

    while (data.length > pos) {
        let _byte = data[pos++];

        if (_byte == this.EOF_byte) {
            if (utf8_bytes_needed != 0) {
                code_point = this.decoderError(fatal);
            } else {
                code_point = this.EOF_code_point;
            }
        } else {
            if (utf8_bytes_needed == 0) {
                if (this.inRange(_byte, 0x00, 0x7F)) {
                    code_point = _byte;
                } else {
                    if (this.inRange(_byte, 0xC2, 0xDF)) {
                        utf8_bytes_needed = 1;
                        utf8_lower_boundary = 0x80;
                        utf8_code_point = _byte - 0xC0;
                    } else if (this.inRange(_byte, 0xE0, 0xEF)) {
                        utf8_bytes_needed = 2;
                        utf8_lower_boundary = 0x800;
                        utf8_code_point = _byte - 0xE0;
                    } else if (this.inRange(_byte, 0xF0, 0xF4)) {
                        utf8_bytes_needed = 3;
                        utf8_lower_boundary = 0x10000;
                        utf8_code_point = _byte - 0xF0;
                    } else {
                        this.decoderError(fatal);
                    }
                    utf8_code_point = utf8_code_point * Math.pow(64, utf8_bytes_needed);
                    code_point = null;
                }
            } else if (!this.inRange(_byte, 0x80, 0xBF)) {
                utf8_code_point = 0;
                utf8_bytes_needed = 0;
                utf8_bytes_seen = 0;
                utf8_lower_boundary = 0;
                pos--;
                code_point = this.decoderError(fatal, _byte);
            } else {
                utf8_bytes_seen += 1;
                utf8_code_point = utf8_code_point + (_byte - 0x80) * Math.pow(64, utf8_bytes_needed - utf8_bytes_seen);

                if (utf8_bytes_seen !== utf8_bytes_needed) {
                    code_point = null;
                } else {
                    let cp = utf8_code_point;
                    let lower_boundary = utf8_lower_boundary;
                    utf8_code_point = 0;
                    utf8_bytes_needed = 0;
                    utf8_bytes_seen = 0;
                    utf8_lower_boundary = 0;
                    if (this.inRange(cp, lower_boundary, 0x10FFFF) && !this.inRange(cp, 0xD800, 0xDFFF)) {
                        code_point = cp;
                    } else {
                        code_point = this.decoderError(fatal, _byte);
                    }
                }

            }
        }
        //Decode string
        if (code_point !== null && code_point !== this.EOF_code_point) {
            if (code_point <= 0xFFFF) {
                if (code_point > 0)result += String.fromCharCode(code_point);
            } else {
                code_point -= 0x10000;
                result += String.fromCharCode(0xD800 + ((code_point >> 10) & 0x3ff));
                result += String.fromCharCode(0xDC00 + (code_point & 0x3ff));
            }
        }
    }
    return result;
}

`


Aggiungi una descrizione alla risposta. @terran
Rohit Poudel

-3

Sto usando questo frammento di dattiloscritto:

function UInt8ArrayToString(uInt8Array: Uint8Array): string
{
    var s: string = "[";
    for(var i: number = 0; i < uInt8Array.byteLength; i++)
    {
        if( i > 0 )
            s += ", ";
        s += uInt8Array[i];
    }
    s += "]";
    return s;
}

Rimuovi le annotazioni del tipo se hai bisogno della versione JavaScript. Spero che questo ti aiuti!


3
L'OP ha chiesto di non aggiungere un carattere alla volta. Inoltre, non vuole visualizzarlo come una rappresentazione di stringa di lista, ma piuttosto come una stringa. Inoltre, questo non converte i caratteri in stringa ma ne visualizza il numero.
Albert
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.