当前位置: 首页 > 图文教程 > 网络编程 > Javascript > JS幻想 读取二进制文件

Javascript
IE8 原生JSON支持
iframe 自适应高度[在IE6 IE7 FF下测试通过]
javascript 清除输入框中的数据
IE和FireFox(FF)中js和css的不同
JavaScript asp.net 获取当前超链接中的文本
用JavaScript页面不刷新时全选择,全删除(GridView)
JavaScript获取GridView选择的行内容
javaScript 读取和设置文档元素的样式属性
js select 控制实现代码
javascript 类方法定义还是有点区别
jQuery 绝对入门
网页开发中的容易忽略的问题 javascript HTML中的table
childNodes 用法两例
js 只能输入数字和小数点的文本框改进版
javascript 09年最新版的省市联动
jquery 实现的全选和反选
js 拖拽翻页实现代码
保证JavaScript和Asp、Php等后端程序间传值编码统一
JavaScript 无符号右移赋值操作
JavaScript 无符号右移运算符

Javascript 中的 JS幻想 读取二进制文件


出处:互联网   整理: 软晨网(RuanChen.com)   发布: 2009-09-12   浏览: 166 ::
收藏到网摘: n/a

如果说让JavaScript读取站点上一文本文件,那不过是个再简单不了的事了;但若说要换成一个二进制的文件,并且是完全静态的读取,那似乎有点天方夜谭了。 且不说浏览器内置的HTTP插件是否支持二进制数据流,就JavaScript其自身就毫无二进制的处理能力。聪明的读者也许想说用VBScript就可以实现了。不错,因为VBScript,IE,ActiveX都是微软的产物,所以他们有着无缝的结合。IE的HTTP组件确实能够读取二进制数据,而且也只能够让VBScript读取。但对于其他浏览器,就束手无策了。
毕竟脚本的理念仅仅是用来处理一些简单的交互的,对于处理字节流之类的复杂问题完全不该是脚本的职责。不过作为一种探索,我们还是可以挖掘下其中的乐趣。当然,首先要明确的是,对于二进制的读取JS确实是无能为力的,不过我们可以来模拟,以达到相同的效果,下面就跟着我来吧。
比如现在想要做个推箱子的小游戏,共200关。这时唯一值得考虑到事就出现了:把这200关地图数据保存在何处?如果直接硬塞在一个脚本文件里似乎显得太大,或者单独保存在一个文件里,但是用什么格式。。。不过对于推箱子游戏来说简单的文本格式也够了,但对于一些地图较复杂的也许就会使用BASE64编码,然后由客户端的HTTP组件下载下来解码使用。BASE64编码在JS中还是很常用的,毕竟它不受任何的环境限制,能够处理字符串就行。
既然有个BASE64,那为什么就不能有BASE128,BASE256了呢?如果能实现“BASE256”,岂不就是二进制字节流了?如果真可以如此,那这种方法早就流传开了,还留着那么多的BASE64做什么:)毕竟这是字符串,而不叫字节串,那肯定是有区别的。不妨把一个二进制的文件,当作文本文件读取回来试试,很快你就会发现超过一旦文件中出现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。事实上,在64K以下的文件中肯定有个字符根本就不会出现的(为什么仔细考虑下就明白),即使是在64K以上,还是有非常多的文件不存在某个字符的。毕竟一个Unicode字符有65536之多,很少有文件会把他们全都用上,除非是个冗余极小的压缩文件,但也不会很多。
到此,编码解码思路已明了,剩下自然是实现他们。
刚才提到了源文件中出现最少(甚至是没有)的Unicode字符,不妨就称作key
首先来定义新生成的二进制文件头格式:
复制代码 代码如下:
编码程序虽不复杂,不过也不是几句就能搞定的,为方面这里给个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.etherdream.com/FunnyScript/JSBin/JSBinDemo.html
可以在里面输入 JSBin.rar.txt,123.jpg.txt,jsmin.exe.txt即对相应的编码文件加载,所显示的内容与编码前的文件一模一样.对一个二进制的文件仅仅做了几个字节的修改,就能让JavaScript读取,不是很有趣吗?