Uint8Array转换为Javascript中的字符串


122

我有一些UTF-8编码的数据存在于Javascript的一系列Uint8Array元素中。有没有一种有效的方法可以将它们解码为常规的javascript字符串(我相信Javascript使用16位Unicode)?我不想同时添加一个字符,因为字符串替换会占用大量CPU资源。


不知道它是否会工作,但是我u8array.toString()在从BrowserFS读取文件时使用了这些文件,这些文件在调用时公开了Uint8Array对象fs.readFile
jcubic

1
@jcubic对我来说,toStringUint8Array返回逗号分隔的数字如"91,50,48,49,57,45"(铬79)
kolen

Answers:


171

TextEncoderTextDecoder从所述编码标准,这是由polyfilled stringencoding库,字符串和ArrayBuffers之间转换:

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

39
对于像我这样懒惰的人npm install text-encodingvar textEncoding = require('text-encoding'); var TextDecoder = textEncoding.TextDecoder;。不用了,谢谢。
Evan Hu

16
当心npm文本编码库,webpack捆绑分析器显示该库非常
庞大

3
@VincentScheib浏览器删除了除以外的所有其他格式的支持utf-8。因此,该TextEncoder参数是不必要的!
Tripulse

1
nodejs.org/api/string_decoder.html从例如:const的{StringDecoder} =需要( 'string_decoder'); const解码器=新的StringDecoder('utf8'); const cent = Buffer.from([0xC2,0xA2]); console.log(decoder.write(cent));
Curist

4
请注意,Node.js 在v11中添加了TextEncoder/ TextDecoderAPI,因此,如果仅以当前Node版本为目标,则无需安装任何额外的软件包。
Loilo

42

这应该工作:

// 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;
}

与其他解决方案相比,它更干净一些,因为它不使用任何技巧,也不依赖于Browser JS功能,例如,也可以在其他JS环境中使用。

查看JSFiddle演示

另请参阅相关问题:此处此处


6
这似乎有点慢。但是我发现宇宙中唯一的片段是可行的。好发现+采用!
Redsandro

6
我不明白为什么没有更多的支持。通过小片段的UTF-8约定似乎很明智。正如其他人指出的那样,异步Blob + Filereader非常适合大文本。
DanHorner

2
问题是如何在没有字符串连接的情况下执行此操作
Jack Wester

5
效果很好,除了它不能处理4个以上的字节序列,例如,fromUTF8Array([240,159,154,133])结果为空(而fromUTF8Array([226,152,131])→"☃"
锤碎了

1
为什么排除案例8、9、10和11?有什么特殊原因吗?第15种情况也是可能的,对吗?15(1111)将表示使用了4个字节,不是吗?
RaR

31

这是我使用的:

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

7
doc来看,这似乎无法解码UTF8。
艾伯特

29
这将引发RangeError更大的文本。“超出了最大调用堆栈大小”
Redsandro 2014年

1
如果要将大型Uint8Arrays转换为二进制字符串并获取RangeError,请参阅stackoverflow.com/a/12713326/471341中的Uint8ToString函数。
yonran 2014年

SCRIPT28: Out of stack space当我输入300 + k字符或RangeErrorChrome 39 时,IE 11会抛出异常。Firefox33也可以。这三个都可以100 + k正常运行。
羊皮的

这无法从en.wikipedia.org/wiki/UTF-8上的示例Unicode字符产生正确的结果。例如String.fromCharCode.apply(null,新的Uint8Array([0xc2,0xa2]))不产生¢。
Vincent Scheib

16

可在其中一个Chrome示例应用程序中找到,尽管这是为了处理较大的数据块而您可以进行异步转换。

/**
 * 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
就像您说的那样,除非要转换的缓冲区确实非常大,否则这将非常糟糕。在V8中实现的简单字符串(例如10-40字节)的同步UTF-8与wchar转换应该小于一微秒,而我想您的代码将需要数百倍。都一样谢谢
杰克·韦斯特2013年

15

在Node中,“ Buffer实例也是Uint8Array实例buf.toString()在这种情况下适用。


对我来说很棒。如此简单!但实际上Uint8Array具有toString()方法。
毁灭者

简洁大方,当时还不知道Buffer也是Uint8Array。谢谢!
LeOn-韩立

1
@doom在浏览器端,Uint8Array.toString()不会编译utf-8字符串,它将列出数组中的数字值。因此,如果您拥有的是来自另一个来源的Uint8Array,而它恰好也不是缓冲区,那么您将需要创建一个来做魔术:Buffer.from(uint8array).toString('utf-8')
Joachim Lous

12

只要很少调用提供的函数,并且仅将其用于中等大小的数组,Albert给出的解决方案就可以很好地工作,否则效率极低。这是一个增强的Vanilla JavaScript解决方案,可同时用于Node和浏览器,并具有以下优点:

•有效地适用于所有八位字节数组大小

•不产生中间的一次性字符串

•在现代JS引擎上支持4字节字符(否则用“?”代替)

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
这里最好的解决方案,因为它还可以处理4个字节的字符(例如emoji表情),谢谢!
14:36

1
与此相反的是什么?
simbo1905 '19

6

执行@Sudhir所说的,然后从逗号分隔的数字列表中获取字符串,请使用:

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

如果仍然有用,它将为您提供所需的字符串


抱歉,您没有注意到您不想一次添加一个字符的最后一个句子。希望这对其他CPU使用没有问题的人有所帮助。
shuki 2012年

14
这不会执行UTF8解码。
艾伯特

甚至更短:String.fromCharCode.apply(null, unitArr);。如前所述,它不处理UTF8编码,但是如果您仅需要ASCII支持但无权访问TextEncoder / TextDecoder,则有时这很简单。
拉文斯汀

答案提到一个@Sudhir,但我搜索了页面,现在找到了这样的答案。因此,最好插上他所说的任何内容
Joakim '18

这将对更长的字符串产生可怕的影响。不要在字符串上使用+运算符。
Max

3

如果由于IE不支持TextDecoder API而无法使用它,则

  1. 您可以使用Mozilla开发人员网络网站推荐的FastestSmallestTextEncoderDecoder polyfill
  2. 您也可以使用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

试试这些功能,

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

来源:https : //gist.github.com/tomfa/706d10fed78c497731ac,赞扬Tomfa


2

令我感到沮丧的是,人们没有展示出如何双向进行,也没有展示出事情在没有琐碎的UTF8字符串上起作用。我在codereview.stackexchange.com上找到了一篇帖子,其中包含一些运行良好的代码。我用它把古老的符文转换成字节,在字节上测试了一些crypo,然后将其转换回字符串。工作代码是在github 这里。为了清楚起见,我将方法重命名为:

// 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;
}

单元测试使用以下UTF-8字符串:

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

请注意,字符串长度仅为117个字符,但字节长度在编码时为234个。

如果取消注释console.log行,则可以看到解码的字符串与编码的字符串相同(通过Shamir的秘密共享算法传递字节!):

演示编码和解码的单元测试


String.fromCharCode.apply(null, chars)如果chars太大,将会出错。
马克·施密特

1

在NodeJS中,我们有可用的缓冲区,使用它们进行字符串转换确实很容易。更好的是,将Uint8Array转换为Buffer很容易。尝试下面的代码,它在Node中对我来说几乎可以完成涉及Uint8Arrays的任何转换:

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

我们只是从Uint8Array中提取ArrayBuffer,然后将其转换为适当的NodeJS Buffer。然后,我们将Buffer转换为字符串(如果需要,可以使用十六进制或base64编码)。

如果我们想从字符串转换回Uint8Array,则可以这样做:

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

请注意,如果在转换为字符串时声明了类似base64的编码,则必须使用 Buffer.from(str, "base64")使用base64或其他编码方式。

如果没有模块,则无法在浏览器中使用!NodeJS缓冲区只是在浏览器中不存在,因此,除非您将Buffer功能添加到浏览器,否则该方法将不起作用。不过,实际上这很容易做到,只需使用像this这样的模块,它既小又快速!


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

`


添加一些描述来回答。@terran
Rohit Poudel

-3

我正在使用此Typescript代码段:

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

如果需要JavaScript版本,请删除类型注释。希望这可以帮助!


3
OP要求一次不要添加一个字符。另外,他不想将其显示为list的字符串表示形式,而只是显示为字符串。此外,这不会将字符转换为字符串,而是显示其编号。
艾伯特
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.