曾经写过一篇《用JavaScript读写二进制文件》,其实严格的说是JScript,但是很多不明真相的同学根本不区分。内容摘自CodeProject《Reading and Writing Binary Files Using JScript》一文。最近要用JScript处理一些二进制的*.torrent文件,重新看了一遍原文,发现有人在回复中提供了一种更简洁的方法

《a shorter and quicker way [modified]》,英文好的同学自己看原文,我为英文不好的同学简单翻译一下,虽然我英文也很烂。

我一直在寻找怎样在WSH中利用JScript处理二进制文件,这是我找到的最好的方法但是我很快就意识到你可以使用1252代码页(Demon注:详见《ISO-8859-1和Windows-1252的区别》),这不需要庞大的映射,并且经过一番睡觉之后(Demon注:原文是after sleeping on it,实在不知道怎么翻译了o(╯□╰)o),我意识到把读到的字符一个一个映射是非常繁琐和缓慢的。于是我原封不动的保留读取的字符串,但把它封装成一个拥有映射方法的字符串对象,像这样:

// When we read a binary stream as ISO 8859-1 (Latin 1), we should get a string
// 当我们用ISO 8859-1 (Latin 1)编码读取二进制流的时候,我们将得到一个字符串,
// where each charCodeAt value matches the byte from the stream. Unfortunately
// 每个字符的charCodeAt值都对应二进制流的一个字节。遗憾的是
// Windows won't give you Latin 1 -- when you ask for it, you get code page
// Windows并不给你Latin 1 —— 当你请求它的时候,你得到的是代码页
// 1252, which has extra characters stuck in for byte values from 128 to 159.
// 1252,它含有额外的字符,会影响从128到159的字节的值
// These two strings allow us to translate between the bogus Windows characters
// 这两个字符串允许我们完成虚假的Windows字符和原始字节值之间的转换
// and the original byte values.
varbogusWindows1252chars ="\u20AC\u201A\u0192\u201E\u2026\u2020\u2021"+
"\u02C6\u2030\u0160\u2039\u0152\u017D"+
"\u2018\u2019\u201C\u201D\u2022\u2013\u2014"+
"\u02DC\u2122\u0161\u203A\u0153\u017E\u0178";
// No translation is necessary for characters 0x81, 0x8D, 0x8F, 0x90, or 0x9D.
// 0x81, 0x8D, 0x8F, 0x90, or 0x9D的字符不需要转换
varcorrectLatin1chars ="\u0080\u0082\u0083\u0084\u0085\u0086\u0087"+
"\u0088\u0089\u008A\u008B\u008C\u008E"+
"\u0091\u0092\u0093\u0094\u0095\u0096\u0097"+
"\u0098\u0099\u009A\u009B\u009C\u009E\u009F";
// This turns a string read as codepage 1252 into a boxed string with a
// 返回一个以1252代码页读取的字符串,并封装了byteAt方法
// byteAt method. We also modify the slice method to return a similar object.
// 我们也修改了slice方法使之返回一个相同的对象
functionbinaryString(str)
{
varr = str ?newString(str) :newString();
// always return an object with a .length
// 总是返回一个含有 .length属性的对象
r.byteAt =function(index)
{
// translate character back to originating Windows-1252 byte value
// 把Windows-1252字节转换成原始字节值
if(this.charCodeAt(index) <=255)
return this.charCodeAt(index);
varp = bogusWindows1252chars.indexOf(this.charAt(index));
returncorrectLatin1Chars.charCodeAt(p);
};
r.slice =function(start, end)
{
returnbinaryString(this.substring(start, end));
};
returnr;
}
// Does reverse translation from bytes back to Windows-1252 characters. You can
// 完成相反的从原始值向Windows-1252字符的转换
// build up a string to write back to disk by concatenating a bunch of these.
// 你可以建立一个写回硬盘的字符串,以连接的方法
functionfromByte(num)
{
varc =String.fromCharCode(num);
varp = correctLatin1chars.indexOf(c);
returnp >=0? bogusWindows1252chars.charAt(p) : c;
}
// Reads bytes from a file, returning them as a binaryString.
// 读取文件的字节,以二进制字符串的形式返回它们
functionbinaryReadFile(path, maxLength)
{
varbinstream =newActiveXObject("ADODB.Stream");
binstream.Type =2/*adTypeText 文本模式 */;
binstream.Charset ="iso-8859-1";// actually Windows codepage 1252 其实是Windows-1252
binstream.Open();
binstream.LoadFromFile(path);
returnbinaryString(binstream.ReadText(maxLength));
}

我仅仅实现二进制读取,而没有实现二进制写入,但是结合上下文,这是很简单的。你可能想把 fromByte 函数拓展成为一个能够转换较大的块的方法,比如像产生大端序或者 UTF-8 的字符串的方法,或者任何你想要的格式,这样你就不需要每次都一个一个地连接字节。

这样比原来短了很多,不是吗?也许还会更快,至少你不用多次处理同一字节。在我使用的某些地方,它确实比较快,比如读取MP3文件并提取一些它们的ID3标签。在这个情况下,你只需要用indexOf来寻找标签,这样只有很少的字符需要转换成字节。

如果需要更大的读写,可以把映射缓存到数组中。这样 byteAt 和 fromByte 就会变得很短并且很快。你仅仅需要初始化一个循环来操作它们,从bogusWindows1252chars到correctLatin1chars字符串。

翻译得很烂,有个别语句我自己都没看懂。在这里纠正一下原作者的错误, ADODB.Stream很BUG,当你用iso-8859-1字符集读取文本的时候,跟上面说的一样,Windows不会给你iso-8859-1(无视标准是微软的一贯作风),而是用Windows-1252字符集(两者的区别详见《ISO-8859-1和Windows-1252的区别》)。所以得到的字符串的charCodeAt值与原二进制流并不一一对应,需要将Windows-1252额外的字符做一个映射。但是,如果你用iso-8859-1写入文本,这时的的确确是iso-8859-1,Windows不会再当成Windows-1252。所以,读取的字符串并不需要再次转回Windows-1252,原作者写错了。