对VBS效率的再思考——处理二进制数据

标签: , , ,

那天无意中搜到一篇名为《我为什么喜欢VBS和C?》的文章,里面引用了《VBS和C语言效率比较》中的程序。

Option Explicit
Dim begin_time,end_time,elapse_time
Dim str,length,i,c
Dim ado,fso,file
begin_time = Timer
Set ado = CreateObject("adodb.stream")
Set fso = CreateObject("scripting.filesystemobject")
Set file = fso.OpenTextFile("foo.txt",2,True)
ado.Type = 1
ado.Open
ado.LoadFromFile("foo.jpg")
str = ado.Read
length = LenB(str)
For i = 1 To length
    c = AscB(MidB(str,i,1))
    file.WriteLine c & "," & "_"
Next
ado.Close
end_time = Timer
elapse_time = end_time - begin_time
WScript.Echo elapse_time

时隔半年多再看这段代码,实在没有什么效率可言。正如有人在《用VBS读写二进制文件》中的回复:

然后用ascB(midB(str,i,1))来获取指定字节所代表的数据进行相关运算。我想慢的原因是把字节数据当作了字符来对待,这样一旦二进制数据文件稍大(不到1M),就根本处理不了。

刚才又看了您的“VBS和C语言效率比较”,发现您用的也是ascB(midB(str,i,1)),看来vb虽然简单,但是效率只能低下!可惜我不懂C语言!

VBS本来就不应该用来处理二进制文件,但是如果一定要用VBS处理,怎样最快呢?为了对比重新找了一张145KB的图片测试,上面的代码花了7.2秒左右。稍加思索,对代码做了第一次修改(简单起见,变量声明就不写了):

begin_time = Timer
Set ado = CreateObject("adodb.stream")
Set fso = CreateObject("scripting.filesystemobject")
Set oDic = CreateObject("scripting.dictionary")
Set file = fso.OpenTextFile("1.txt",2,True)
ado.Type = 1
ado.Open
ado.LoadFromFile("foo.jpg")
Do Until ado.EOS
    odic.Add odic.Count, ado.Read(1)
Loop
ado.Close
arr = odic.Items
For Each i In arr
    c = AscB(i)
    file.WriteLine c & "," & "_"
Next
end_time = Timer
elapse_time = end_time - begin_time
WScript.Echo elapse_time

我认为影响效率的主要是循环中的c = AscB(MidB(str,i,1)),用字符串函数来处理二进制数据是很慢的,于是改成了数组(动态数组太麻烦,我就用字典来模拟了,会比动态数组慢一点点,但是差别不大)。修改后效率有所提高,用了4.4秒左右。

同时,我的朋友也给出了他的修改方案,在这里称之为第二次修改:

begin_time = Timer
Set ado = CreateObject("adodb.stream")
ado.Type = 1 : ado.Open
ado.LoadFromFile("foo.jpg")
str = ado.Read : ado.Close
ado.Type = 2
ado.Charset = "gb2312"
ado.Open
length = LenB(str)
For i = 1 To length
    c = AscB(MidB(str,i,1))
    ado.WriteText c & "," & "_" & vbCrLf
Next
ado.SaveToFile "2.txt", 2
end_time = Timer
elapse_time = end_time - begin_time
WScript.Echo elapse_time

朋友认为影响效率的是用fso对象来写txt文件,于是改用ado来写,效率也有所提高,用了5.5秒左右。

把第一次修改和第二次修改综合起来,得到第三次修改:

begin_time = Timer
Set ado = CreateObject("adodb.stream")
Set oDic = CreateObject("scripting.dictionary")
ado.Type = 1 : ado.Open
ado.LoadFromFile("foo.jpg")
Do Until ado.EOS
    odic.Add odic.Count, ado.Read(1)
Loop
ado.Close
ado.Type = 2
ado.Charset = "gb2312"
ado.Open
arr = odic.Items
For Each i In arr
    c = AscB(i)
    ado.WriteText c & "," & "_" & vbCrLf
Next
ado.SaveToFile "3.txt", 2
end_time = Timer
elapse_time = end_time - begin_time
WScript.Echo elapse_time

经过两次优化,只需要3秒左右。原以为这已经没有办法再优化了,但是突然发现WriteLine很刺眼,再次修改之:

begin_time = Timer
Set ado = CreateObject("adodb.stream")
Set fso = CreateObject("scripting.filesystemobject")
Set oDic = CreateObject("scripting.dictionary")
Set file = fso.OpenTextFile("4.txt",2,True)
ado.Type = 1
ado.Open
ado.LoadFromFile("foo.jpg")
Do Until ado.EOS
    odic.Add odic.Count, ado.Read(1)
Loop
ado.Close
arr = odic.Items
length = UBound(arr)
For i = 0 To length
    arr(i) = AscB(arr(i))
Next
str = Join(arr, ",_" & vbCrLf)
file.Write str
end_time = Timer
elapse_time = end_time - begin_time
WScript.Echo elapse_time

仅仅花了2.5秒。其实并不是ado对象要比fso对象快,而是每次调用WriteLine方法都会写磁盘,频繁的IO当然会影响效率。而ado对象是先把字符串写到内存的缓冲区中,最后再一次性写入磁盘,所以会有ado比较快的假象。

这是我能想出的效率最高的版本了,如果哪位高手还有更好的优化方法,欢迎不吝赐教。当然,这里写的示例程序没有什么实用价值,只是对处理二进制的方法做了一些粗浅的讨论。

所有代码下载:

[download id=58]赞赏

微信赞赏支付宝赞赏

随机文章:

  1. VB6拾遗:LSet语句和RSet语句
  2. 利用WMI打造完美“三无”后门(序章)
  3. 用Tiny C Compiler编译Lua
  4. VBS打开选择文件对话框(Windows 7)
  5. VB使用WebBrowser读取网页中iframe的内容

3 条评论 发表在“对VBS效率的再思考——处理二进制数据”上

  1. PopEye说道:

    Demo,我分别运行了这几个版本,效率果然相差悬殊。但是我想或许还能再提高些,毕竟字典的效率不如数组,但是动态数组我也尝试过,处理几百K的文件时效率要优于你最后的版本,但一旦文件超过1M,效率反倒差了。我分析后认为在循环中的Redim操作是导致效率变差的根本原因,所以不如直接取文件的字节数做静态数组,这样应该会好很多。验证测试:在C盘下有一个xxxxx.jpg文件,大小为1.25M。用你最后的版本测试两次时间分别为:50.23秒/50.13秒,用下面的版本测试时间分别为:5.59秒/5.34秒。另外,我觉得如果事先做变量声明,程序运行会稍微快一点点。还有就是我把你的代码中变量都重新命名了,抱歉。。。
    Option Explicit
    Dim dtmStartTime,dtmEndTime,dtmElapsedTime,intSize,arrArray(),I,J,strDelimiter
    dtmStartTime = Timer
    Dim objADODB,objFSO,objFile,objSourceFile
    Set objADODB = CreateObject(“ADODB.Stream”)
    Set objFSO = CreateObject(“Scripting.FileSystemObject”)
    Set objFile = objFSO.OpenTextFile(“C:\xxxxx.TXT”,2,True)
    objADODB.Type = 1
    objADODB.Open
    objADODB.LoadFromFile(“C:\xxxxx.JPG”)
    intSize = objADODB.Size
    ReDim arrArray(intSize – 1)
    For I = 0 To intSize – 1 Step 1
    arrArray(I) = objADODB.Read(1)
    Next
    objADODB.Close
    For J = 0 To UBound(arrArray)
    arrArray(J) = AscB(arrArray(J))
    Next
    strDelimiter = “,_” & vbCrLf
    objFile.Write Join(arrArray,strDelimiter)
    dtmEndTime = Timer
    dtmElapsedTime = dtmEndTime – dtmStartTime
    WScript.Echo dtmElapsedTime

    • Demon说道:

      你给出的程序很好,学习了。
      字典会比动态数组慢一点,但是差别不是很大。
      声明变量是很好的习惯,我为了简单起见没有声明。

  2. abcn说道:

    为什么一定要用dictionary
    odic.Add odic.Count, ado.Read(1)
    直接取asc不行吗
    AscB(ado.Read(1))

留下回复