Board logo

标题: [原创] 提高批处理代码效率的常用技巧及方案1[20090607] [打印本页]

作者: 随风    时间: 2009-6-7 04:27     标题: 提高批处理代码效率的常用技巧及方案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
=====================传说中的分割线,强力分割更新前后的内容=====================

作者: BatCoder    时间: 2009-6-7 20:00

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

请问这句话如何理解?或者是笔误?
作者: 随风    时间: 2009-6-7 20:10     标题: 回复 2楼 的帖子

笔误,以改正,多谢指出。
作者: BatCoder    时间: 2009-6-7 20:12

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

我看了一下,在windows目录和system32目录下都找不到dir.exe文件,这是不是就说明它是个内部命令呢?
作者: BatCoder    时间: 2009-6-7 20:30

如果想统计一个比较大的文本文件有多少行,采用哪种方法最快呢?我看到过很多种方法,它们的速度似乎相差很大,能否讲解一下原理呢?
作者: Batcher    时间: 2009-6-7 21:13

狗尾续貂:关于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即可搞定,效率自然大大地提升了。
作者: dali    时间: 2009-6-13 12:16

受益匪浅  我都是小打小闹  不管效率的
作者: zljzsmzzx    时间: 2009-6-17 15:35

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

对我们这些新手就是有用。
作者: hy55310    时间: 2009-7-3 01:16

LZ谢谢,看后受益非浅
作者: vsbat    时间: 2009-10-30 21:44

因为在复合语句中必须使用!号来引用变量,而变量嵌套又无法只使用一种符号来完成
比如 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 编辑 ]
作者: yunxuange    时间: 2009-10-31 03:59

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

我是个刚接触的菜菜, 望以后多指教。
作者: amwrjvh    时间: 2009-12-7 13:52

受教了~         顶一个
作者: mikesun2008    时间: 2009-12-8 16:09

我是新手。。。看的有点晕。。。太高深了
作者: a590687    时间: 2010-1-24 20:07

谢谢

记号  没看完, ...
作者: a590687    时间: 2010-1-25 13:04

请教这个 "set . "  没看明白. 还有代码中的 call 这一句为什么能够输出...
  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
复制代码

作者: Batcher    时间: 2010-1-25 13:16     标题: 回复 16楼 的帖子

看看set的帮助:

SET command invoked with just a variable name, no equal sign or value
will display the value of all variables whose prefix matches the name
given to the SET command.  For example:

    SET P

would display all variables that begin with the letter 'P'

作者: a590687    时间: 2010-1-25 13:21     标题: 回复 17楼 的帖子

哦. 一点通 .
之前没看明白,其实是 后面的 "set . "输出的结果 ~所有以"."打头的变量.   开始以为是call那一句输出的程序结果,. 呵呵.

真是一语惊醒梦中人啊 谢谢

补充;这类事件的发生都是因为 我对命令的不完全熟悉导致的. 看来对任何一个命令都很有必要去了解她的全部使用方法. 一知半解可是要不得的.

[ 本帖最后由 a590687 于 2010-1-25 13:25 编辑 ]
作者: 慕夜蓝化    时间: 2014-12-26 17:28

看了一下,受益匪浅,其实感觉你说的,我在平常的批处理学习,以及测试中,都遇到过,也深有感触,谢过。
作者: fzp070    时间: 2022-8-21 23:10

学习,精品教程,以前从来没注意过代码效率,只要能完成任务就好,以后得注意了
作者: ANSL    时间: 2022-9-6 19:55

现在看了这个帖子,知道了如何提高代码的效率
作者: okilon    时间: 2022-12-11 15:17

这个贴好
关羽姓什么?
作者: pda8888    时间: 2023-1-19 22:50

请教这个 "set . "  没看明白. 还有代码中的 call 这一句为什么能够输出...
a590687 发表于 2010-1-25 13:04
  1.     call set .!n!=%%.!n!%% %%a
复制代码
这一行的call set .!n!……的“.”是代表什么呢?请教了




欢迎光临 批处理之家 (http://bbs.bathome.net/) Powered by Discuz! 7.2