标题: 对VBS效率的再思考——处理二进制数据
作者: Demon
链接: https://demon.tw/programming/vbs-efficiency.html
版权: 本博客的所有文章,都遵守“署名-非商业性使用-相同方式共享 2.5 中国大陆”协议条款。
那天无意中搜到一篇名为《我为什么喜欢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]赞赏微信赞赏支付宝赞赏
随机文章:
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
你给出的程序很好,学习了。
字典会比动态数组慢一点,但是差别不是很大。
声明变量是很好的习惯,我为了简单起见没有声明。
为什么一定要用dictionary
odic.Add odic.Count, ado.Read(1)
直接取asc不行吗
AscB(ado.Read(1))