贝网博客

我的分类
流水记事
源码下载
Asp.net
其它
数据库
Javascript
.Net技术
我的相册
友情链接
博客园
CSDN博客
Start0
最新回复
嗷嗷的cCC
fasdfasdf
[:..
lz这个东西好厉害,我..
哈哈,好照片
不错,以前一直用黄色..
终于找到支持ff的修正..
终于找到支持ff的修正..
新鲜性
看看,试试,好不好使。
.Net技术 日志列表    
本站一共有博客83条,当前显示26条
回复:2  发表于:2010-07-10 17:52:18
更新于:2013-05-28 15:47:42

概念相关笔记

这是俺学习正则时的一些正则学习笔记
可能理解会有些不对,谁看到谁提哈,嘿嘿

1、容易混淆的单行模式和多行模式:
单行模式只影响.(小数点)的匹配,关闭单行模式,.匹配换行以外的任意字符;开启单行模式,.匹配任意字符
多行模式只影响^和$的匹配,关闭多行模式,^只能匹配字符串开头,$只能匹配字符串结尾;
        开启多行模式,^匹配字符串开头或行的开头,$匹配字符串结尾或行的结尾
因为正则发展的历史原因,造成这2个概念好像是相反的概念,实际这2个概念是没有任何关系的2个概念

2、全局模式(C#没有,js有):关闭时,只匹配一次;开启时,匹配全部字符串,
在js中,关闭全局模式,等效于C#中的Match方法;开启全局模式,等效于C#中的Matches方法

3、贪婪模式与懒惰模式:举例说明:有字符串:0<div>a1</div>b1<div>c1</div><div>d1</div>9
贪婪模式的正则:<div>.*</div>,只有一个匹配结果:<div>a1</div>b1<div>c1</div><div>d1</div>
懒惰模式的正则:<div>.*?</div>,有3个匹配结果,分别是:<div>a1</div>    <div>c1</div>       <div>d1</div>
注:贪婪模式的原理是匹配优先,而懒惰模式的原理是忽略优先,比如:
字符串 abd
贪婪模式正则:ab?c    在匹配时,会先尝试进行ab匹配,再比对c,不匹配了,进行回溯,进行ac的匹配
懒惰模式正则:ab??c  在匹配时,会先尝试进行ac匹配,不匹配了,进行回溯,进行abc的匹配

4、非回溯匹配(也叫固化分组):(?>):举例说明:字符串:张三是中国人,李四是中国人,王五是韩国人
正则:(.*)中国人,因为正则引擎的贪婪特性,.*第一次扫描时会匹配全部字符串,发现后面没有字符了,不能匹配正则里的“中国人”,于是把.*的匹配往前递推一个,发现“国”也不能匹配正则里的“中国人”,于是再把.*的匹配往前递推,一直推到“张三是中国人,李四是”,此时匹配到了“中国人”,于是.*匹配的结果就是:张三是中国人,李四是  这里说的往前递推就是回溯
把正则改为:(?>.*)中国人  匹配就会失败,因为正则式里指定了.*不允许回溯,所以.*第一次扫描时会匹配全部字符串,再往后扫描时匹配不到,就直接返回了,而不会往前递推。注意:非回溯组也是非捕获组,就是这个括号里的值不会被捕获
之所以有这个非回溯,是因为在正则表达式引擎时,回溯是很耗资源和时间的,要尽量避免回溯,比如:
字符串:<a href="http://www.beinet.cn">这是我的网站</a>,要用正则匹配里面的url和文本,可以用下面2个正则,都可以实现:
<a href="(.+?)">(.+?)</a>
<a href="([^"]+)">([^<]+)</a>
但是第一个正则,在匹配时会有回溯,比如href是懒惰匹配,这个.+?会先匹配h,然后看后面是不是",不是,再递推下一个字符t,一直递推19次
而第二个正则,直接就匹配到"前面,不存在回溯,所以在写正则时,要尽量使用没有回溯,或者回溯少的正则

其它笔记

1、\b:表示单词的起始或结束
\B:表示非单词边界(不在单词的开始或结束)
^:表示字符串的起始位置,指定多行模式时,表示行的起始位置
$:表示字符串的结束位置,指定多行模式时,表示行的结束位置

2、反向引用:\1这样的转义数字,代表前面捕获的内容,如果我们想匹配重复的单词,就可以用这种转义数字
举例:this isa a this this a a file list file filea,我们要找出其中重复的单词,可以用正则:
\b([a-z]+)\b \1\b 来匹配,\1表示第一个括号里的内容,\2表示第2个,如此类推

3、捕获的顺序是按左括号的出现顺序,从1开始顺序递增
注:捕获就是把括号里的内容压入堆栈
例如:([+-])?(\d+(\.\d+)?)(.*)
([+-])为捕获的第一个内容,通常为$1,
      C#中可以用Match.Groups[0].Value来得到捕获的值(在正则中可以用\1反向引用,以下类推)
               也可以用Match.Result("$1")来得到捕获的值
(\d+(\.\d+)?)为捕获的第二个内容,通常为$2
而$2中的(\.\d+)为捕获的第三个内容,通常为$3
最后的(.*)为捕获的第四个内容,通常为$4
注意:如果补获组进行了命名,则未命名的第1个左括号为$1,未命名的第2个左括号为$2,以此类推,直到没有未命名的补获为止,再开始按顺序推算有命名的补获组

4、如果对某个括号里的内容不想进行捕获,可以使用?:
例如:例3修改为:([+-])?(\d+(?:\.\d+)?)(.*)
例3里的$3就变成了(.*),而$4就不存在了
技巧:如果不想加?:,可以在匹配时增加选项:RegexOptions.ExplicitCapture,这个选项只会捕获用(?<name>...)的组,但是如果指定了反射引用时,必须对引用的的捕获显式命名,比如正则:<([^\s]+)></\1>,如果指定RegexOptions.ExplicitCapture时会报错

5、替换时保留匹配内容,例如字符串:http://beinet.cn/
要替换成超链接形式<a href=‘http://beinet.cn/’>beinet.cn</a>,可以用C#语句:
    Regex.Replace(@"http://beinet.cn/", @"(http://(.*)/)", @"<a href='$1'>$2</a>");

    Regex.Replace(@"http://beinet.cn/", @"(?<url>http://(?<host>.*)/)", @"<a href='${url}'>${host}</a>");

6、\s匹配空白字符,包括:空格、Tab、换行、回车,等价于 [\t\r\n ]
  \S匹配上述4个字符以外的其它所有字符

  所以:[\s\S] 就可以匹配任意字符了   

7、\w :匹配包括下划线的任何单词字符,等价于 [A-Za-z0-9_]
\W :匹配任何非单词字符,等价于 [^A-Z a-z 0-9_] 

  所以:[\w\W] 也可以匹配任意字符了   

8、\d :匹配所有数字,一般等价于 [0-9]  注:在C#里,默认情况下也匹配全角的0-9
\D :匹配任何非数字
  所以:[\d\D] 也可以匹配任意字符了   

9、.(句点字符。): 匹配除 \n 以外的任何字符。
注意1:[.\n]并不能匹配任意字符,因为在[ ]里,.只是代表自己,不匹配其它字符
              所以要匹配任意字符,请参考:5,6,7
注意2:如果指定正则选项为Singleline,则此时.匹配任意字符了

10、\nnn:匹配一个3位的8进制Ascii字符,如\103匹配大写C字符
\xnn:匹配一个2位的16进制Ascii字符,如\x43匹配大写C字符
\unnnn:匹配一个4位的16进制Unicode字符
\cV:匹配一个控制字符,如\cV匹配Ctrl-V
注意1:为了跟反向引用区分开,表示8进制字符时,比如\43,请写成\043

11、零宽度断言:有的地方称之为环视,或者预搜索,或声明,就是根据表达式匹配一个位置,而不是匹配字符,举例:
有字符串为:abcdefghijklmnopqrstuvwxyz
正声明?=
(?=opq):匹配n与o中间的位置,此时:mn(?=opq),就可以匹配到mn,而m(?=opq)匹配不到东西
                 (?=opq)op,就可以匹配到op,而(?=opq)p匹配不到东西
    举例:Languages have: Java C#.Net C++ Javascript VB.Net JScript.Net Pascal
                正则:\S+(?=\.Net) 将得到结果:C# VB JScript 

逆向正声明?<=
(?<=opq):匹配q与r中间的位置,此时:pq(?<=opq),就可以匹配到pq,而p(?<=opq)匹配不到东西
                   (?<=opq)rst,就可以匹配到rst,而(?<=opq)st匹配不到东西
注:Javascript不支持逆向正声明
    举例:名单:张三 李四 张建四 王五
                 正则:(?<=张)\S+ 将得到结果:三 建四
负声明?|
(?!opq):匹配所有位置,除了n与o中间的位置,此时:[a-z](?!opq),就可以匹配到除n以外的所有字符,而n(?!opq)匹配不到东西
                   (?!opq)[a-z],就可以匹配到除o以外的所有字符,而(?!opq)o匹配不到东西
    举例:123A 456c 789 111C
                 正则:\d{3}(?![A-Z]) 将得到结果:456 789
逆向负声明?<!
(?<!opq):匹配所有位置,除了q与r中间的位置,此时:[a-z](?<!opq),就可以匹配到除q以外的所有字符,而q(?<!opq)匹配不到东西
                   (?<!opq)[a-z],就可以匹配到除r以外的所有字符,而(?<!opq)r匹配不到东西
注:Javascript不支持逆向负声明
    举例:123A 456C 789 111C
                 正则:(?<!1)\d{2}[A-Z] 将得到结果:56C
综合应用举例:
\b\w+(?=o)o\b:匹配所有以o结尾的单词
Regex.Replace("I have 1234567Yuan", @"(?<=\d)(?=(?:\d{3})+(?!\d))", ","):替换字符串里的数字为科学计数法(即3位数字一个逗号)
上面的是C#,Javascript因为不支持逆向环视,所以要用:'123456'.replace(/(\d)(?=(?:\d{3})+(?!\d))/g, "$1,")

12、决策(也叫平衡组)是正则里的3目运算表达式,形如:(?(exp)yes|no),如果exp成立,就匹配yes,否则匹配no
(?!)表示返回匹配失败,如:(\d)(?(1).|(?!))可以匹配以数字开头的任意2个字符
举例1:
字符串:1a cb 3a 5c 3b 正则:(?(\d)\da|b) 可以匹配到结果:1a b 3a b
需要注意的是yes表达式是\da,如果是正则:(?(\d)a|b) 将只会匹配到:b b,
因为expression成立时,是匹配到这个expression的位置,后面的yes也必须要包含这个expression
当然:(?(\d)\wa|b)的匹配结果也是可以的,因为\w包含了\d
对于字符串a1234,正则(?(?<!a)\d\d|\d)可以匹配到1 和 23
举例2:引用前面的条件
字符串:10-12 z0-az 11-sd  正则:(\d)?(0)-(?(1)\d\d|[a-z][a-z]) 后面的?(1),表示前面的第1 个捕获如果匹配时,用yes匹配,否则用no匹配
这个正则可以匹配到:10-12  0-az
参考:http://blog.csdn.net/lxcnn/article/details/4402808
http://www.cnblogs.com/luckcs/articles/2212996.html
 

13、正则表达式选项,(?i:)指定括号内的匹配忽略大小写,比如正则:(?i:a) 表示匹配a或A,而不管是否指定了RegexOptions.IgnoreCase选项
(?n:):指定只有显式命名或编号的组才进行捕获,类似于RegexOptions.ExplicitCapture
(?x:):消除模式中的非转义空白并启用由 # 标记的注释,类似于RegexOptions.IgnorePatternWhitespace
(?m:):指定使用多行模式,类似于RegexOptions.Multiline
(?s:):指定使用单行模式,类似于RegexOptions.Singleline
注:选项可以叠加,比如:(?is:) 表示单行模式,忽略大小写

14、应用 | 时要注意,正则引擎总是选择第一个选项进行匹配,无法匹配时才考虑第2个选项,然后第3个,比如:
字符串:abcd  正则:a|ab 只匹配到 a       而正则:ab|a 则匹配到 ab

15、Javascript提取匹配中的内容举例,下面是在Html里循环提取超链接的Href和链接文本
var a = /<a\s+[^>]*href="([^"\s]*)"[^>]*>([\s\S]*?)<\/a>/ig;
while(a.test(html)){// 第二个test会从第一个test的lastIndex+1处开始匹配
    alert(RegExp.$1);
    alert(RegExp.$2);
}

正则举例

1、需求:如果小数在2位以内,就保持不变,如果有第3位小数,且第3位小数不是0,那也保留,如果是0就不保留,第3位以后的数字全部替换掉
比如:string str = 1.23=》1.23、1.234=1.234、1.230=》1.23、1.2345678=》1.234
此时,Regex.Replace(str, @"(\.\d\d[1-9]?)\d*", "$1")就可以实现,但是对于1.23和1.234,替换操作浪费了一点时间,因为结果相当于用23替换成23
有效率一点的做法是把正则改成:(\.\d\d(?>[1-9]?))\d+
注意里面的固化分组,如果不使用固化分组的正则:(\.\d\d[1-9]?)\d+ 在匹配1.625时,因为\d+至少要匹配一个数字,而[1-9]?可以不匹配,所以导致回溯,\d+匹配了5,导致替换结果成了1.62,与需求不符,所以这里要用固化分组,避免回溯

2、需求:匹配出字符串里的日期,比如:string str = January 31  我们要得到后面的日期31
一般我们会用正则:(0?[1-9]|[12]\d|3[01])  这个多选组合,0?[1-9]匹配01-09或1-9;[12]\d匹配10-29;3[01]匹配30-31,应该是没错的但是匹配的第一个
结果是3,而不是31      这是因为正则引擎按顺序测试组合,0?[1-9]可以匹配3,所以错误结果出现了
正确的正则应该是把能匹配最短数字的0?[1-9]放到最后,变成:([12]\d|3[01]|0?[1-9])  就OK了
或者使用下列正则之一:
(31|[123]0|[012]?[1-9])               (0[1-9]|[12]\d?|3[01]?|[4-9])

3、需求:匹配双引号和里面的内容,内容里允许出现\" 和\\这样的转义
如果内容不包含引号,那正则就是:"[^"]*"
如果加上允许转义的双引号时,我们先用逆序环视,正则变成:"([^"]|(?<=\\)")*",这个表示式可以匹配 "aa\"bb" 这样的文本,但是对于"aa\\" and "bb",它的匹配结果是错误的,因为它把转义的\\后面的这个斜杠去环视了,所以这个正则不能用
再改用:"(\\.|[^"])*",就是匹配\和一个字符,或非双引号,对于上面的这回可以匹配了,不过,对于没有结束双引号的字符串:"aa\"bb,它又匹配到了"aa\",因为正则引擎的回溯到\"时,[^"]能匹配前面的\,于是就返回了匹配成功
所以最终的正则应该是:"([^"\\]|\\.)*"   或者使用固化分组:"(?>([^"]|\\.)*)"
注:这个正则顺序交换一下,变成:"(\\.|[^"\\])*"  也是可以的,但是这个正则回溯比不交换前多,参见下图

4、需求:替换字符串前后的空白(C#的Trim方法已经可以实现,但是js没有这个功能)
在网上最常见的作法是用正则: (^\s*)|(\s*$)  可以搜索 Javascript trim,得到一大堆的类似结果如下:
String.prototype.trim= function(){return this.replace(/(^\s*)|(\s*$)/g, "");} 
这个当然没有问题,但是这个正则是可以改进的,首先,用\s*,这样也可以匹配空,没有空白的字符串也会进行2次替换,
    而且正则里有2个捕获,而实际上捕获没有使用到,
所以比较好的作法是把*改成+,并去掉括号,用:String.prototype.trim= function(){return this.replace(/^\s+|\s+$/g, "");}
注:虽然这点改进很小,但是如果不是替换成"",那结果就出错了,并且在做任何工作时,都想到这么一点点,那总的提升效率还是很多的

5、需求:匹配HTML标签,允许标签中的属性值包含<或>,例如:<input value="a>bc" type="text">
如果没有后面那个要求,那么匹配HTML标签,就是简单的:<[^>]+>
根据要求,我们可以知道,属性值是包含在单引号或双引号里的,所以可以得到下面正则:
<("[^"]*"|'[^']*'|[^'">])*> 

6、需求:匹配嵌套div标签的最内层,例如:<div><div>2<div>3</div></div>1<div>2<div>3<a>3</a>3</div></div></div>,取出3的div
首先自然是两端的正则:<div[^>]*>.*?</div>,这个得到结果:<div><div>2<div>3</div>
那么要求在中间不能出现<div字样,用(?:(?!<div).)匹配前面不等于<div的任意一个字符,得到最终正则如下:
<div[^>]*>(?:(?!<div).)*?</div> 

7、需求:匹配嵌套div标签的最外层
 div>1<div>2<div>3<div>4</div>5 匹配到<div>4</div>
div>1<div>2<div>3<div>4</div>5</div> 匹配到<div>3<div>4</div>5</div>
div>1<div>2<div>3<div>4</div>5</div>6</div>7</div>8</div> 匹配到<div>2<div>3<div>4</div>5</div>6</div>
首先自然是两端的正则:<div> 和 </div>,
然后(?<o><div>)|(?<-o></div>)表示匹配div和相应的结束div,<o>表示把捕获压入堆栈,<-o>表示取出堆栈,堆栈没有数据就表示匹配失败,第三部分(?:(?!</?div)[\s\S])表示不包含div的任意字符,最后还有一块(?(o)(?!))表示如果堆栈中还有div,匹配失败
最终正则如下:
<div>((?<o><div>)|(?<-o></div>)|(?:(?!</?div)[\s\S]))*(?(o)(?!))</div>

8、需求:匹配有效的物理路径,如 c:\ d:/abc/ddd.txt e:\\\\abc////\\kk.exe(注:连续的\或/都被Windows认为是一个\,所以有效)
物理路径自然要以盘符开头,所以正则开始是:^[a-zA-Z]:[\\/]+
接着是后面的子目录,目录或文件名按Windows规定,不允许出现<>/\|:"*? 以及 回车换行共11个字符,所以匹配子目录的正则是:[^\<\>\/\\\|\:""\*\?\r\n]+[\\/]+,因为可能有多级子目录,也可能没有子目录,所以匹配全部子目录的正则就是:(?:[^\<\>\/\\\|\:""\*\?\r\n]+[\\/]+)*,最后完整的正则就是:
^[a-zA-Z]:[\\/]+(?:[^\<\>\/\\\|\:""\*\?\r\n]+[\\/]+)*[^\<\>\/\\\|\:""\*\?\r\n]*$

9、需求:输入6~20位的密码,要求必须是大写字母、小写字母和数字的组合
完整的正则是:
^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])[a-zA-Z0-9]{6,20}$
另外一个使用平衡组的正则:
^(?:([0-9])|([a-z])|([A-Z])){6,20}(?(1)|(?!))(?(2)|(?!))(?(3)|(?!))$

10、需求:匹配HTML的a标签里的href,要求支持单引号、双引号或无引号的可能
可能的数据有:<a href='xxx' target='_self'> <a href=xxx target='_self'> <a href="xxx" target='_self'><a href="javascript:alert('1')"><a href='javascript:alert("1")'>
首先自然是匹配到href的正则:<a\s[^>]*href=,接着匹配引号,因为可能没引号,因此是:(['"])?,后面要判断出现了引号没有,用一个三目运算符:(?(1)yes|no)
如果前面出现了引号,用正则:(?:(?!\1).)*\1,表示不等于前面引号的多个字符+前面的引号(\1表示引用)
完整的正则是(当然这个正则没有考虑更复杂的情况,比如<a href="javascript:alert(\"1\")">,这个等你来扩展吧):
<a\s[^>]*href=(['"])?(?(1)((?:(?!\1).)*)\1|([^\s>]*))

 发表于:2012-12-15 11:14:54
更新于:2012-12-15 11:15:55

今天在进行数据同步时,通过SqlDataReader读取全部的100万数据,传给SqlBulkCopy复制到其它数据库,在复制前需要检查源表和目标表结构是否一致,如果不一致就不同步,直接返回,代码大致如下:

using (SqlDataReader reader = SqlHelper.ExecuteReader(m_sourceCon, "select * from tb"))
{
    if(源表结构与目标表结构不一致){
        MessageBox.Show("结构不一致");
        return;
    }
    // 开始SqlBulkCopy的代码
}

在上面的代码里,tb表的数据量为100万条,每次走到MessageBox后都会超时,即使调整ExecuteReader的Timeout为更长时间也不行,始终是30秒左右超时,通过修改代码反复调查,发现是走到using的结束时超时,于是在if代码框里增加reader.Close(),果然,就是在Close的时候导致的超时

在同事的帮助下,发现在MSDN上对Close方法有个说明:
Close 方法填写输出参数的值、返回值和 RecordsAffected,从而增加了关闭用于处理大型或复杂查询的 SqlDataReader 所用的时间。 如果返回值和查询影响的记录的数量不重要,则可以在调用 Close 方法前调用关联的 SqlCommand 对象的 Cancel 方法,从而减少关闭 SqlDataReader 所需的时间。

原来如此,把ExecuteReader代码修改一下,把SqlDataReader的Command对象返回,并通过调用Command.Cancel()方法来避免这个问题,修改后的代码:

SqlCommand command;
using (SqlDataReader reader = SqlHelper.ExecuteReader(m_sourceCon, "select * from tb", out command))
{
    if(源表结构与目标表结构不一致){
        command.Cancel();
        MessageBox.Show("结构不一致");
        return;
    }
    // 开始SqlBulkCopy的代码
}

至此,问题解决,比较郁闷的就是,SqlHelper.ExecuteReader方法需要多加一个out参数

 发表于:2012-12-14 14:27:52
更新于:2012-12-14 16:22:28

小时候听过一个泡茶的故事,正常的顺序是:烧开水20分钟,准备茶叶1分钟,清洗茶杯5分钟
正常情况下,大家都是先烧开水,在等水烧开的过程中,去准备茶叶和清洗茶杯,而不会等水烧开后,再去准备茶叶和清洗茶杯,这样可以节约6分钟时间。

同样的,最近经常处理一些数据同步的工作,比如把数据从SqlServer同步到Redis或MongoDB里去,很多人的代码都是,先读取SqlServer的数据,经过一番处理,再写入Redis,这里犯了泡茶那样的错误,这些工作都是一步步来的,比如读取数据10秒,处理1秒,写入Redis5秒,每批数据都要耗时16秒,代码简单叙述如下:
while(true){
    // 从SQLServer获取一批数据;
    DataTable dt = "select top(2000) * from tb where id>@lastid order by id"
    if (dt.Rows.Count <= 0)// 没有数据就退出
        break;

    处理数据代码
               
    WriteToRedis(item1.Key, item1.Value);
   
    lastid = (int)dt.Rows[dt.Rows.Count-1]["id"];// 用于获取下一批数据
}

实际上,我们也可以像泡茶那样并行处理,来节约一些时间,微软给我们提供了一个类:System.Threading.ReaderWriterLockSlim
这个类用于管理资源访问的锁定状态,可以实现多线程读取或独占式写入,简单的说,就是对一个数据,读取多个线程一起读取,但是写入只允许独占,也就是在写入时,不允许其它线程来读取或写入,而且必须其它线程的读写操作都完成,才能得到写锁,代码如下:

//全局定义一个读写线程锁
static ReaderWriterLockSlim m_RWLock = new ReaderWriterLockSlim();

while(true){
    // 从SQLServer获取一批数据;
    DataTable dt = "select top(2000) * from tb where id>@lastid order by id"
    if (dt.Rows.Count <= 0)// 没有数据就退出
        break;
    lastid = (int)dt.Rows[dt.Rows.Count-1]["id"];// 用于获取下一批数据

    处理数据代码1(如果SQLServer慢,处理数据代码放入线程)

    //阻塞,直到获取到写锁(写锁只允许一个线程读取数据)
    //只有下面的ReadLock全部释放,这一步才能继续,
    //这样可以读取数据库和写入Redis同步进行,节省读取数据库的时间
    m_RWLock.EnterWriteLock();
    //获取到写锁后,马上释放
    m_RWLock.ExitWriteLock();
   
    // 使用线程写入到Redis
    ThreadPool.UnsafeQueueUserWorkItem(state => {
        try
        {
            //获取读锁, 允许多个线程同时操作Redis
            m_RWLock.EnterReadLock();
            try
            {
                处理数据代码2(如果Redis慢,处理数据代码放EnterWriteLock前面)
               
                WriteToRedis(item1.Key, item1.Value);
            }
            finally
            {
                m_RWLock.ExitReadLock();
            }
        }
        catch (Exception ex)
        {
            LogHelper.WriteException("异常:", ex);
        }
    }, null);
}

 

代码复杂了许多,不过为了效率,忍忍吧,呵呵

 发表于:2012-11-13 14:51:00
更新于:2012-11-13 15:50:33

1、正则工具,可以对匹配结果进行分组汇总统计等,网上找到的正则工具都无法进行汇总,所以我做了这个,可以方便的对日志里出现的次数进行统计
Github地址:https://github.com/youbl/Beinet.cn.RegexTool

2、代替SQL Server的导入导出功能的一个工具,SqlServer的导入导出功能每次都要重新输入连接信息,太坑爹了,复制粘贴也很累人的,所以开发了这个工具
Github地址:https://github.com/youbl/Beinet.cn.DataSync

3、Windows的Hosts管理工具,这种工具网上也很多,但是我这边集成了ping、nslook等功能,会贴心一些吧,哈哈
Github地址:https://github.com/youbl/Beinet.cn.HostsManager

4、驾照科目一模拟考试软件,注:题库有变化,需要自己重新抓取
Github地址:https://github.com/youbl/Beinet.cn.DrivingTest

 发表于:2012-11-06 17:22:53

今天看到,写入文件的代码如下:
using (FileStream fs = new FileStream(@"c:\1.tmp", FileMode.OpenOrCreate, FileAccess.Write))
{
    fs.Write(arr, 0 ,arr.Length);
}
对这个OpenOrCreate有点疑义,查了一下msdn,解释如下:
 

看晕了,我以前一直是使用FileMode.Create,这个枚举的解释:

也就是说Create,如果文件存在,会覆盖,但是OpenOrCreate好像也是覆盖,到底是不是呢?经过测试,发现,OpenOrCreate不是覆盖,而是重写,举个例子,如果文件内容是:123456789,如果使用OpenOrCreate枚举写入abc,那么文件的内容会变成:abc456789

也就是说,OpenOrCreate是打开文件,并把指针指向文件头,重写里面的字符,如果写入的数据长度比文件数据长度小,那么文件的后面那部分数据会保持旧数据不变,而不会扔掉.

在我的实际应用中,OpenOrCreate这个枚举好像意义不大,也许在别的地方确实有用途吧,总之,一定要搞清楚这2个枚举的区别,千万不要用错

 发表于:2012-11-01 11:24:00
更新于:2012-11-01 11:25:26

记录一下自己负责的软件搜索站点的优化过程

1、最初级的搜索方案,就是直接在SqlServer里使用类似这样的SQL语句:where name like '%keyword%' order by downNum desc
这个方案效率很低,全表扫描,数据量小还凑合,数据大了,SQLServer的CPU压力直线上升,而使用数据库本身的全文检索方案,很多结果出不来,放弃

2、建立一个搜索类给各个应用项目去引用,定义一个List<Soft>静态变量,每隔半小时,从数据库把所有软件数据填充到这个静态变量,搜索时,通过foreach循环这个List变量,用代码:
foreach(Soft s in list){
  if(s.Name.IndexOf(keyword) >= 0){
    returnLst.Add(s);
  }
}
把sql的like方案放到web服务器的内存中来匹配,效率比sql确实快了很多,而且压力分散到各个web服务器,数据库的压力减轻了很多,但是缺点同样很明显,就是每台web服务器都保存一份内存,到了半小时,都去数据库更新数据,这个时间点数据库压力一样很大,使用很短时间后,放弃

3、把搜索代码分离出来,单独架设一个搜索站点,通过WCF或JSON接口提供搜索服务,各个应用项目调用这个WCF或JSON接口实现搜索功能,同时改用盘古分词搜索功能,大大丰富了搜索结果数量不足的缺点
但是因为盘古分词搜索依赖于词库,对于新词,比如微博、陌陌之类,如果没有维护,就会搜索不到,另外对于非词组,也会搜索不到结果,最后把方案2中的IndexOf搜索功能也加上,先进行IndexOf搜索,再进行盘古分词搜索,把2个结果的并集返回,造成搜索站点的效率比较低下,而搜索内容又非常多样,把每个搜索词的搜索结果加入缓存也不太现实,内存占用太大,于是改进为对每周的TOP5000搜索词的搜索结果存入缓存,提高缓存命中率,同时还引入了Redis内存数据库,避免得到软件id列表,再去访问数据库时也造成数据库压力

4、一些细节改进:
  a、WCF接口不支持GZIP压缩,使用SOAP协议,造成网络流量偏大,弃用,改用ashx接口,通过Protobuf序列化结果并gzip压缩后返回,同时为调用方封装dll,请求ashx接口并反序列化,经过压力测试,Protobuf接口比WCF接口,网络流量降低了一半,cpu和内存压力也有很大提升,具体数据有空再贴上来吧
  b、s.Name.IndexOf(keyword) 修改为 s.Name.IndexOf(keyword, StringComparison.Ordinal),因为循环量非常大,这个小改进也降低了站点的不少压力
  c、对几十万的软件加载到内存时,根据标题的每个char进行分组到Dictionary中,原理大致是:如果搜索:手机,只对标题包括“手”的那些软件进行循环,这个功能加入后,站点的cpu压力降低了10%
  d、缓存由HttpRuntimeCache,改为自定义的静态变量保存缓存,在缓存过期时,也返回过期缓存,同时后台启动一个线程去填充新的数据,避免缓存过期时,都去执行业务的压力

 发表于:2012-02-27 10:46:31
更新于:2012-10-29 16:34:35

这两天,由于负责的一个搜索站点服务器CPU很高,领导要求优化代码,在本地安装了个JetBrains dotTrace Performance 4.5,
并抓取IIS的请求,分析请求,发现每次请求IndexOf这个方法调用都超过10万次,已经占用了相当的处理时间了,经过ILSpy反编译,发现IndexOf方法定义:
public int IndexOf(string value)
{
    return this.IndexOf(value, StringComparison.CurrentCulture);
}

参考MSDN:

于是改用另外一个枚举:

Ordinal  
单单这个IndexOf的效率能提高一倍以上(其实不止)

另外,Contains方法默认使用的就是Ordinal了

还有一个字符串的Compare方法,我们经常使用:
string a="1", b = "2";
return a.CompareTo(b);

其实可以改用:
string.Compare(version1, version2, StringComparison.Ordinal);
或者:
string.CompareOrdinal(version1, version2);

效率也是很有提升的,关于StringComparison.CurrentCulture和StringComparison.Ordinal的区别,或其它StringComparison枚举,可以参考MSDN:
http://msdn.microsoft.com/zh-cn/library/system.stringcomparison(VS.80).aspx

 发表于:2012-08-16 16:02:16
更新于:2012-10-29 16:33:12

在之前的工作中,使用Fiddler2经常无法捕获到HttpWebRequest发起的请求,找了好久的原因都没找到,在应用程序里写

<system.net>
<defaultProxy enabled="true">
  <proxy proxyaddress="http://127.0.0.1:8888" bypassonlocal="False"/>
</defaultProxy>
</system.net>

也没有效果,今天终于发现,是因为我封装了HttpWebRequest的方法,里面手动写了一句:
httpWebRequest.Proxy = null;

默认情况下,HttpWebRequest会使用系统设置的代理去请求(IE里的代理设置),设置为null后,就不使用代理,直接请求,而Fiddler2是通过设置代理为127.0.0.1:8888来捕获Http请求的,所以就捕获不到了,把上面那行代码注释掉,就ok了


©2008 Beinet.cn 版权所有