一开始,我曾想到将字节数据转换为JS字符串,每两个字节一组转换为一个字节,然后写入类型为文本、编码为“Unicode”的Adodb.Stream对象。
这样做有下面这些问题,但各有方法解决:
首先,将字节数据转换为双字节字符必须要考虑高低位字节顺序(Byte Order)的问题。关系到JS字符串字节顺序的到底是硬件还是软件,目前我还无从得知。不过在目前一般Windows的IE/JScript 5.7中,JS字符串是高位在前低位在后(little endian)。解决方法:在字节对转换成字符前,先将高低字节数据调换。
其次,当字节数据的长度为奇数时,必然存在最后不够凑齐一对字节转换为字符的问题。解决方法:可以先在数据尾部任意补充一个字节,待数据写入Stream对象之后,再将Stream对象转换为二进制类型并移除最后一个字节。
再者,Adodb.Stream在的编码为Unicode时,会自动在文本流的开始位置加入两个字节的BOM(Byte Order Mark)。解决方法:将已经写入数据的Stream类型转换为二进制,此时从第3个字节起读出的所有数据即为我们需要的数据。
// 两种生成字符串的方法任选其一 function getDataString() { var s = ''; for (var i = 0; i < 0x100; i++,i++) { s += String.fromCharCode((i+1 << 8) + i); } return s; } function getDataString() { var a = []; for (var i = 0; i < 0x100; i++,i++) { a.push((i+1 << 8) + i); } return String.fromCharCode.apply(null, a); } var s = getDataString(); var stream = new ActiveXObject('Adodb.Stream'); stream.Mode = 3; stream.Open(); stream.Type = 2; stream.Charset = 'unicode'; stream.WriteText(s); stream.Position = 0; stream.Type = 1; stream.Position = 2; var bs = stream.Read(); stream.Position = 0; stream.Write(bs); stream.SetEOS(); stream.SaveToFile('256bytes.bin'); stream.Close();
后来,我想如果用貌似与Adodb.Stream兼容性更好的VBS来实现,是否可以大大精简代码?
于是我想先试试,通过朝Stream中写入单字节数据的方法是否可行。
可惜经过实践发现,作为VB阉割版的VBS也不能直接支持字节数组的创建,要想往Stream中写入自定义的数据,还是得通过字符串文本。
那么再试试往编码为“iso8859-1”的Stream中写入“单字节字符”,结果ChrB()函数返回的单字节值写不进去,而Chr()函数对大于127的数据不能正确处理。
最后发现,ChrW()能正确处理0~255的数据并写入编码为“iso8859-1”的Stream中。实际写入的只有低字节,高位0均被舍弃。
而且这么做还省去了处理字节顺序和BOM的问题。
Dim i, stream Set stream = CreateObject("Adodb.Stream") stream.Mode = 3 stream.Open stream.Type = 2 stream.Charset = "iso8859-1" For i = 0 To 256 - 1 stream.WriteText ChrW(i) Next stream.SaveToFile "256bytes.bin" stream.Close
进一步推断,其实VBS的ChrW()就相当于JS的fromCharCode(),所以在JS中也能直接用这种更简单的方式来处理
var a = []; for (var i = 0; i < 0x100; i++) { a.push(i); } var s = String.fromCharCode.apply(null, a); var stream = new ActiveXObject('Adodb.Stream'); stream.Mode = 3; stream.Open(); stream.Type = 2; stream.Charset = 'iso8859-1'; stream.WriteText(s); stream.SaveToFile('256bytes.bin'); stream.Close();
注意:Adodb.Stream对象的类型要设为文本(2),字符集要设为“iso8859-1”,同时待写入字符的高字节必须为0。
PS:可能是由于之前看过一些使用JS对数据进行编码的源码后,某些印象先入为主,一遇到JS处理二进制数据的问题,就首先想到要使用一个字符表示两个字节的数据。现在放开思路,其实完全可以让一个字符只表示单个字节的数据,只不过高字节的0舍弃不用罢了,这样一来代码将可以大大精简。
至于运行效率问题,内存消耗可能会扩大一倍,但是省却了许多的字节调换处理,在数据量规模不太大的情况下,也应该影响不大,最终还是要受制于脚本引擎自身的效率。