慢
2本来今天早上打算去杭图,但昨天睡得很晚,醒来已经到了中午12点,放了别人的鸽子,心里过意不去,又觉得浪费了早上美好的时间,更觉惭愧,于是想要在下午弥补,打算把《Pro Git》看完,给自己点安慰。大约还有四、五章的样子没看,看了下时间,离下午五点还剩四个小时,得抓紧。被这样一种心思给驱动着,结果却完全无法专注进去看,越想快点看完结果越囫囵吞枣地在看,越囫囵吞枣地在看就越急躁,看了十几分钟以后就看不下去了。想想就算看完,那也只是看完了而已,什么收获都不会有,那么干脆停下来吧。
然后我又拿起了《禅与摩托车维修艺术》(即:《万里任禅游》),奇怪的是看这本书的时候心情总是很平静,也许是从来没有打算在什么时候看完它。刚翻到第二部分,突然又想起了这本书中的一句话:
仓促本身就是20世纪最要不得的态度,当你做某件事的时候,一旦想要求快,就表示你再也不关心它,而想去做别的事。
的确,现在信息的获取速度越来越快,结果却把使人们变得越来越不耐烦,越来越没有耐心去做一件事情,也越来越没有耐心去等待一件事情的结果。
信息时代不仅仅加速了信息获取的方式,也加速地消耗着人们的耐心。所以在这个快速的世界里,我想最重要心态是慢。慢不是囫囵吞枣地去做一件事情,也不是对这件是事情无所谓,三天打鱼,两天晒网,慢是从容,从容地把一件事情做好,做完美,虽然这个世界上没有完美的事情,但是当把一件事情做到接近完美的事情,意想不到的事情就会发生。
说到这里,便想起了那部昨天导致我晚睡的电影《时间规划局》,里面的“时间穷人”因为剩余时间总是不多,所以总是行色匆匆。而”时间富人”因为时间总是很充足,所以步调总是比较慢,也比较从容。想想我们的时代,我们的时间并没有科技的发展而流逝地更快,或者流逝的更慢。相对于以前,因为科技的成就,我们做一件事情的时间也更加充裕,我们应该更加从容地去做事情,而不应该更加急迫。
所以,切记切记,慢慢来,不必着急,不知不觉你可以走得比别人更远。
New Year, New Slash-to-Search
0大家新年好,赶在2012来临之前,对之前写的一个Chrome小插件Slash-to-Search做了一些小的改进,新年新气象,如果你还在用这个插件,那么来看看吧:

What’s New?
- 选项页面有了新UI,如上图。
- 规则的添加和显示集成到了一起。
- 去除了规则到处功能,让插件更小巧。
《精通正则表达式》阅读笔记
3花了近三周的晚上的时间把《精通正则表达式》这本书看完了,说是看完,其实是只看3,4,5,6四章,前面的和后面的几章按照作者的建议觉得自己没有必要去看了。毕竟对于这本书来说,这四章才是重点,在这四章中,作者就已经把正则表达式的语法,正则表达式引擎的工作原理,以及正则表达式的调优讲完。下面是我在看这四章的过程中的一些笔记和总结。
正则表达式的语法
对于正则表达式的语法,相信大多数人都和我一样知道一些基本的语法,但是对于一些不常见的语法还是非常陌生,这边就把这些比较陌生的正则表达式语法记录一下:
- 分组且捕获的文本括号:
(),文本被捕获以后可以通过反向引用(\1,\2等等)来引用。 - 分组但是不捕获的文本括号:
(?:),文本被捕获后不能通过反向引用来引用到。 - 忽略优先量词:
*?,+?,??,{min,max}?:量词在正常情况下是“匹配优先”的,会尽量匹配更多的内容(Greedy),忽略优先量词则优先匹配尽量少的内容。 - 占有优先量词:
?+,*+,++,{m,n}+,与匹配优先量词很相似,只是从来不交还已经匹配的字符,即如果已经匹配到了,就放弃该量词表达式内的所有的回溯可能都抛弃。 - 固化分组:
(?>…),使用固化分组的匹配与正常的匹配无差别,但是如果匹配进行到此结构之后,那么此结构体中所有的备用状态都会被放弃。占有优先量词可以看做是固化分组的特殊情况。 - 条件判断:
(?if then|else):测试if条件表达式,如果测试为真,则执行then部分表达式,如果测试失败,则执行else部分表达式,if条件表达式是特殊的表达式,大概有两种情况,一种是对捕获型括号的特殊引用,比如(?(1))这样,会测试第一组捕获型括号是否参与了匹配;另一种是对环视进行匹配,如果环视能够匹配,则返回true。 - 环视,即零宽断言:
(?=…)(?!…)(?<=…)(?<!…),具体的解释可以参考我之前关于零宽断言的一篇文章:使用零宽断言来匹配不包含连续字符串的行。 - 匹配上一次匹配结束的位置:\G,首先出现在Perl中,在用于迭代匹配的时候比较有用。
正则表达式引擎的工作原理
一般上正则表达式的引擎可以分为两种:NFA和DFA,分别表示非确定性有穷自动机和确定性有穷自动机,这两者背后的理论基础大可不必去了解,只需要知道NFA引擎是在进行匹配的时候是表达式主导的,而DFA引擎在进行匹配的时候是文本主导的,下面具体来拿一个例子来将述下这两者的区别:
NFA引擎工作原理
对于NFA引擎,它会先拿出正则表达式的第一部分,然后看看文本有没有符合这个部分的,如果是,则继续表达式的下一部分进行匹配。
现在假设我们有一个正则表达式是a+b,需要用它去匹配aaaaab这个字符串,它的匹配过程大概如下:
- 首先找到正则表达式的第一个部分
a+ - 然后看文本是否匹配到了这个表达式,在文本中,前面的5个a是能够匹配
a+的,那么继续正则表达式的下面部分 - 后面是一个
b,它能够匹配到文本中5个a后面的b,也匹配成功。 - 这样正则表达式的所有部分都能够匹配,所以整个表达式就能够匹配成功。
NFA引擎还有一个重要的特性,这个特性对于理解NFA引擎的原理也是非常关键:回溯。具体来就讲,在遇到两个可能成功的分支时,会先尝试一个可能性,然后记住另一个,如果这个可能性失败了,那么就回溯到之前的可能,回溯的一个重要的原则是,如果需要在“进行尝试”和“跳过尝试”之间选择,对于匹配优先量词,引擎会优先选择“进行尝试”,对于忽略优先量词,引擎会优先选择“跳过尝试”;在进行回溯时,选择回溯的路径是距离最近存储的路径,即基于LIFO原则。
DFA引擎工作原理
对于DFA引擎,DFA引擎会在扫描子字符串时,记录当前有效的所有可能匹配。这个我们就拿书中的tonight的例子来讲。
现在我们拿to(nite|knight|night)来匹配tonight,它的匹配过程大致如下:
- 引擎看拿到文本中的
t,然后找到了表达式中的t可以与之匹配,这样,表达式就可能能够匹配到文本,这样引擎就会添加这样一种潜在的可能。 - 然后引擎继续按照文本往下移动到了
n,这个时候,表达式的多选分支里面只有两个分支可以与之匹配,knight这个分支不能匹配,有效的可能就变成了两个。 - 继续往下走到文本中的
g,这个nite这个分支也不能匹配了,有效的可能变成了一个。 - 最后当匹配到之后的
t时,night这个分支显然能够匹配,这样引擎发现匹配已经成功。
NFA引擎和DFA引擎的各自优缺点:
- 从上面的讲解中可以了解到,NFA引擎因为是正则表达式主导的,可能对文本中的同一个部分进行反复的扫描,DFA则只会对文本进行一次扫描,这样NFA引擎的速度就相对DFA要慢。
- 另外NFA是正则表达式主导的,所以不同的正则表达式对速度的影响比较大,但是DFA因为是文本主导,所以不同的正则表达式对其速度影响不大。
- 从上面一条我们也可以知道,NFA的可玩性就比较大了,相对来说比DFA要灵活很多。其实后面的调优也都是针对NFA引擎的,因为DFA实在是没有什么好调优。
正则表达式调优
正则表达式的调优是在了解了正则表达式的工作原理的基础上进行的,主要是针对NFA引擎,看完正则表达式的调优这一章以后,我自己总结了一条调优的规律:在正则表达式中尽量使用确定的字符:
比较(night|nite|nittt|niasdf)和ni(ght|te|ttt|asdf)这两个正则表达式,因为后面一个正则表达式把各个多选分支中的ni提出来了,这样引擎就能够通过字符扫描的方式快速找到匹配文本中的ni部分,然后再开始匹配后面的多选分支;而前面的那个表达式,引擎在文本的每一个位置都要尝试四个分支,显然速度要慢很多。相比而言,后一个正则表达式比前一个要“确定”,匹配的速度就更快。
针对这一条规律,还可以有很多调优的方式,比如能够加锚点就加锚点之类的,具体的大家可以翻看书的第六章。
总结
应该说这本书觉得是一本值得阅读的书,花时间去看绝对是值得的,特别是当你会一点正则表达式后再去看,收获可能更大。不过这书的翻译看起来有点吃力,读起来不是很顺畅,或许是因为正则表达式本身看起来就有点累,所以看这本书的过程中最好保持头脑清醒,保持注意力集中,这样才能跟上作者的思路。
内存屏障
2前几天一直在看Java内存模型(JMM)这个东西,在了解JMM的过程中也不断地遇到了内存屏障这个名词,遂花了一点时间去了解了一下。
什么是内存屏障
内存屏障,说白了就是一个指令,其作用正如其名字里面所描述的,起到的是屏障的作用,具体地来说,内存屏障这条指令的作用是让屏障指令前面的指令不会被重排序到屏障指令之后,屏障指令之后的指令也不会被重排序到屏障指令之前,相当于一道屏障挡在了指令之间,前面和后面的指令都不能跨过屏障(关于指令重排序,这里不再解释,有兴趣了解的可以去Google一下)。
内存屏障的种类
鉴于CPU和编译器都可能对指令进行重排序,所以我将内存屏障分为两种:
- 编译器级别的:防止编译器对指令进行重排序,例如GCC的
asm volatile("" ::: "memory")等等。 - 处理器级别的:防止处理器对指令进行重排序,例如X86的
lock,lfence(),sfenec()等等(关于处理器级别的内存屏障如何实现,可以参考这篇文章)。
Java的volatile如何通过内存屏障保证其语义的:
首先,需要知道Java中的volatile有两个语义:
- 线程对volatile变量的读写都会马上反映到主存上,对其他线程马上可见。
- 对volatile变量的读写操作不会和其他对共享内存进行操作的指令重排序。
跟踪HotSpot JVM中对volatile的变量的写操作可以发现,其在写操作之后插入了一条指令
asm volatile ("lock; addl $0,0(%%esp)" ::: "cc", "memory");
我们可以把这个指令分成两部分来看:
asm volatile ("" ::: "memory"):这个是GCC中常用的编译器级别的内存屏障,首先这是一条汇编指令,加入了volatile表示指令不会被重排,后面的clobber list里面加入了”memory”表示告诉编译器,指令对主存做了修改,所有CPU中的缓存都失效了,后面的指令需要重新从主存中重新Load数据。lock; addl $0,0(%%esp):对于这一条指令,首先看addl $0,0(%%esp)这个部分,这个部分实际上是个无用操作,如果esp栈顶是0的话,就给栈顶加上一个0,由于这个操作会影响到程序状态字寄存器,所以在后面的clobber list中加入了cc(Condition Code Registry,也称为Flag Registry或Programm Status Word Registry);再看这个汇编指令前面加入了一个lock前缀,这个lock前缀的意思是执行指令的CPU会发出LOCK#信号,独占系统主线直到前面的读写操作全部完成并且执行完加了lock前缀的这条指令,在这个过程中其他CPU的读写操作全部被Hold住(参考Intel的开发手册)。所以lock前缀实际上可以看作是一个多处理器的内存屏障。
这两个部分合起来就起到了一个完整的内存屏障,即对编译器有效,也对处理器有效,另外也保证了volatile的可见性问题。
周末骑行–苏杭往返
2上周末和@woodcafe @厚脸皮后端 @Trinea一起完成了苏杭自行车两天往返,那两天的最低温度大概都在零度左右,最高温度也不过摄氏八度,骑车只要一停下来变马上觉得冷,除了中间在苏州睡了7个多小时之外,剩下的时间基本都在路上,去苏州花了将近14个小时,回杭州花了11个小时,第一次体验了在冬天长时间骑车是什么感觉。这也应该是今年做过的最累的一件事情了,比半程马拉松还要累,不过路上也总算有收获一些沿途的美丽风景,聊以慰藉。
周六从杭州到苏州的路上走地匆匆,边找路,边赶路,在乌镇的时候还碰上@Trinea的车爆胎,耽搁了一点时间。赶到吴江防护林的时候,夜色已经悄悄降临,看到一轮明月从林间升起,才想起早上刚刚在新闻里面看到说今天有月全食,兴奋不已,随即在林字里停了一会儿,小桥,人家,明月,桥下流水,流水中的月影,看到这幅风景,便觉得不虚此行。不过没有单反相机,同行的几位也没有带单反,不能留下那一时的美,甚为遗憾。
到了吴江南北快速干线上的时候,月食开始,一路追着月亮骑过去,看月亮慢慢从盈变缺,几个小时便看到了月的阴晴圆缺,只怕之后再也看不到这样的景色,可惜的是等到月全食完全发生,我们已经到了市区,忙着找旅店,找填肚子的地方,没有看到红月。
有了第一天的经历,第二天从苏州回杭州便觉得轻松了很多,特地在吴江防护林停了一下,掏出手机拍了几张照片:
一直都呆在办公室里面,或者宅在家里,整个秋天就这样晃荡着过了,在初冬看到这样的红叶也算是对秋天的告别:

这张是@woodcafe拍的,吴江防护林里面有蛮多这样的风景,路左右的树连成了一片:

过了吴江防护林,很快便到了浙江境内,也就是乌镇,在乌镇的一座桥上偶遇落日,刚好一条运沙船从桥下穿过,也是很久没有见过的美景:

今年已经快要结束了,应该不会再出去骑车了,明年希望能够和不同的人出去骑车,到不同的地方去,看不同的风景,遇上不同的故事,这样才有意思,不是吗?
另:@woodcafe准备这个春节去环海南岛,大概要花上十天左右,我没有这么多时间可以用,去不成,只能在这里祝顺利了。