从JavaScriptstring读取字节
我有一个string包含JavaScript中的二进制数据。 现在我想读取,例如,从它的整数。 所以我得到前4个字符,使用charCodeAt
,做一些移位等来获得一个整数。
问题是JavaScript中的string是UTF-16(而不是ASCII), charCodeAt
通常返回高于256的值。
Mozilla的参考文献指出:“前128个Unicode代码点是ASCII字符编码的直接匹配”。 (ASCII值大于128的情况如何)。
如何将charCodeAt
的结果转换为ASCII值? 还是有更好的方法来将一个四字符的string转换为一个4字节的整数?
我相信你可以用相对简单的一点操作来做到这一点:
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"
Borgar的答案似乎是正确的。
只是想澄清一点。 Javascript将按位操作视为'32位有符号整数,其中最后(最左边)位是符号位。 也就是说,
getIntAt([0x7f,0,0,0],0).toString(16) // "7f000000" getIntAt([0x80,0,0,0],0).toString(16) // "-80000000"
但是,对于八位字节数据处理(例如,networkingstream等),通常需要'unsigned int'表示。 这可以通过添加一个'>>> 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"
有两种方法可以将utf-8string编码和解码为一个字节数组,并返回。
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)); // Да!
虽然@Borgar正确回答了这个问题,但他的解决scheme非常缓慢。 我花了一段时间来追查(我在一个更大的项目中使用了他的function),所以我想我会分享我的见解。
我最终得到了@Kadm之类的东西 。 这个速度不算快一点,就像500倍一样快(不夸张!)。 我写了一个基准 ,所以你可以看到它自己:)
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; }
博加的解决scheme完美的作品。 如果你想要一个更具体的实现,你可能想看看vjeux中的BinaryReader类 (logging是基于Jonas Raoni Soares Silva的二进制parsing器类 )。
borgars解决scheme改进 :
... 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 ); ...
你是如何将二进制数据放入string的? 如何将二进制数据编码成string是一个重要的考虑因素,在继续之前,您需要回答这个问题。
我知道将二进制数据转换为string的一种方式是使用XHR对象,并将其设置为期望UTF-16。
一旦它在utf-16中,可以使用"....".charCodeAt(0)
从string中检索16位数字"....".charCodeAt(0)
这将是介于0和65535之间的数字
那么,如果你愿意,你可以将这个数字转换成0到255之间的两个数字,如下所示:
var leftByte = mynumber>>>8; var rightByte = mynumber&255;
一个很好的和快速的黑客是使用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的原始string被replace(可能是多于一个)hex字节表示。 例如字母“ż”变成“%C5%BC”。 事实上encodeURI也是一些常规的ascii字符,比如空格,但是没关系。 重要的是,在这一点上,原始string的每个字节或者逐字地表示(如“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.(hex正好是C5)。
现在,如果你像我一样,你会问自己:等一下…如果这真的是一个值为122,97,197,188的字节序列,而JS真的使用UTF,那么为什么我会看到这个“ ż“字符,而不是原来的”ż“?
那么,(我相信)这个序列122,97,197,188(我们在应用charCodeAt时看到的)不是一个字节序列,而是一个代码序列。 字符“Å”有一个代码197,但其实际上是两个字节长的序列:C3 85。
所以,这个技巧是可行的,因为unescape会将数字以百分比编码的string作为代码而不是字节值 – 或者更具体地说:unescape对多字节字符一无所知,所以当它逐个解码字节时,处理值低于128只是伟大的,但是当它们超过127和多字节时不是那么好 – 在这种情况下,unescape只是返回一个多字节字符,它恰好具有与请求的字节值相等的代码。 这个“bug”实际上是有用的function。
我将假设你的目标是从string中读取任意字节。 我的第一个build议是将你的string表示成二进制数据的hex表示。
您可以使用从hex转换为数字来读取值:
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 );
然后你可以解释字节string,但是你想要的。
希望这可以帮助! 干杯!