视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
JS幻想读取二进制文件_javascript技巧
2020-11-27 20:42:37 责编:小采
文档

如果说让JavaScript读取站点上一文本文件,那不过是个再简单不了的事了;但若说要换成一个二进制的文件,并且是完全静态的读取,那似乎有点天方夜谭了。

且不说浏览器内置的HTTP插件是否支持二进制数据流,就JavaScript其自身就毫无二进制的处理能力。聪明的读者也许想说用VBScript就可以实现了。不错,因为VBScript,IE,ActiveX都是微软的产物,所以他们有着无缝的结合。IE的HTTP组件确实能够读取二进制数据,而且也只能够让VBScript读取。但对于其他浏览器,就束手无策了。

毕竟脚本的理念仅仅是用来处理一些简单的交互的,对于处理字节流之类的复杂问题完全不该是脚本的职责。不过作为一种探索,我们还是可以挖掘下其中的乐趣。当然,首先要明确的是,对于二进制的读取JS确实是为力的,不过我们可以来模拟,以达到相同的效果,下面就跟着我来吧。

比如现在想要做个推箱子的小游戏,共200关。这时唯一值得考虑到事就出现了:把这200关地图数据保存在何处?如果直接硬塞在一个脚本文件里似乎显得太大,或者单独保存在一个文件里,但是用什么格式。。不过对于推箱子游戏来说简单的文本格式也够了,但对于一些地图较复杂的也许就会使用BASE编码,然后由客户端的HTTP组件下载下来解码使用。BASE编码在JS中还是很常用的,毕竟它不受任何的环境,能够处理字符串就行。

既然有个BASE,那为什么就不能有BASE128,BASE256了呢?如果能实现“BASE256”,岂不就是二进制字节流了?如果真可以如此,那这种方法早就流传开了,还留着那么多的BASE做什么:)毕竟这是字符串,而不叫字节串,那肯定是有区别的。不妨把一个二进制的文件,当作文本文件读取回来试试,很快你就会发现超过一旦文件中出现127(0x7F)以上的字符,马上就出错了;如果存在个0x00字节的话,后面的内容都会荡然无存,这意味着256个字符中能够利用的还不到一半。

然而,可别忘了这个测试使用的仅仅是最基本的ASCII编码,对于功能强大的XMLHTTP支持的也绝不仅限于如此,那么就试试Unicode字符会怎样。在记事本随便输几个字符,保存为Unicode格式的文本文件。这时用XMLHTTP读取,显示出来的与记事本里的一模一样,但是再用16进制编辑器打开此文件时,就大不相同了。在文件的开头出现了FF FE两字节,后面的每个内容都是由一个0隔开。毕竟这是16位的Unicode字符,除了基本的ASCII外,还要保存各国的文字。例如一个中文就占用了2个字节,而英文数字仍然占用2字节,只是高位由0填充罢了(注意高位字节是在低位字节后面的)。
XMLHTTP能够成功显示出来就说明Unicode还是支持的。现在试着修改文件里面的数据,看看超过了那些范围才会出错。把数据修改成如下:FFFE 0001 0203 7F00 8000 8100 FF00 FFFF。用XMLHTTP测试,虽然显示的都是乱码,但并没出错,返回的字符串用charCodeAt(i)及toString(16)方法一试,原形毕露!几经测试,Unicode并不像ASCII那样有范围,但唯独一个例外:0x0000!
众所周知,0x00就是ASCII的结束标志。但到了Unicode的世界里一切都是16位的,因此字符尾也成了0x0000。到了这里似乎有点遗憾,但接着的目标很明确:如果能够去掉文件中的0x0000,并在之前加上0xFEFF,就能够让JavaScript读取了。

去掉以及恢复,不妨就称他编码与解码吧。编码的方法就见智见仁了,最简单的办法就是记录下每个0x0000的位置,然后除去;在客户端按照记录的位置再复原回去。虽然简单,但也别忘了,0x00在二进制文件中是相当多的,即便是0x0000也是。这样光是记录他们的内容就有很多,显然不是很好。既然说到要记录,为何一定要记录0x0000的位置?反过来想,我们应该记录这个文件中出现次数最少的字符,以及它的位置,然后把0x0000的地方替换成这个字符;解码的时候一旦出现这个字符,但当前位置又不在记录中,就可以确定这就是个0x0000。事实上,在K以下的文件中肯定有个字符根本就不会出现的(为什么仔细考虑下就明白),即使是在K以上,还是有非常多的文件不存在某个字符的。毕竟一个Unicode字符有65536之多,很少有文件会把他们全都用上,除非是个冗余极小的压缩文件,但也不会很多。

到此,编码解码思路已明了,剩下自然是实现他们。
刚才提到了源文件中出现最少(甚至是没有)的Unicode字符,不妨就称作key
首先来定义新生成的二进制文件头格式:

代码如下:

00 01 0xFEFF。 Unicode文件头,这是必须的 
02 03 Key值。 为了不让0x0000成为Key,在寻找的过程中忽略0x0000 
03 04 Key出现的次数+1。 +1是为了避免该位置出现0x0000,后面的也都一样 
05 06 
07 08 第1个Key的位置 用4字节保存每个Key的位置。 
09 0A 
0B 0C 。。 
0D 0E 
0F 10 第n个Key的位置 
11 12 文件数据内容。。

编码程序虽不复杂,不过也不是几句就能搞定的,为方面这里给个ASP版本的(运行效率非常低,不过处理小文件还是很快的)。JavaScript的解码程序倒是非常的简短,放在Demo.html里一起贴出了。
JSBin.asp:

代码如下:

<%@LANGUAGE="VBSCRIPT" CODEPAGE="65001"%> 
<%Option Explicit%> 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml"> 
<head> 
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
<title>JSBin</title> 
</head> 
<body> 
<% 
'================================================== 
' 类: Stream 
'================================================== 
Const adTypeBinary = 1 
Const adLongVarBinary = 205 
Const adSaveCreateOverWrite = 2 
Class Stream 
 Dim bytBuffer 
 Dim lngSize 
 Dim lngOffset 
 '================================================== 
 ' 方法: Load 
 ' 说明: 从文件载入数据流 
 '================================================== 
 Public Function Load(Path) 
 Dim objADOStream 
 Dim binData 
 Dim i 
 Set objADOStream = Server.CreateObject("ADODB.Stream") 
 With objADOStream 
 .Type = adTypeBinary 
 .Open 
 End With 
 With objADOStream 
 .LoadFromFile Path 
 binData = .Read 
 .Close 
 End With 
 Set objADOStream = Nothing 
 lngSize = Ubound(binData) 
 ReDim bytBuffer(lngSize) 
 lngOffset = 0 
 ' 
 ' 读取数据 
 ' 
 For i = 0 To lngSize 
 bytBuffer(i) = AscB(MidB(binData, i + 1, 1)) 
 Next 
 lngSize = lngSize + 1 
 End Function 
 '================================================== 
 ' 方法: Save 
 '================================================== 
 Public Function Save(Path) 
 Dim objADOStream 
 Dim objRS 
 Dim i 
 Dim binData 
 Set objADOStream = Server.CreateObject("ADODB.Stream") 
 Set objRS = Server.CreateObject("ADODB.Recordset") 
 ' 
 ' ASP处理二进制只能如此 
 ' 
 For i = 0 To lngSize - 1 
 binData = binData & ChrB(bytBuffer(i)) 
 Next 
 With objRS 
 .Fields.Append "t", adLongVarBinary, lngSize 
 .Open 
 .AddNew 
 .Fields("t").AppendChunk binData 
 .Update 
 binData = .Fields("t").GetChunk(lngSize) 
 End With 
 With objADOStream 
 .Type = adTypeBinary 
 .Open 
 .Write binData 
 .SaveToFile Path, adSaveCreateOverWrite 
 .Close 
 End With 
 Set objADOStream = Nothing 
 Set objRS = Nothing 
 End Function 
 '================================================== 
 ' 方法: Seek 
 ' 说明: 定位字节流当前位置 
 '================================================== 
 Public Function Seek(pos) 
 lngOffset = pos 
 End Function 
 '================================================== 
 ' 方法: Read 
 '================================================== 
 Public Function ReadByte() 
 ReadByte = bytBuffer(lngOffset) 
 lngOffset = lngOffset + 1 
 End Function 
 '================================================== 
 ' 方法: WriteUInt 
 '================================================== 
 Public Function WriteUInt(Code) 
 bytBuffer(lngOffset) = CByte(Code Mod 256) 
 bytBuffer(lngOffset + 1) = CByte(Code \ 256) 
 lngOffset = lngOffset + 2 
 End Function 
 '================================================== 
 ' 属性: Size 
 '================================================== 
 Public Property Get Size() 
 Size = lngSize 
 End Property 
 Public Property Let Size(value) 
 lngSize = value 
 ReDim Preserve bytBuffer(lngSize - 1) 
 End Property 
End Class 
'================================================== 
' 类: Vector 
'================================================== 
Const DEFAULT_SIZE = 20 
Const NUM_INC = 50 
Class Vector 
 Dim arrContainer() 
 Dim lngSize 
 Dim lngCount 
 '================================================== 
 ' 过程: 类构造 
 '================================================== 
 Private Sub Class_Initialize() 
 lngCount = 0 
 lngSize = DEFAULT_SIZE 
 ReDim arrContainer(DEFAULT_SIZE - 1) 
 End Sub 
 Private Sub Class_Terminate() 
 End Sub 
 '================================================== 
 ' 属性: Add 
 '================================================== 
 Public Function Add(value) 
 If lngCount = lngSize Then 
 lngSize = lngSize + NUM_INC 
 ReDim Preserve arrContainer(lngSize) 
 End If 
 arrContainer(lngCount) = value 
 lngCount = lngCount + 1 
 End Function 
 '================================================== 
 ' 属性: Item 
 '================================================== 
 Public Property Get Item(id) 
 Item = arrContainer(id) 
 End Property 
 '================================================== 
 ' 属性: Count 
 '================================================== 
 Public Property Get Count() 
 Count = lngCount 
 End Property 
End Class 
'================================================== 
' 函数: JSBin 
' 说明: 将制定的文件转换为JS兼容的二进制文件 
' EtherDream 08/06/10 
'================================================== 
Function JSBin(FileIn, FileOut) 
 Const USHRT_MAX = 65536 
 Dim objStream 
 Dim lngFileLen 
 Dim lngSize 
 Dim intBuffer() 
 Dim Table(65535) 
 Dim intVal 
 Dim vctKey 
 Dim vctZero 
 Dim intKeyNum 
 Dim intKeyVal 
 Dim i 
 ' 
 ' 建立脚本字节流对象 
 ' 
 Set objStream = New Stream 
 Set vctKey = New Vector 
 Set vctZero = New Vector 
 ' 
 ' 载入文件 
 ' 
 objStream.Load FileIn 
 lngFileLen = objStream.Size 
 lngSize = (lngFileLen - 1) \ 2 
 ' 
 ' 将字节流转换为整型数组 
 ' 
 ReDim intBuffer(lngSize) 
 On Error Resume Next 
 With objStream 
 For i = 0 To lngSize 
 intVal = .ReadByte() 
 intVal = intVal + .ReadByte() * 256 
 intBuffer(i) = intVal 
 Next 
 End With 
 On Error Goto 0 
 ' 
 ' 计数器清零 
 ' 
 Table(0) = USHRT_MAX 
 For i = 1 To USHRT_MAX - 1 
 Table(i) = 0 
 Next 
' 
' 统计每个Unicode字符出现的次数(\0\0除外) 
' 
 With vctZero 
 For i = 0 To lngSize 
 intVal = intBuffer(i) 
 If intVal = 0 Then 
 .Add i 
 Else 
 Table(intVal) = Table(intVal) + 1 
 End If 
 Next 
 End With 
' 
' 寻找出现次数最少的Unicode 
' 
 intKeyNum = USHRT_MAX 
 For i = 0 To USHRT_MAX - 1 
 intVal = Table(i) 
 If intVal < intKeyNum Then 
 intKeyNum = intVal 
 intKeyVal = i 
 End If 
' 
' 发现从未出现过的字符直接完成 
' 
 If intKeyNum = 0 Then 
 Exit For 
 End If 
 Next 
 ' 
 ' 寻找并记录整型数组中所有intKeyVal的位置 
 ' 
 If intKeyNum > 0 Then 
 With vctKey 
 For i = 0 To lngSize 
 If intBuffer(i) = intKeyVal Then 
 .Add i 
 End If 
 Next 
 End With 
 End If 
 ' 
 ' 将整型数组中的0替换为intKeyVal 
 ' 
 With vctZero 
 For i = 0 To .Count - 1 
 intBuffer(.Item(i)) = intKeyVal 
 Next 
 End With 
 Dim pos 
 ' 
 ' 生成目标文件 
 ' 
 With objStream 
 .Size = 6 + intKeyNum * 4 + (lngSize + 1) * 2 
 .Seek 0 
 .WriteUInt 65279 'Unicode文件头 0xFEFF 
 .WriteUInt intKeyVal '出现最少的Unicode值 (0已排除) 
 .WriteUInt intKeyNum + 1 '出现最少的Unicode次数 (避免0) 
 For i = 0 To intKeyNum - 1 '记录每个最少值的出现位置 
 pos = vctKey.Item(i) 
 .WriteUInt (pos MOD 65535) + 1 '(避免0) 
 .WriteUInt (pos \ 65535) + 1 '(避免0) 
 Next 
 For i = 0 To lngSize 
 .WriteUInt intBuffer(i) 
 Next 
 ' 
 ' 保存数据至文件 
 ' 
 .Save FileOut 
 Response.Write "转换完成!<br>保存至 " & FileOut & "<br>源文件: " & lngFileLen & "字节.<br>转换后: " & .Size & "字节." 
 End With 
 Set objStream = Nothing 
 Set vctZero = Nothing 
 Set vctKey = Nothing 
End Function 
Sub Main() 
 Dim strFile 
 Dim strFileIn 
 Dim strFileOut 
 strFile = Request.QueryString("path") 
 strFileIn = Server.MapPath(strFile) 
 strFileOut = Server.MapPath(strFile & ".txt") 
 JSBin strFileIn, strFileOut 
End Sub 
Main 
%> 
</body> 
</html>

使用时加上path参数即可对指定的文件编码,比如JSBin.asp?path=123.rar,就会对123.rar编码,并生成123.rar.txt的文件.
客户端的可以在我的空间上预览:
http://www.gxlcms.com/
可以在里面输入 JSBin.rar.txt,123.jpg.txt,jsmin.exe.txt即对相应的编码文件加载,所显示的内容与编码前的文件一模一样.对一个二进制的文件仅仅做了几个字节的修改,就能让JavaScript读取,不是很有趣吗?

下载本文
显示全文
专题