【问题标题】:Reading bytes from a JavaScript string从 JavaScript 字符串中读取字节
【发布时间】:2009-08-06 17:46:38
【问题描述】:

我在 JavaScript 中有一个包含二进制数据的字符串。例如,现在我想从中读取一个整数。所以我得到了前 4 个字符,使用charCodeAt,做一些移位等得到一个整数。

问题在于 JavaScript 中的字符串是 UTF-16(而不是 ASCII),charCodeAt 经常返回高于 256 的值。

Mozilla reference 声明“前 128 个 Unicode 代码点是 ASCII 字符编码的直接匹配”。 (ASCII 值 > 128 呢?)。

如何将charCodeAt 的结果转换为ASCII 值?或者有没有更好的方法将四个字符的字符串转换为 4 字节整数?

【问题讨论】:

  • 你能举个例子吗?
  • ASCII 的 NO 值 > 128(实际上,它没有值 > 127:它只定义了 0 到 127 之间的代码)。所以询问“ASCII 值 > 128”是没有意义的;你一定是指其他一些字符编码(ISO-8859-x 代表 x 的某个值,也许?)
  • 有扩展的ASCII码(从128到255)asciitable.com
  • "扩展 ASCII" 不是真的。 “asciitable.com”实际上是在向您展示 ISO-8859-1,它与 JavaScript 如何存储 Unicode 字符串几乎没有关系。

标签: javascript


【解决方案1】:

我相信你可以通过相对简单的位操作来做到这一点:

function stringToBytes ( str ) {
  var ch, st, re = [];
  for (var i = 0; i < str.length; i++ ) {
    ch = str.charCodeAt(i);  // get char 
    st = [];                 // set up "stack"
    do {
      st.push( ch & 0xFF );  // push byte to stack
      ch = ch >> 8;          // shift value down by 1 byte
    }  
    while ( ch );
    // add stack contents to result
    // done because chars have "wrong" endianness
    re = re.concat( st.reverse() );
  }
  // return an array of bytes
  return re;
}

stringToBytes( "A\u1242B\u4123C" );  // [65, 18, 66, 66, 65, 35, 67]

通过将字节数组读取为内存并将其添加到更大的数字中,将输出相加应该是一件简单的事情:

function getIntAt ( arr, offs ) {
  return (arr[offs+0] << 24) +
         (arr[offs+1] << 16) +
         (arr[offs+2] << 8) +
          arr[offs+3];
}

function getWordAt ( arr, offs ) {
  return (arr[offs+0] << 8) +
          arr[offs+1];
}

'\\u' + getWordAt( stringToBytes( "A\u1242" ), 1 ).toString(16);  // "1242"

【讨论】:

  • 您输出的编码甚至没有明确定义。在大多数情况下,您无法在这种虚构的编码和字符串之间进行往返。
  • 确实如此。仅当您知道它是索引 1 处的 2 字节字时才有用。而且您绝对无法从输出字节数组中知道这一点。
【解决方案2】:

Borgar 的回答似乎是正确的。

只是想澄清一点。 Javascript 将按位运算视为“32 位有符号整数”,其中最后一位(最左侧)是符号位。即,

getIntAt([0x7f,0,0,0],0).toString(16)  //  "7f000000"

getIntAt([0x80,0,0,0],0).toString(16)  // "-80000000"

但是,对于八位字节数据处理(例如,网络流等),通常需要“无符号整数”表示。这可以通过添加一个 '>>> 0'(零填充右移)运算符来实现,该运算符在内部告诉 Javascript 将其视为无符号。

function getUIntAt ( arr, offs ) {
  return (arr[offs+0] << 24) +
         (arr[offs+1] << 16) +
         (arr[offs+2] << 8) +
          arr[offs+3] >>> 0;
}

getUIntAt([0x80,0,0,0],0).toString(16)   // "80000000"

【讨论】:

  • 我们可以用 * Math.pow(2, 24) 代替有问题的 >> 0)。这将被视为 64 位双精度,而不是被视为 32 位无符号整数。不过,这可能会慢一点。返回 (b3 * Math.pow(2, 24)) + (b2 github.com/vjeux/jsDataView/commit/…
【解决方案3】:

将utf-8字符串编码和解码为字节数组并返回有两种方法。

var utf8 = {}

utf8.toByteArray = function(str) {
    var byteArray = [];
    for (var i = 0; i < str.length; i++)
        if (str.charCodeAt(i) <= 0x7F)
            byteArray.push(str.charCodeAt(i));
        else {
            var h = encodeURIComponent(str.charAt(i)).substr(1).split('%');
            for (var j = 0; j < h.length; j++)
                byteArray.push(parseInt(h[j], 16));
        }
    return byteArray;
};

utf8.parse = function(byteArray) {
    var str = '';
    for (var i = 0; i < byteArray.length; i++)
        str +=  byteArray[i] <= 0x7F?
                byteArray[i] === 0x25 ? "%25" : // %
                String.fromCharCode(byteArray[i]) :
                "%" + byteArray[i].toString(16).toUpperCase();
    return decodeURIComponent(str);
};

// sample
var str = "Да!";
var ba = utf8.toByteArray(str);
alert(ba);             // 208, 148, 208, 176, 33
alert(ba.length);      // 5
alert(utf8.parse(ba)); // Да!

【讨论】:

  • 我喜欢这个解决方案。我投了赞成票。我不明白你为什么不包括127 而选择了十六进制的0x7F。我正在使用它来检测 JavaScript 中的多字节字符串。例如。 "Şerban".length != toByteArray("Şerban").length
【解决方案4】:

虽然@Borgar 正确回答了这个问题,但他的解决方案相当缓慢。我花了一段时间才找到它(我在一个更大的项目中的某个地方使用了他的功能),所以我想我会分享我的见解。

我最终得到了类似@Kadm 的东西。它不是快了百分之几,而是快了 500 倍(毫不夸张!)。我写了little benchmark,大家可以自己看看:)

function stringToBytesFaster ( str ) { 
var ch, st, re = [], j=0;
for (var i = 0; i < str.length; i++ ) { 
    ch = str.charCodeAt(i);
    if(ch < 127)
    {
        re[j++] = ch & 0xFF;
    }
    else
    {
        st = [];    // clear stack
        do {
            st.push( ch & 0xFF );  // push byte to stack
            ch = ch >> 8;          // shift value down by 1 byte
        }
        while ( ch );
        // add stack contents to result
        // done because chars have "wrong" endianness
        st = st.reverse();
        for(var k=0;k<st.length; ++k)
            re[j++] = st[k];
    }
}   
// return an array of bytes
return re; 
}

【讨论】:

  • 中文字符似乎有一些问题。 Codepoint 与编码不同。
  • Borgar 和 Kadm 提供了不同的解决方案,提供了不同的结果。 Borgar 代码的这个(真的快得多)版本返回的结果与 Borgar 的代码相同。它不会返回与 Kadm 代码相同的结果,而且作者从未声称它确实如此。 Borgar 方法提取原始字节(以与十六进制编辑器相同的方式,或者 xxd 会这样做)。它不了解“代码点”或 unicode。 Kadm 方法使用 支持 unicode 的 encodeUriComponent 对它们进行解构,结果输出不同 - 尽管我无法解释实际差异。
  • 那么哪个解决方案是正确的?
【解决方案5】:

Borga 的解决方案非常有效。如果您想要更具体的实现,您可能需要查看BinaryReader class from vjeux(据记录,它基于binary-parser class from Jonas Raoni Soares Silva)。

【讨论】:

  • 谢谢你的链接,非常非常有用
【解决方案6】:

您最初是如何将二进制数据放入字符串的?如何将二进制数据编码为字符串是一个重要的考虑因素,您需要先回答该问题,然后才能继续操作。

我知道将二进制数据转换为字符串的一种方法是使用 XHR 对象,并将其设置为期望 UTF-16。

一旦它在 utf-16 中,您可以使用 "....".charCodeAt(0) 从字符串中检索 16 位数字

这将是一个介于 0 和 65535 之间的数字

然后,如果您愿意,可以将该数字转换为 0 到 255 之间的两个数字,如下所示:

var leftByte = mynumber>>>8;
var rightByte = mynumber&255;

【讨论】:

    【解决方案7】:

    borgars 解决方案改进

    ...
    do {
          st.unshift( ch & 0xFF );  // push byte to stack
          ch = ch >> 8;          // shift value down by 1 byte
        }  
        while ( ch );
        // add stack contents to result
        // done because chars have "wrong" endianness
        re = re.concat( st );
    ...
    

    【讨论】:

    • 重要的是它运行得比 push & reverse 快得多
    • 编辑说明:取决于数组的大小,在一些限制后随着更大的数组变慢,根本没有区别
    【解决方案8】:

    一个又好又快的技巧是使用 encodeURI 和 unescape 的组合:

    t=[]; 
    for(s=unescape(encodeURI("zażółć gęślą jaźń")),i=0;i<s.length;++i)
      t.push(s.charCodeAt(i));
    t
    
    [122, 97, 197, 188, 195, 179, 197, 130, 196, 135, 32, 103, 196, 153, 197, 155, 108, 196, 133, 32, 106, 97, 197, 186, 197, 132]
    

    也许有必要解释一下为什么它会起作用,所以让我把它分成几个步骤:

     encodeURI("zażółć gęślą jaźń")
    

    返回

     "za%C5%BC%C3%B3%C5%82%C4%87%20g%C4%99%C5%9Bl%C4%85%20ja%C5%BA%C5%84"
    

    - 如果您仔细观察 - 是原始字符串,其中所有值>127 的字符都被(可能不止一个)十六进制字节表示形式替换。 例如字母“ż”变成了“%C5%BC”。事实上,encodeURI 也转义了一些常规的 ascii 字符,如空格,但这并不重要。重要的是,此时原始字符串的每个字节要么逐字表示(如“z”、“a”、“g”或“j”的情况),要么表示为百分比编码的字节序列(就像“ż”的情况一样,它最初是两个字节 197 和 188,然后被转换为 %C5 和 %BC)。

    现在,我们应用 unescape:

    unescape("za%C5%BC%C3%B3%C5%82%C4%87%20g%C4%99%C5%9Bl%C4%85%20ja%C5%BA%C5%84")
    

    这给了

    "zażóÅÄ gÄÅlÄ jaźÅ"
    

    如果您不是以波兰语为母语的人,您可能不会注意到,这个结果实际上与原来的“zażółć gęślą jaźń”有很大不同。对于初学者来说,它有不同数量的字符:) 当然,你可以说,这个奇怪的大字母 A 不属于标准的 ascii 集。事实上,这个“Å”的值是 197。(正好是十六进制的 C5)。

    现在,如果你和我一样,你会问自己:等一下……如果这真的是一个字节序列,值为 122、97、197、188,并且 JS 真的使用 UTF 那我为什么要看到这个“ż”字符,而不是原来的“ż”?

    嗯,事情是(我相信)这个序列 122、97、197、188(我们在应用 charCodeAt 时会看到)不是 bytes 序列,而是 序列>代码。字符“Å”的代码是 197,但实际上是两个字节长的序列:C3 85。

    所以,这个技巧之所以奏效,是因为 unescape 将出现在百分比编码字符串中的数字视为代码,而不是字节值 - 或者,更具体地说:unescape 对多字节字符一无所知,因此当它一个接一个地解码字节时,处理低于 128 的值很好,但当它们高于 127 和多字节时就不太好 - 在这种情况下,unescape 只返回一个多字节字符,该字符恰好具有等于请求的字节值的代码。这个“bug”实际上是一个有用的功能。

    【讨论】:

      【解决方案9】:

      我将假设您的目标是从字符串中读取任意字节。 我的第一个建议是让你的字符串表示为二进制数据的十六进制表示。

      您可以使用从十六进制转换为数字来读取值:

      var BITS_PER_BYTE = 8;
      
      function readBytes(hexString, numBytes) {
          return Number( parseInt( hexString.substr(0, numBytes * (BITS_PER_BYTE/4) ),16 ) );
      }
      
      function removeBytes(hexString, numBytes) {
          return hexString.substr( numBytes * (BITS_PER_BYTE/BITS_PER_CHAR) );
      }
      

      然后可以使用这些函数来读取您想要的任何内容:

      var hex = '4ef2c3382fd';
      alert( 'We had: ' + hex );
      
      var intVal = readBytes(hex,2);
      alert( 'Two bytes: ' + intVal.toString(2) );
      
      hex = removeBytes(hex,2);
      alert( 'Now we have: ' + hex );
      

      然后您可以根据需要解释字节字符串。

      希望这会有所帮助! 干杯!

      【讨论】:

      • 错过BITS_PER_CHAR ;)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多