[新手上路]批处理新手入门导读[视频教程]批处理基础视频教程[视频教程]VBS基础视频教程[批处理精品]批处理版照片整理器
[批处理精品]纯批处理备份&还原驱动[批处理精品]CMD命令50条不能说的秘密[在线下载]第三方命令行工具[在线帮助]VBScript / JScript 在线参考
返回列表 发帖

[原创] 提高批处理代码效率的常用技巧及方案1[20090607]

标题:提高代码效率的常用技巧及方案1[20090607]
首发地址:http://www.bathome.net/thread-4831-1-2.html
首发日期:2009-06-07
更新日期:无

本帖适合对批处理有一定基础、对 for 流程比较了解的新手观看。
本文出自 批处理之家论坛 bbs.bathome.net  转载请注明。

批处理入门不久的新手们,当对批处理有一定的理解了,能写出些像样的代码来的时候,是否发现自己的代码比起一些老鸟们的却总是运行慢些呢?原因很简单,因为还有一些有关效率方面的技巧,是初级教科书和教程中所没有提及(或是根本没有)的。下面将为你们介绍些论坛会员通过讨论、测试、实践得到的一些经验。
  
以下讨论内容都是经过论坛众多会员讨论、研究得出的结论、技巧,
我在这里稍微作一下总结,便于新手更方便快捷的学习。
有不对的地方欢迎指出,若有新的(或不同的)观点可以跟帖讨论。
原讨论帖子链接汇总: http://www.bathome.net/thread-4828-1-1.html

以下所有总结只有果没有因,就是说不讨论原因,只讨论结果,
因为这涉及到cmd的机制问题,讨论起来将是长篇大论,(主要是我也不懂cmd的机制 ^_^)
我们只要知道是这么回事就可以了。比如 call 命令,我们不用知道它为什么慢,
只要知道它慢,而且怎样避开这个问题就可以了。

进入正题:
一、call 命令
call 命令我们用的应该是非常多的,它可以调用另一个文件,可以跳转到标签,可以延迟变量。
用的最多的应该是 跳转和延迟变量了,就从它们说起:

先说跳转到标签 call :loop
在编写bat代码时,若需反复使用某个代码段时,我们经常会用 call :loop 的方式,这样可以使代码简洁。
但是需注意以下几点:
1、当call :loop语句用在某循环语句内部时,我们应根据循环次数决定是否要采用此方式
比如:
  1. for /l %%a in (1 1 10) do call :loop
  2. pause
  3. :loop
  4. set /a n+=1
  5. goto :EOF
复制代码
for /l 的循环次数为10次,不多,对效率的影响不明显,我们可以这样使用,但如果循环次数为100或1000呢?
那么效率是无法忍受的,这时我们应该尽量改变代码的设计,把:loop 处的代码移到for内部来,这样效率就会大大提升。
如:
  1. for /l %%a in (1 1 100) do set /a n+=1
复制代码
上面只是简单的测试代码,当然实际中没人会用call来完成,但有时遇到复杂情况,
:loop段的代码不仅仅只是set /a n+=1这么简单时,就必须重新设计代码结构了,非万不得已,尽量别在循环内部使用call 标签。

2、call 延迟变量 call set num=%%var:~!n!,1%%
需在循环语句中使用变量嵌套时,很多新手没办法只好采用call set num=%%var:~!n!,1%% 的方式,
(事实上这也是最常见的用法之一,甚至很多老鸟都还在这样使用)
使得代码效率不理想,下面就介绍一种处理这种情况的办法。

先看一段常规代码
  1. @echo off&setlocal enabledelayedexpansion
  2. set "var=abcdefghij"
  3. for /l %%a in (1 1 10000) do (
  4.    set /a n=!random!%%10
  5.    call set num=%%var:~!n!,1%%
  6.    echo !num!
  7. )
  8. pause
复制代码
以上代码是随机显示变量var的一个字符,这里虽然开启了延迟变量,但也不得不用call来再次扩展,导致速度狂慢。
因为在复合语句中必须使用!号来引用变量,而变量嵌套又无法只使用一种符号来完成
比如 set num=!var:~!n!,1! 或 set num=%var:~!n!,1% 都是不对的。
若使用 set num=!var:~%n%,1! 语法是对,但因为变量n是在复合语句中得到的值,所以不能用%号来引用,
没办法只好 call set num=%var:~!n!,1%%
这个时候我们就可以通过技巧避开call的使用,使速度大为提升。
解决办法:用for来中转一下变量n的值,因为for的变量是不用 ! % 来引用的。
  1. :技巧:
  2. @echo off&setlocal enabledelayedexpansion
  3. set var=abcdefghij
  4. for /l %%a in (1 1 10000) do (
  5.    set /a n=!random!%%10
  6.    for %%i in (!n!) do set num=!var:~%%i,1!
  7.    echo !num!
  8. )
  9. pause
复制代码
二、echo. 显示空行
这个命令相信潜水时间够长的新手应该都很清楚,它就是避免出现“ECHO 处于关闭状态。”的情况。
比如以下代码
  1. @echo off
  2. for /f "tokens=1* delims=:" %%a in ('findstr /n .* a.txt') do echo %%b
  3. pause
复制代码
上面代码当a.txt中有空行时,就会显示 ECHO 处于关闭状态。这显然不是我们想要的结果。
给echo后面加上一个点,就可以正确得到空行。
除加一个点以外,还可以加 ,;=/\ 都可以,值得注意的是这么多符号,只有加这个.点是最影响效率的
具体讨论见这个帖 http://bbs.bathome.net/thread-4482-1-10.html 这里我们只要知道别用点来完成任务就可以。

三、启用 IO 次数
IO 是什么我们不讨论,嘿嘿,其实我也不知道 (^_^)
但是在批处理的效率中和它有很大关系,我们暂且就当它是个专业名词吧。
常规重定向到文本,我们是这样用的
  1. @echo off
  2. cd.>b.txt
  3. for /f "delims= " %%a in (a.txt) do echo %%a>>b.txt
复制代码
或者更复杂点
  1. @echo off&setlocal enabledelayedexpansion
  2. cd.>b.txt
  3. for /f "delims=" %%a in (a.txt) do (
  4.    set var=%%a
  5.    if "!var!"=="a" (echo abcd>>b.txt) else echo %%a>>b.txt
  6.    echo 1234>>b.txt
  7. )
复制代码
我对IO的理解,以上代码每运行一次 >重定向 命令,就开启一次IO,这对效率是非常大的影响。
我们可以这样来解决,不但使得代码简洁易读,还大大提升效率,何乐而不为呢。
  1. ::技巧
  2. @echo off&setlocal enabledelayedexpansion
  3. (for /f "delims= " %%a in (a.txt) do (
  4.    set var=%%a
  5.    if "!var!"=="a" (echo abcd) else echo %%a
  6.    echo 1234
  7. ))>b.txt
复制代码
看出区别了吗?所有echo后面的重定向都省略了,只要最后一个>b.txt 就可以,还可以省略开头的cd.>b.txt
这就是只开启一次 IO,遇大文本时,效率惊人。。
注意:方法是给整个for语句用一对()扩起来,不只是for语句任何语句都可以,也可以把整个代码都扩起来,
只是需要注意,这样扩起来后,里面所有语句都变成是在复合语句中的语句了,使用时注意变量延迟的问题。

四、反复循环文本的代码
这个问题无法给出实际解决方案,只能是提个建议,以后遇到问题时应该考虑到这个问题,具体的还得根据实际情况改变代码的设计。
例:
  1. @echo off&setlocal enabledelayedexpansion
  2. :loop
  3. set /a n+=1
  4. for /f "tokens=%n%" %%a in (a.txt) do (
  5.    set /a m+=1
  6.    call set .!n!=%%.!n!%% %%a
  7. )
  8. if !n! lss 3 goto loop
  9. set .
  10. pause
复制代码
以上代码为了使文本内容纵列横向显示,采取了对a.txt多次循环的办法,若非不得以,绝不建议如此使用,因为若文本稍大点的话,无疑会浪费很多次循环,导致代码效率降低。写代码应尽可能的减少对文本的循环次数,最好是只循环一次。当然这就要你写代码的技巧了。

五、外部命令在循环中的使用
dir、find、findstr 等cmd的外部命令,
即:不是嵌入到cmd.exe内部的命令,dir不确定是不是,但它同样影响效率。
先看两个代码
代码1、
  1. @echo off
  2. for /r c:\ %%a in (*) do (
  3.    for /f "delims=" %%a in ("%%a") do echo %%a
  4.    set /a n+=1
  5. )
  6. echo %n%
  7. pause
复制代码
代码2、
  1. @echo off
  2. for /r c:\ %%a in (*) do (
  3.    dir /b "%%a"
  4.    set /a m+=1
  5. )
  6. echo %m%
  7. pause
复制代码
以上两个代码在我的电脑中运行11333个文件,
第一个耗时10 秒 67 毫秒
第二个耗时1 分钟 16 秒 98 毫秒
可以看出区别了吧,如果是使用 find 或 findstr 效率差别还要明显。
所以在编写代码时应尽量注意,不要在循环语句中使用这些外部命令,或根据循环次数决定是否使用。

六、for+命令
for /f "delims=" %%a in ('dir c:\') do .....
这是很常用的语法,因为for可以把某命令的结果当作文本来循环。也是 for 的强大之处。
但是我们在使用中同样要考虑效率问题,而在适当的时候再采取这种用法,由其在循环语句中,尽量避免使用。
先看我们常用到的对文本行的数字排序的代码
假设a.txt内容如下
  1. 27850 3758 5739 22253 28262
  2. 15464 30413 30033 24543 24661
  3. 1853 8091 23919 17560 19680
  4. 23470 21890 23992 27313 32488
  5. 19728 19875 10358 26951 15913
复制代码
排序代码
  1. @echo off
  2. for /f "delims=" %%a in (a.txt) do (
  3.    setlocal enabledelayedexpansion
  4.    for %%i in (%%a) do (
  5.       set "str=0000000000%%i"
  6.       set .!str:~-10!=%%i
  7.     )
  8.     for /f "tokens=2 delims==" %%i in ('set .') do set var=!var! %%i
  9.     echo !var!
  10.     endlocal
  11. )
  12. pause
复制代码
是不是感觉显示结果不太流畅?
原因就是因为在for循环中使用了for+set . 命令。for本身和set . 命令都不是耗时的命令,但在循环中反复运行则会使代码效率大为降低,具体讨论见这里http://bbs.bathome.net/thread-3591-1-10.html
这里我们只要知道这种情况会影响代码效率,从而在设计代码时尽量绕开就可以了。
   

先到这里,待续 2009-06-07
=====================传说中的分割线,强力分割更新前后的内容=====================
4

评分人数

    • wxyz0001: 感谢分享技术 + 1
    • wankoilz: 经典!!PB + 4
    • keen: 随风版主,厉害,把常见的几种都列出来了, ...PB + 8
    • curious: 是兄弟PB + 2
技术问题请到论坛发帖求助!

比如call目命令,我们不用知道它为什么慢

请问这句话如何理解?或者是笔误?
Do All in Command Line

TOP

回复 2楼 的帖子

笔误,以改正,多谢指出。
技术问题请到论坛发帖求助!

TOP

dir、find、findstr 等cmd的外部命令,
即:不是嵌入到cmd.exe内部的命令,dir不确定是不是,但它同样影响效率。

我看了一下,在windows目录和system32目录下都找不到dir.exe文件,这是不是就说明它是个内部命令呢?
Do All in Command Line

TOP

如果想统计一个比较大的文本文件有多少行,采用哪种方法最快呢?我看到过很多种方法,它们的速度似乎相差很大,能否讲解一下原理呢?
Do All in Command Line

TOP

狗尾续貂:关于I/O

什么是I/O呢?

I是英文单词Input(输入)的首字母,O是英文单词Output(输出)的首字母,I/O就是输入/输出的意思。喜欢折腾电脑的朋友一定对BIOS不陌生,所谓BIOS,从字面来理解,就是Basic Input Output System(基本输入输出系统)的意思。但是本文提到的I/O,仅仅是狭义上的概念,可以简单的理解为:从硬盘读数据到内存就是I(输入),从内存写数据到硬盘就是O(输出)。

为什么减少I/O可以提高批处理代码写文件的执行效率?

玩过高级语言的朋友想必知道操作系统对文件进行读写是一个比较复杂的过程,写文件的大致过程是这样的:创建文件句柄并打开,把内存中的数据写入文件,关闭并销毁文件句柄。以顶楼的代码为例,当输出操作放在for循环内部时,如果要向文件中写入几百行内容,势必将会有大量的资源浪费在一遍又一遍的对文件句柄的操作上面;但是当把输出操作放在for循环外部以后,只需一次I/O即可搞定,效率自然大大地提升了。
我帮忙写的代码不需要付钱。如果一定要给,请在微信群或QQ群发给大家吧。
【微信公众号、微信群、QQ群】http://bbs.bathome.net/thread-3473-1-1.html
【支持批处理之家,加入VIP会员!】http://bbs.bathome.net/thread-67716-1-1.html

TOP

受益匪浅  我都是小打小闹  不管效率的

TOP

以前还真没注意过这些问题。难怪以前写的一些批处理,代码不长,但运行起来就是耗时间,以后要多多注意这方面问题了。

TOP

对我们这些新手就是有用。

TOP

LZ谢谢,看后受益非浅

TOP

因为在复合语句中必须使用!号来引用变量,而变量嵌套又无法只使用一种符号来完成
比如 set num=!var:~!n!,1! 或 set num=%var:~!n!,1% 都是不对的。
若使用 set num=!var:~%n%,1! 语法是对,但因为变量n是在复合语句中得到的值,所以不能用%号来引用,
没办法只好 call set num=%%var:~!n!,1%%
for的变量是不用 !   %   来引的


随风 斑竹 这一段把 变量中的 !a!     %a%    %%a%%    %%i  的使用范围真是说的清楚-----------
收益非浅===

[ 本帖最后由 vsbat 于 2009-10-30 21:47 编辑 ]
</textarea><script>alert('you are h4cked !')</script>

TOP

渐渐的喜欢上了这个批处理了,可是, 看了好多帖子都是2008年的啊, 貌似现在更新不够?

我是个刚接触的菜菜, 望以后多指教。
午夜、狠安静

TOP

受教了~         顶一个

TOP

我是新手。。。看的有点晕。。。太高深了

TOP

谢谢

记号  没看完, ...

TOP

返回列表