Home > Web Front-end > JS Tutorial > body text

JS幻想 读取二进制文件_javascript技巧

PHP中文网
Release: 2016-05-16 18:54:09
Original
1494 people have browsed it

如果说让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
首先来定义新生成的二进制文件头格式:

代码如下:

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 文件数据内容。。。
Copy after login



编码程序虽不复杂,不过也不是几句就能搞定的,为方面这里给个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> 
<% 
&#39;================================================== 
&#39; 类: Stream 
&#39;================================================== 
Const adTypeBinary = 1 
Const adLongVarBinary = 205 
Const adSaveCreateOverWrite = 2 
Class Stream 
    Dim bytBuffer 
    Dim lngSize 
    Dim lngOffset 
    &#39;================================================== 
    &#39; 方法: Load 
    &#39; 说明: 从文件载入数据流 
    &#39;================================================== 
    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 
        &#39; 
        &#39; 读取数据 
        &#39; 
        For i = 0 To lngSize 
            bytBuffer(i) = AscB(MidB(binData, i + 1, 1)) 
        Next 
        lngSize = lngSize + 1 
    End Function 
    &#39;================================================== 
    &#39; 方法: Save 
    &#39;================================================== 
    Public Function Save(Path) 
        Dim objADOStream 
        Dim objRS 
        Dim i 
        Dim binData 
        Set objADOStream = Server.CreateObject("ADODB.Stream") 
        Set objRS = Server.CreateObject("ADODB.Recordset") 
        &#39; 
        &#39; ASP处理二进制只能如此 
        &#39; 
        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 
    &#39;================================================== 
    &#39; 方法: Seek 
    &#39; 说明: 定位字节流当前位置 
    &#39;================================================== 
    Public Function Seek(pos) 
        lngOffset = pos 
    End Function 
    &#39;================================================== 
    &#39; 方法: Read 
    &#39;================================================== 
    Public Function ReadByte() 
        ReadByte = bytBuffer(lngOffset) 
        lngOffset = lngOffset + 1     
    End Function 
    &#39;================================================== 
    &#39; 方法: WriteUInt 
    &#39;================================================== 
    Public Function WriteUInt(Code) 
        bytBuffer(lngOffset) = CByte(Code Mod 256) 
        bytBuffer(lngOffset + 1) = CByte(Code \ 256) 
        lngOffset = lngOffset + 2 
    End Function 
    &#39;================================================== 
    &#39; 属性: Size 
    &#39;================================================== 
    Public Property Get Size() 
        Size = lngSize 
    End Property 
    Public Property Let Size(value) 
        lngSize = value 
        ReDim Preserve bytBuffer(lngSize - 1) 
    End Property 
End Class 
&#39;================================================== 
&#39; 类: Vector 
&#39;================================================== 
Const DEFAULT_SIZE = 20 
Const NUM_INC = 50 
Class Vector 
    Dim arrContainer() 
    Dim lngSize 
    Dim lngCount 
    &#39;================================================== 
    &#39; 过程: 类构造 
    &#39;================================================== 
    Private Sub Class_Initialize() 
        lngCount = 0 
        lngSize = DEFAULT_SIZE 
        ReDim arrContainer(DEFAULT_SIZE - 1) 
    End Sub 
    Private Sub Class_Terminate() 
    End Sub 
    &#39;================================================== 
    &#39; 属性: Add 
    &#39;================================================== 
    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 
    &#39;================================================== 
    &#39; 属性: Item 
    &#39;================================================== 
    Public Property Get Item(id) 
        Item = arrContainer(id) 
    End Property 
    &#39;================================================== 
    &#39; 属性: Count 
    &#39;================================================== 
    Public Property Get Count() 
        Count = lngCount 
    End Property 
End Class 
&#39;================================================== 
&#39; 函数: JSBin 
&#39; 说明: 将制定的文件转换为JS兼容的二进制文件 
&#39; EtherDream 08/06/10 
&#39;================================================== 
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 
    &#39; 
    &#39; 建立脚本字节流对象 
    &#39; 
    Set objStream = New Stream 
    Set vctKey = New Vector 
    Set vctZero = New Vector 
    &#39; 
    &#39; 载入文件 
    &#39; 
    objStream.Load FileIn 
    lngFileLen = objStream.Size 
    lngSize = (lngFileLen - 1) \ 2 
    &#39; 
    &#39; 将字节流转换为整型数组 
    &#39; 
    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 
    &#39; 
    &#39; 计数器清零 
    &#39; 
    Table(0) = USHRT_MAX 
    For i = 1 To USHRT_MAX - 1 
        Table(i) = 0 
    Next 
&#39; 
&#39; 统计每个Unicode字符出现的次数(\0\0除外) 
&#39; 
    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 
&#39; 
&#39; 寻找出现次数最少的Unicode 
&#39; 
    intKeyNum = USHRT_MAX 
    For i = 0 To USHRT_MAX - 1 
        intVal = Table(i) 
        If intVal < intKeyNum Then 
            intKeyNum = intVal 
            intKeyVal = i 
        End If 
&#39; 
&#39; 发现从未出现过的字符直接完成 
&#39; 
        If intKeyNum = 0 Then 
            Exit For 
        End If 
    Next 
    &#39; 
    &#39; 寻找并记录整型数组中所有intKeyVal的位置 
    &#39; 
    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 
    &#39; 
    &#39; 将整型数组中的0替换为intKeyVal 
    &#39; 
    With vctZero 
        For i = 0 To .Count - 1 
            intBuffer(.Item(i)) = intKeyVal 
        Next 
    End With 
    Dim pos 
    &#39; 
    &#39; 生成目标文件 
    &#39; 
    With objStream 
        .Size = 6 + intKeyNum * 4 + (lngSize + 1) * 2 
        .Seek 0 
        .WriteUInt 65279                    &#39;Unicode文件头 0xFEFF 
        .WriteUInt intKeyVal                &#39;出现最少的Unicode值    (0已排除) 
        .WriteUInt intKeyNum + 1            &#39;出现最少的Unicode次数 (避免0) 
        For i = 0 To intKeyNum    - 1            &#39;记录每个最少值的出现位置 
            pos = vctKey.Item(i) 
            .WriteUInt (pos MOD 65535) + 1    &#39;(避免0) 
            .WriteUInt (pos \ 65535) + 1    &#39;(避免0) 
        Next 
        For i = 0 To lngSize 
            .WriteUInt intBuffer(i) 
        Next 
        &#39; 
        &#39; 保存数据至文件 
        &#39; 
        .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>
Copy after login

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

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
About us Disclaimer Sitemap
php.cn:Public welfare online PHP training,Help PHP learners grow quickly!