从JavaScript字符串中读取字节

时间:2009-08-06 17:46:39

标签: javascript

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

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

Mozilla reference表示“前128个Unicode代码点是ASCII字符编码的直接匹配。” (那么ASCII值> 128?)。

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

9 个答案:

答案 0 :(得分:35)

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

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 :(得分:15)

Borgar的回答似乎是正确的。

只想澄清一点。 Javascript将按位操作视为'32位有符号的int,其中最后一个(最左侧)位是符号位。即,

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

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

但是,对于八位位组数据处理(例如,网络流等),通常需要'unsigned int'表示。这可以通过添加'&gt;&gt;&gt;来实现。 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"

答案 2 :(得分:14)

有两种方法可以将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)); // Да!

答案 3 :(得分:9)

虽然@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; 
}

答案 4 :(得分:5)

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

答案 5 :(得分:3)

你是如何将二进制数据首先放入字符串中的?如何将二进制数据编码为字符串是一个重要的考虑因素,在继续之前需要回答该问题。

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

一旦它在utf-16中,您可以使用"....".charCodeAt(0)

从字符串中检索16位数字

这是0到65535之间的数字

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

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

答案 6 :(得分:3)

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 );
...

答案 7 :(得分:3)

一个不错的快速黑客是使用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"

- 如果仔细观察 - 是原始字符串,其中值>&gt; 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”实际上很有用。

答案 8 :(得分:2)

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

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

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

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

希望这有帮助! 干杯!