Thunderbird:使用Active Directory配置LDAP地址簿

公司人员较多时,同名情况偶有发生,而且因为入职离职总有变动,还有可能出现帐号名重用的情况,
使用静态地址簿不仅会有地址失效,有时还会发生一些令人发囧的状况。

如何才能有一个实时同步的邮件地址簿呢?
好消息是,Mozilla Thunderbird邮件客户端可以配置LDAP作为邮件地址簿。

在Windows操作系统占主流的中国,企业内部大多使用Active Directory作为目录服务,例如我们公司。
但因为各公司AD中的配置不同,Thunderbird一些默认的LDAP配置并不能正确工作,需要手工做些修改。

首先你需要了解贵公司的AD服务器地址,例如我们公司是 “xyz.xxx.com“。

其次需要了解贵公司AD的分类和用户属性,可以下载 Active Directory Explorer 进行查看。

下图是我公司的用户对象属性列表:

  • displayNam 一般是中文名
  • sAMAccountName 是用户的域帐户名
  • userPrincipalName 应该与贵公司的电子邮件格式一致,形如“zhangsan@corp.com

distinguishedName 能够知道AD中用户的BaseDN是多少,
形如 “CN=张三,CN=Users,DC=corp,DC=com”,
去除掉第一个逗号及其之前的内容(“CN=张三,”)即可是用户的BaseDN,后边配置Thunderbird时需要。

掌握这些之后,打开Thunderbird:Tools=>Options=>Composition=>Addressing=>Edit Directories=>Add,将显示如下界面:

  • “Hostname”配置贵公司AD服务器的地址,
  • “Base DN”配置为用户分类的DN,端口默认就是389,
  • “Bind DN”配置你自己的域帐户,用于登录AD。

再切换到“Advanced”页签,按下图配置:

“Search filter”配置串的意思是,当用户输入帐户名搜索时,与AD上用户的哪个属性进行匹配。

配置完这些后,还需要最关键的一步:配置AD查询结果与邮件地址簿数据结构间的映射关系。
打开Thunderbird:Tools=>Options=>Advanced=>General=>Config Editor,将显示如下界面:

需要修改2个最关键的映射:用户姓名、主地址邮件。

  • “ldap_2.servers.default.attrmap.NickName”,用于配置将AD上用户的哪个属性值映射为地址簿姓名,
    你需要根据上边使用ADExplorer工具查看到的结果来确定哪个属性才是存储的用户中文姓名,然后在这配上它。
  • “ldap_2.servers.default.attrmap.PrimaryEmail”,用于配置将AD上用户的哪个属性值映射为地址簿主邮件地址,
    一般配置为userPrincipalName即可,不过你最好根据ADExplorer工具查看的结果来选择正确的属性。

修改完就大功告成了,可以打开Address Book,切换到你配置的LDAP地址簿(本文中的 MyAdBook),
在搜索框中输入自己的邮件帐户名测试一下(例如:zhangsan)。

Enjoy it!

John Denver - Perhaps Love

《John Denver - 也许爱》

John Denver - Perhaps Love

译:疯炎疯语

perhaps love is like a resting place
也许爱情像就一片栖息之地

a shelter from the storm
或是暴风雨中的避难所

it exists to give you comfort
它给你安慰

it is there to keep you warm
它给你温暖

and in those times of trouble
当你陷入困境

when you are most alone
感到孤独无助时

the memory of love will bring you home
爱的回忆将指引你回家

perhaps love is like a window
也许爱情就像一扇窗户

perhaps an open door
或是一扇打开的门

it invites you to come closer
它邀请你走近它

it wants to show you more
想让你了解更多

and even if you lose yourself
甚至当你迷失自我

and don’t know what to do
不知如何是好时

the memory of love will see you through
爱的回忆将伴你度难过难关


oh, love to some is like a cloud
哦,有的人觉得爱似浮云

to some as strong as steel
有的人觉得爱坚如钢铁

for some a way of living
对于有些人爱是一种生活方式

for some a way to feel
对于有些人爱是一种感觉

and some say love is holding on
有人说爱是坚持

and some say letting go
有人说爱是顺其自然

and some say love is everything
有些人说爱是一切

and some say they don’t know
有些人说他们不懂何为真爱


perhaps love is like the ocean
也许爱像海洋

full of conflict, full of change
充满矛盾,充满变化

like a fire when it’s cold outside
像寒冬里的篝火

or thunder when it rains
亦或雨天里的惊雷

if i should live forever
如果我将永生

and all my dreams come true
且我所有的梦想成真

my memories of love will be of you
我对爱的回忆都将是你

end

Stephen Breyer大法官在清华大学法学院演讲有感

观美国联邦最高法院大法官Stephen Breyer在清华大学法学院演讲有感

很早就看了大法官Stephen Breyer在清华法学院的演讲,深有感触一直想写篇文章,奈何提笔容易下笔难,码字码了半年多。
很喜欢这个老头,睿智坦诚,言谈幽默,一把老骨头演讲时还是充满激情。

布雷耶大法官首先谈到了美国的法律系统。美国是联邦制国家,各州有很大的自治权,各州拥有独立的法律体系(立法司法),
美国的大多数法律是由各州制定的(美国95%的法律都是州法,例如刑法、民法、金融法、商法等等),
同样的行为在一州是合法的在另一州却可能是非法。
而且美国是判例法国家(所谓判例法,就是基于法院的判决而形成的具有法律效力的判定,
这种判定对以后的判决具有法律规范效力,能够作为法院判案的法律依据 — 百科),
因此美国的法律非常复杂,百姓一般都搞不明白。美国的联邦最高法院一般只受理与联邦法律(包括宪法)相关的案件。
各州也可以审理与联邦法相关的案件,当这类案件的败诉方层层上诉之后就到了最高法院那里。
因为上诉到最高法院的案子非常多,所以不是所有的上诉案件都会被受理,最高法可以行使“司法选择权”决定受理哪些案子。

对于美国联邦最高法院的职责,Stephen Breyer大法官总结为2句非常精辟的话:
“第一个职责是决定我应该决定的事;第二个职责是决定什么事需要我去决定。”
通俗点说:职责一是审理应该最高法院审理的案子(管辖权与裁判权),
职责二是选择受理哪些案子由最高法进行审理(司法选择权)。

这2句话非常有趣,虽然有点绕口,但却很有逻辑。

法官的职责是根据查明的事实,选择适用的法律(或者判例)做出判决。
因为不同的法官对法律条文可能会有不同的理解,所以可能出现法条适用不一致的情况。
美国联邦最高法院的主要任务就是“确保联邦法律和宪法(在各州)的适用的统一”,
最高法院拥有对法律条文的最终解释权。为什么“确保法律适用的统一”这么重要呢?
这是因为法律的权威在于它的统一,统一了才有公正,有了公正才能得到人们的尊重,才有权威。

举个例子,情形相似的故意杀人案,在A省判死刑(立即执行),在B省判死缓,
A省的死刑犯家属就会以B省为例子质疑为何判死刑,
B省受害者家属就会以A省为例子质疑为何判死缓,当法律因为失去公正而被人们质疑时,法律就失去了权威。
古人言“不患寡而患不均,不患贫而患不公”说的也是这样的道理。

布雷耶大法官接着谈了谈美国的法院系统。美国的司法体制遵循“宪法至上”原则,而最高法院拥有对宪法的最终解释权。
“最终解释权”是个非常大的权力,这是判定是非的权力,只有手握真理的人才能拥有,在宗教里这样的人是先知。
将这样的权力交给谁是个难题,布雷耶大法官坦承也不能给出确切的答案,他只能谈谈美国人民是怎么处理这个问题的。
在美国,政府握有武力,国会握有财力,交给前者容易形成独裁,交给后者有可能造成法制不公,
因为民选的政客为了讨好选民可能制定出有利大多数选民但却损害少数选民的法律,这将有违法律公平公正的原则。
因此美国人民将这个权力交给了法官们,因为法官没有任何实权,他们无法利用这个权力去干坏事,当然前提是司法独立。
美国最高法院的法官均由总统直接提名经参议院同意后任命。

加纳首席大法官曾问布雷耶大法官:“为什么美国法官说什么人民都会照做?”。
对于这个问题,布雷耶大法官反复强调,“这是一个非常复杂的过程,构建法治的规则并不是一件容易的事情”,
不是有了法律就能有法治。美国建国200多年后能在法治上有如今的成就也是非常不易,
布雷耶大法官通过三个故事来阐述这个过程有多曲折艰难。

第一个故事讲的切诺基诉佐治亚州案。
佐治亚州北部有一个印第安部落切诺基人(the Cherokee)。在美国建国之初,政府和切诺基人签订了诸多条约,
承认切诺基人对土地的所有权,并许诺会保护切诺基人。1829年在诺基人的土地上发现了金矿,
佐治亚州为了占有金矿强占了切诺基人的土地,于是切诺基人聘请了律师上告到了联邦最高法院。
最高法院裁定印第安人胜诉,土地和金矿属于切诺基人。
但当时的总统安德鲁·杰克逊(Andrew Jackson)拒绝执行最高法院的判决,
杰克逊说:“既然是最高法院判的就让他们自己执行去”。
不仅如此,杰克逊还把联邦军队派到佐治亚州去驱逐印第安人,印第安人被迫踏上了迁徙的“血泪之路”。

第二个故事讲的是布朗诉托皮卡教育局案。
19世纪中期美国的内战解放了黑奴,但之后的80年,美国的种族隔离制度仍很严重。
美国南方大概有三分之一的地方,白人和黑人必须分别就读不同公立学校。
1954年美国最高法院判决种族隔离本质上就是一种不平等,隔离教育违反了美国宪法第十四修正案中所保障的同等保护权,
因此违宪。最高法院在判决中明确指出,禁止在南方搞种族隔离制度。
在最高法院1954年作出该判决之后,1955年、1956年南方各州都没有有效的执行该判决。
1957年在阿肯色州,州长奥尔弗·法柏斯(英语:Orval Faubus)命令了当地的国民兵阻挡黑人学童进入当地的
小石城中央中学(英语:Little Rock Central High School)就读。
艾森豪总统与州长谈判后发出指令,联邦政府接管当地所有国民兵,令黑人学童能够顺利进入学校就读。
后来,一群白人包围学校,制造种族暴力事件,企图阻止黑人学童到校上课,
总统随后派遣美国第101空降师中的 1,000 位伞兵来维持秩序。

第三个故事讲的是布什诉戈尔案。
2000年的美国总统大选上演了一场旷日持久的“世纪司法大战”。
大选的投票结果显示双方孰胜孰败的问题仅在毫厘间,戈尔与布什谁赢得佛罗里达州25张选举人票,谁就坐上总统宝座。
民主党人要求在棕榈滩县几个“自己的地盘”上重新进行人工计票。
与此同时,共和党人州务卿哈里斯宣布了计票的最后期限。
无法按时完成人工计票的民主党将官司从州法院打到州最高法院。
布什阵营则向联邦最高法院提出上诉,对佛州最高法院有利于民主党判决的合法性提出质疑。
最高法院受理此案,并于12月4日做出九票一致的判决:搁置州最高法院的判决,将案件发回。
12月8日,佛州最高法院以4∶3票给予戈尔最后的计票机会。
布什阵营再向联邦最高法院提起紧急上诉。最高法院以5∶4票发出立即停止计票的紧急命令。
12月12日,最高法院以同样的票数作出最后裁决:“推翻佛州最高法院继续人工计票的决定。”
布什最终以少数民选票当选总统。
虽然最高法院的这个影响了很多人的判决很不受欢迎,并且不一定对的,但美国人民服从了这个判决,没有暴力发生。

从这三个故事中,我们认识到美国的法治化过程并非一帆风顺,是一系列的历史机缘才塑造出了今天的美国,
因此美国是独一无二的,它的成功只有参考意义而无指导意义。

美国的大法官们也曾遇到判决得不到执行的无奈,在一件又一件的重大案件中,
通过法官们对法律的坚守、执行者与人民对法律的支持,才使美国实现了如今“法官说什么人民都会照做”的法治效果。
为了不破坏司法权威,作为执行者的zf可以选择拒绝执行法院的判决,但不应强迫法院改变判决,丧失了独立性的司法没有权威可言。
司法的权威是通过一系列公正的判决来确立的,斯伟江律师曾说:“正义虽然不在当下,但,我们等得到!”,
对于法院的判决,有时我们无法在当下判断出它的好坏,但是时间终会证明一切。

最后,耶鲁法学院葛维宝教授在评述中说到:”即便在法治较为成熟的国家,建立并维护他们的法律体系也依赖于多方的长久努力”。
法治不是一件一劳永逸的事情,法治的建立维护需要所有人持续不懈的努力。
如果法律得不到人民的支持,大家都不遵守,就将失去效用沦为空谈。
应该通过加强公民教育,让所有人尤其是下一代了解什么是法律,什么是宪法,法治的意义等,
让他们知道参与法治建设是公民的义务,只有这样法治才有希望。

什么是时间

我们常这么说“完成这件事需要时间”,也曾听过这样的话“即使你什么也不做,时间依然在流逝”。
当你在看这篇文章时,时间亦在悄然流逝。那时间到底是什么?

直觉告诉我们,时间是独立于我们意识之外客观存在的,不因我们的悲喜而改变。
当我们停止不动时,时间仍在流逝。但是当时间停止时呢?
在影视作品中,你也许见过这样的画面:当时间停止时,所有的物体都静止不动了,钟摆停摆,
空中的水滴停止下落,声音也消失了(声音靠震动传播),等等。
由此可见,时间是与变化相关的,度量的是万物运动变化的过程。
或许有人会疑问:既然时间是与变化相关的,那么为何我不动的时候时间仍在流逝呢?
这是因为时间是对整个宇宙变化的度量,只要宇宙中的任何事物运动变化了,时间就在走动。

由于宇宙是无穷大的,因此宇宙的变化也是无限的,所以时间是无限的。
从原子这样的微观层面上观察,宇宙万物无时无刻不在变化之中,因此时间是连续的。
连续的事物不便用于度量,必须将其离散化才成一个标准的量才可用于度量,就像重量的单位一样。
自然界中有的事物呈现出大致周期性的运动或变化,人们便以其周期来标识和衡量时间,
这些事物也就成为人们天然的计时器。

例如,古代人通过观察天文,发现了四时的周期变化,把地球绕太阳一周的运动过程划分为一年,
地球自转一圈的运动过程划分为一日。所以我们平常接触到的“年月日时分秒”并不是时间本身,
而只是时间的一种度量单位而已,时间真正的内涵是事物的变化过程。

小时候你也许见过这样的钟摆,为了让钟走起来我们我们需要在后边拧紧一个弹簧,
然后拨动下方的钟摆,于是随着有节奏感的滴答声,钟摆上的秒针随着跳动。
钟摆的原理是借助其内部弹簧的张力,驱动钟摆有节奏的摆动。
随着钟摆的摆动,钟摆内部的弹簧会逐渐变松,因此每过一段时间我们都要去重新拧紧弹簧。
随着弹簧的老化,弹簧的张力会逐渐变弱,钟摆内的齿轮也会生锈,我们发现钟摆的时间会越来越不准了。

在现代科学中经常使用的原子钟,其以原子“能量态”的变化周期作为计量单位,
因为原子“能量态”的变化周期不易受环境的影响,所以原子钟非常稳定,时间非常准确。
1967年,CIPM(国际计量大会)定义秒是铯133原子(Cs133)基态的两个超精细能级之间跃迁所对应的辐射的9,192,631,770个周期所持续的时间。

百科:时间的定义

时间是指宏观一切具有不停止的持续性和不可逆性的物质状态的各种变化过程,其有共同性质的连续事件的度量衡的总称。
时是对物质运动过程的描述,间是指人为的划分。时间是思维对物质运动过程的分割、划分。

AMD APU系列CPU crossfire支持

AMD的APU系列CPU支持crossfire技术,可以支持双显卡。
也就是说APU支持通过crossfire技术同时启用双显卡,提升系统整体的显示性能。

截至本文发表时,AMD共有三款APU:A4、A6、A8,在这三款APU中都集成了一个GPU(显卡),
当主板支持AMD的crossfire技术时,APU中的集成显卡可与主板上另外安装的的独立显卡协同工作,
此时在操作系统中看到的显卡型号既不是APU中集成显卡的型号,也不是主板上独立显卡的型号,而是一个新的显卡型号。

要启用crossfire支持双显卡,APU与独立显卡的搭配也是有要求的,在APU的包装盒侧面会有相关的说明。
APU与显卡配对的列表如下:

http://www.amd.com/us/products/technologies/dual-graphics/Pages/dual-graphics.aspx#3

例如我之前买的 AMD A-Serise X4 A6-3670 ,APU中的集成显卡为 AMD Radeon™ HD 6530D,
要想配对使用启用双显卡支持的话,则必须选择以下ATI芯片之一:
HD 6450/HD 6570/HD 6670
在APU的外包装盒侧面有相应的说明:

其配对使用后在系统中识别出来的显卡型号是新的型号,分别为:
HD 6550D2/HD 6610D2/HD 6690D2

关于双显卡的性能,在网上找到一个 “HD6550D + HD 6570 = HD 6630D2” 性能对比,各位可以参考:

http://www.tomshardware.com/reviews/amd-a8-3850-llano,2975-2.html

装机时没有整清楚APU与显卡的配对关系,买了迪兰恒进(Dataland)HD 6850 无法启用显卡交火技术,
事后终于搞清楚了,所以此文仅为后来者鉴。

T61升级记:8G DDR2内存64位系统

08年初买了一台港版的T61,当时配置是2G内存,160G硬盘(SATA-I),用了这许多年觉得有必要做些升级。
因为笔记本的内存插槽有限,平时开的应用多内存吃的厉害,所以考虑升级为8G内存。

一、可行性分析

升级8G内存需要考虑机器硬件上能否支持,操作系统能否识别。

1、主板是否支持单根4G,双根8G内存

DDR2出现早期,单根4G的内存还不流行,所以一些早起的笔记本其主板是否支持单根4G的内存需要查阅主板的相关技术文档。
网上转了一圈之后,发现 金士顿官网 上有个页面可以查询主板对内存的支持情况。
例如我的T61查询结果为:

主板支持8G内存也就是硬件上就可以支持8G的内存寻址了。

2、操作系统识别8G内存

32位的操作系统最大支持4G的寻址。因为操作系统的信号中断、其他各类硬件设备(如显卡的显存)都要占用一部分寻址地址,
扣除这部分地址之后剩余的地址大小就是你的机器所能识别的最大内存大小了。如果你的机器上硬件设备越多,
那么占用的寻址地址越多,操作系统能用于内存寻址的地址就越少。扣除之后一般在3G~3.5G上下。
这就是为什么你插了4G内存条,操作系统却只识别出3G左右的原因。

为了让操作系统能识别4G以上的内存,需要安装64位操作系统。

2、CPU支持运行64位操作系统

CPU要能够支持64位的指令集才可以运行64位的操作系统。家用PC一般都是X86架构,
那么你的CPU要能够支持X86-64指令集才可运行64位操作系统。
通过 EVEREST 可以查看你的CPU是否支持64位指令集。如图:

支持X86-64指令集的的CPU可以安装 win7_x64 系统,可以兼容运行x86-32位的程序。
如果你的CPU支持的是IA-64指令集,那么你需要安装 win7_IA64 系统。

二、安装64位操作系统注意事项

配置了8G内存,安装win7_x64操作系统时,操作系统分区(一般是C盘)的大小要设置大点。
如果是笔记本的话,建议设置在80G以上(我的就是设置为80G)。
微软官方建议,安装了64位操作系统后系统盘的可用空间应在20G以上。
原因如下:

1、windows的页面文件

windows需要设置虚拟内存,否则有些程序可能无法正常运行。
系统默认配置是从系统盘分配内存大小的 1~1.5倍 空间作为虚拟内存。
所以,当你的内存为8G时,页面文件大小将是8G~14G左右。
页面文件是系统文件,默认是隐藏的,文件名叫pagefile.sys,显示隐藏文件后可见。
如图:

2、windows的休眠功能

当你启用休眠功能时,windows将把内存的快照数据保存到硬盘中,然后关闭系统。
所以快照文件最大可达8G(同内存大小),快照文件的实际大小与当前已使用的内存大小有关。
windows默认将内存快照文件存放在系统盘,该文件是系统文件默认隐藏,文件名叫hiberfil.sys。
见上图。

3、程序与临时文件

有些程序,即使你将其安装目录指向非系统盘,它也需要往系统盘下的程序文件夹或者windows文件夹放入一些文件。
如果你安装的程序很多的话,就将占用较多的磁盘空间。
此外系统的临时目录也在系统盘下,如果你不经常清理的话,那么这些垃圾文件也会占用大量的磁盘空间。

三、效果

机器升级完之后效果如下:

因为显卡可以按比例使用内存作为共享显存,所以显存的大小已经达到3710M!

读书笔记:《温和激进领导》

评价一个人的最终标准,不是看他在舒适和便利的环境中所采取的立场,
而是要看他在挑战和争论中所占据的位置。

马丁.路德.金

把个人的威胁转化为机会

人民的沉默不语,通常不是有意识地作出这样的选择,而是由于他们觉得自己没有选择。

当人们觉得受到威胁的时候,他们就会倾向于变得自我防卫,他们封闭了自己的创造力,并且认为除此之外别无选择。在很多面对面的交往中,人民除了做出本能的反应之外,没有时间去退后一步并进行任何思考。而本能和恐惧往往会导致沉默的结果。简单地说,当我们没有清楚地看到我们有合理和可行的选择的时候,我们就可能觉得自己成了环境的受害者,并且无力对其做出任何事情。

然而恰恰就在我们目睹或者参与威胁性交往的这些时刻,我们可能正面对着一个打破无所作为的恶性循环、有意识地追寻其他有建设性的方法的机会。

将碰撞转变成机会的第一个也是最重要的特征,是人们看到他们在做出反应的时候有选择的余地。

对选择的认知

  • 把交往看做机会
  • 把沉默看做是一种选择
  • 对复杂的“自我”进行考虑
  • 把碰撞非个人化

别样反应

  • 打断事态发展趋势
  • 挑明问题所在
  • 纠正一些设想和行为
  • 转换方向
  • 使用幽默
  • 拖延反应

我相信勇气往往被人错误地看成就是“不会害怕”。如果你顺着一根绳子从悬崖峭壁上爬下来,却丝毫不觉得恐惧的话,那你要么就是疯了,要么就是麻木不仁。勇气是从现实的角度看待你的恐惧,对它作出正确的解释,考虑其他的选择,并且不畏风险地选择采取行动。

莱昂纳德.祖尼(Leonard Zunin)

巧变小胜为大赢

最开始的时候不要受太多规则的束缚。尝试不同的事情,看看哪个能带来最好的结果。追求理想的目标,但是要把它付诸实践。所有取得成功的事情都不是某个事先制定的计划或者某些规则和法令的产物,而是源于一种对意愿和事件进行观察并调整自己的思维方式。

弗洛伦斯.南丁格尔

JAVA调优:字符串操作性能比较

旧文搬迁:2008-5-27

字符串处理代码的效率与编译器对代码的优化算法是相关的。

1、字符串与常量的连接

1
2
3
String str = "abc" + "123" + 456;
//经java编译器优化后,执行的代码等同于:
String str = "abc123456";

2、字符串与变量的连接

1
2
3
4
5
6
7
(1)
int i = 123;
String str = "abc" + i;

(2)
int i = 123;
String str = "" + i;

字符串之间的运算符 + 的实现,实际上是通过新建一个StringBuffer来实现的。

编译之后,实际运行的等效代码如下:

1
2
3
4
5
6
7
8
9
10
11
//(1)
int i = 123;
StringBuffer sbuff = new StringBuffer("abc");
sbuff.append(i);
String str = sbuff.toString();

//(2)
int i = 123;
StringBuffer sbuff = new StringBuffer();
sbuff.append(i);
String str = sbuff.toString();

所以,类似(2)的代码应该使用 String.valueOf(i) 替代,避免创建StringBuffer对象。

1
2
3
4
//(3)
int ia = 123;
int ib = 456;
String str = "abc" + ia + "def" + ib;

等同于:

1
2
3
4
5
6
7
8
//(3)
int ia = 123;
int ib = 456;
StringBuffer sbuff = new StringBuffer("abc");
sbuff.append(ia);
sbuff.append("def");
sbuff.append(ib);
String str = sbuff.toString();

事实上,每个字符串连接的表达式会且仅会创建一个StringBuffer对象。 例:

1
2
3
4
5
//(4)
int ia = 123;
String str = "abc" + ia;
int ib = 456;
str = str + ib;

编译后执行的等价代码是:

1
2
3
4
5
6
7
8
9
10
11
//(4)
int ia = 123;
StringBuffer sbuff = new StringBuffer("abc");
sbuff.append(ia);
String str = sbuff.toString();

int ib = 456;
StringBuffer sbuff = new StringBuffer(str);
sbuff.append("def");
sbuff.append(ib);
str = sbuff.toString();

所以,如果你在进行字符串相加时 使用了多个表达式, 那么一定要使用自己创建的 StringBuffer来进行字符串连接。

3、反编译分析

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
* StringTuning
*
* @author crazyzh1984
* @version 1.0, 2008-5-22
*/
public class StringTuning {

protected static final Log logger = LogFactory.getLog(StringTuning.class);

protected static void test1() {
String str = "abc" + 123;// abc123
logger.debug(str);
}

protected static void test2() {
int i = 123;
String str = "abc" + i;
logger.debug(str);
}

protected static void test3() {
int i = 123;
String str = "" + i;
logger.debug(str);
}

protected static void test4() {
int ia = 123;
int ib = 456;
String str = "abc" + ia + "def" + ib;
logger.debug(str);
}

protected static void test5() {
int ia = 123;
int ib = 456;
String str1 = "abc" + ia;
str1 = str1 + ib;
logger.debug(str1);
}
}

反编译的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
Compiled from "StringTuning.java"
public class StringTuning extends java.lang.Object{
protected static final org.apache.commons.logging.Log logger;

static java.lang.Class class$0;

static {};
Code:
0: getstatic #13; //Field class$0java/lang/Class;
3: dup
4: ifnonnull 32
7: pop
8: ldc #15; //String com.thunisoft.codereview1.StringTuning
10: invokestatic #17; //Method java/lang/Class.forNameLjava/lang/String;)Ljava/lang/Class;
13: dup
14: putstatic #13; //Field class$0java/lang/Class;
17: goto 32
20: new #23; //class java/lang/NoClassDefFoundError
23: dup_x1
24: swap
25: invokevirtual #25; //Method java/lang/Throwable.getMessage)Ljava/lang/String;
28: invokespecial #31; //Method java/lang/NoClassDefFoundError.""Ljava/lang/String;)V
31: athrow
32: invokestatic #35; //Method org/apache/commons/logging/LogFactory.getLogLjava/lang/Class;)Lorg/apache/commons/logging/Log;
35: putstatic #41; //Field loggerorg/apache/commons/logging/Log;
38: return
Exception table:
from to target type
8 13 20 Class java/lang/ClassNotFoundException

public com.thunisoft.codereview1.StringTuning();
Code:
0: aload_0
1: invokespecial #47; //Method java/lang/Object."")V
4: return

//函数1
protected static void test1();
Code:
0: ldc #52; //String abc123 注:字符串赋值为 "abc123"
2: astore_0
3: getstatic #41; //Field loggerorg/apache/commons/logging/Log;
6: aload_0
7: invokeinterface #54, 2; //InterfaceMethod org/apache/commons/logging/Log.debugLjava/lang/Object;)V
12: return

//函数2
protected static void test2();
Code:
0: bipush 123
2: istore_0
3: new #63; //class java/lang/StringBuffer 注:创建了一个StringBuffer对象
6: dup
7: ldc #65; //String abc
9: invokespecial #67; //Method java/lang/StringBuffer.""Ljava/lang/String;)V
12: iload_0
13: invokevirtual #68; //Method java/lang/StringBuffer.appendI)Ljava/lang/StringBuffer;
16: invokevirtual #72; //Method java/lang/StringBuffer.toString)Ljava/lang/String;
19: astore_1
20: getstatic #41; //Field loggerorg/apache/commons/logging/Log;
23: aload_1
24: invokeinterface #54, 2; //InterfaceMethod org/apache/commons/logging/Log.debugLjava/lang/Object;)V
29: return

//函数3
protected static void test3();
Code:
0: bipush 123
2: istore_0
3: new #63; //class java/lang/StringBuffer 注:创建了一个StringBuffer对象
6: dup
7: invokespecial #78; //Method java/lang/StringBuffer."")V
10: iload_0
11: invokevirtual #68; //Method java/lang/StringBuffer.appendI)Ljava/lang/StringBuffer;
14: invokevirtual #72; //Method java/lang/StringBuffer.toString)Ljava/lang/String;
17: astore_1
18: getstatic #41; //Field loggerorg/apache/commons/logging/Log;
21: aload_1
22: invokeinterface #54, 2; //InterfaceMethod org/apache/commons/logging/Log.debugLjava/lang/Object;)V
27: return

//函数4
protected static void test4();
Code:
0: bipush 123
2: istore_0
3: sipush 456
6: istore_1
7: new #63; //class java/lang/StringBuffer 注:创建了一个StringBuffer对象
10: dup
11: ldc #65; //String abc
13: invokespecial #67; //Method java/lang/StringBuffer.""Ljava/lang/String;)V
16: iload_0
17: invokevirtual #68; //Method java/lang/StringBuffer.appendI)Ljava/lang/StringBuffer;
20: ldc #80; //String def
22: invokevirtual #82; //Method java/lang/StringBuffer.appendLjava/lang/String;)Ljava/lang/StringBuffer;
25: iload_1
26: invokevirtual #68; //Method java/lang/StringBuffer.appendI)Ljava/lang/StringBuffer;
29: invokevirtual #72; //Method java/lang/StringBuffer.toString)Ljava/lang/String;
32: astore_2
33: getstatic #41; //Field loggerorg/apache/commons/logging/Log;
36: aload_2
37: invokeinterface #54, 2; //InterfaceMethod org/apache/commons/logging/Log.debugLjava/lang/Object;)V
42: return

//函数5
protected static void test5();
Code:
0: bipush 123
2: istore_0
3: sipush 456
6: istore_1
7: new #63; //class java/lang/StringBuffer 注:创建了一个StringBuffer对象
10: dup
11: ldc #65; //String abc
13: invokespecial #67; //Method java/lang/StringBuffer.""Ljava/lang/String;)V
16: iload_0
17: invokevirtual #68; //Method java/lang/StringBuffer.appendI)Ljava/lang/StringBuffer;
20: invokevirtual #72; //Method java/lang/StringBuffer.toString)Ljava/lang/String;
23: astore_2
24: new #63; //class java/lang/StringBuffer 注:又创建了一个StringBuffer对象
27: dup
28: aload_2
29: invokestatic #88; //Method java/lang/String.valueOfLjava/lang/Object;)Ljava/lang/String;
32: invokespecial #67; //Method java/lang/StringBuffer.""Ljava/lang/String;)V
35: iload_1
36: invokevirtual #68; //Method java/lang/StringBuffer.appendI)Ljava/lang/StringBuffer;
39: invokevirtual #72; //Method java/lang/StringBuffer.toString)Ljava/lang/String;
42: astore_2
43: getstatic #41; //Field loggerorg/apache/commons/logging/Log;
46: aload_2
47: invokeinterface #54, 2; //InterfaceMethod org/apache/commons/logging/Log.debugLjava/lang/Object;)V
52: return

}

对于被定义成final的原始数据类型的常量,会被静态编译到执行的代码中。 例:

1
2
3
4
5
6
7
8
9
protected static final int CODE_TEST = 888;

protected static void test6() {
int ia = 123;
final int ib = 999;
String str = "abc" + CODE_TEST + ia;
str = "abc" + ib + ia;
logger.debug(str);
}

编译后的执行代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
protected static void test6();
Code:
0: bipush 123
2: istore_0
3: sipush 999
6: istore_1
7: new #67; //class java/lang/StringBuffer
10: dup
11: ldc #99; //String abc888 注:"abc" + CODE_TEST 被静态编译为 "abc888"
13: invokespecial #71; //Method java/lang/StringBuffer."":(Ljava/lang/String;)V
16: iload_0
17: invokevirtual #72; //Method java/lang/StringBuffer.append:(I)Ljava/lang/StringBuffer;
20: invokevirtual #76; //Method java/lang/StringBuffer.toString:()Ljava/lang/String;
23: astore_2
24: new #67; //class java/lang/StringBuffer
27: dup
28: ldc #101; //String abc999 注:"abc" + ib 被静态编译为 "abc999"
30: invokespecial #71; //Method java/lang/StringBuffer."":(Ljava/lang/String;)V
33: iload_0
34: invokevirtual #72; //Method java/lang/StringBuffer.append:(I)Ljava/lang/StringBuffer;
37: invokevirtual #76; //Method java/lang/StringBuffer.toString:()Ljava/lang/String;
40: astore_2
41: getstatic #45; //Field logger:Lorg/apache/commons/logging/Log;
44: aload_2
45: invokeinterface #58, 2; //InterfaceMethod org/apache/commons/logging/Log.debug:(Ljava/lang/Object;)V
50: return

4、测试

假定每秒要处理 count 次字符串相加运算,则测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void main(String[] args) {
final int count = 10000;

// 使用字符串相加
long start = System.currentTimeMillis();
String str = "str=";
for (int i = 0; i < count; i++) {
str = str + i;
}
long elapse = System.currentTimeMillis() - start;
logger.info("elapse 1: " + elapse);

// 使用StringBuffer
start = System.currentTimeMillis();
StringBuffer sbuff = new StringBuffer("str=");
for (int i = 0; i < count; i++) {
sbuff.append(i);
}
str = sbuff.toString();
elapse = System.currentTimeMillis() - start;
logger.info("elapse 2: " + elapse);
}

当 count=10000 时,运行结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
2008-5-27 19:50:24 StringTuning main
信息: elapse 1: 968
2008-5-27 19:50:24 StringTuning main
信息: elapse 2: 0

2008-5-27 19:50:36 StringTuning main
信息: elapse 1: 984
2008-5-27 19:50:36 StringTuning main
信息: elapse 2: 0

2008-5-27 19:50:43 StringTuning main
信息: elapse 1: 968
2008-5-27 19:50:43 StringTuning main
信息: elapse 2: 15

当 count=20000 时,运行结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
2008-5-27 19:48:54 StringTuning main
信息: elapse 1: 9281
2008-5-27 19:48:54 StringTuning main
信息: elapse 2: 0

2008-5-27 19:51:04 StringTuning main
信息: elapse 1: 9218
2008-5-27 19:51:04 StringTuning main
信息: elapse 2: 0

2008-5-27 19:51:26 StringTuning main
信息: elapse 1: 9281
2008-5-27 19:51:26 StringTuning main
信息: elapse 2: 16

我们发现,耗费的时间比运算量的增加的更快,创建每个StringBuffer的成本急剧增加。
为什么呢?原因是内存,频繁的对象创建将导致JVM频繁的GC。
我们可以估算一下2种方式创建的对象个数。

方式1创建的对象个数为:
count*2 (每次”+”操作创建一个StringBuffer和一个String对象)

方式2创建的对象个数为:
log2(count)+1 (因为StringBuffer的数组扩容每次乘2)

实际上,扩容的次数跟StringBuffer的初始大小以及要操作的字符串大小有关。

中秋诗一首

仲秋夜万家团圆兮老少欢,
携知己共赏皓月兮意盎然。
为己志奔波奋斗兮甚孤单,
待他日得偿所愿兮归故乡。

敬祝所有在外努力奋斗的人们,中秋快乐!

克艳情史

C克与X艳情比金坚,但二位如何相识诸位知否?

吾羡鸳鸯,深感好奇,遂查史料。

据正史所载,当时的情形是这样的:

某年某月某日,日将晚,日落西山,霞满天下。
时克与艳携游某园,行至水边,湖光山色甚美,驻足远望。
艳于霞光晚风中,丝发轻扬,隐约如仙子下凡。克心荡漾,猝牵艳手。
艳遭此袭,面涨通红,急欲挣脱,怎奈何陈克威猛,摆脱不得!
艳复挣扎,克执益坚。反复数次,终不得脱,艳料天意如此,于是乃降。
克艳情事由此开始。
—— 摘自《陈克列传》

另据野史记载,当时情形如下:

某年某月某日,日将晚,日落西山,霞洒天下。
时克与艳携游某园,行至水边,湖光山色甚美,驻足远望。
克于霞光晚风中,甚为威武,威威然有如泰山。艳见此景,仰慕不已,猝吻克颊。
克遭此袭,面涨通红,手足无措!
艳与曰:“愿侍郎君,矢志不渝!”。
克甚感动,复曰:“得妻如此,夫复何求?”。
相携而归。
—— 摘自《陈克演义》

众说纷纭,无据可考。有诗云:

花开时节,月圆时候
露湿胭脂初透
明月照满西楼
似这般良辰美景
似这般蜜意绸缪
且将淡泊相依
且将殷勤相守
让花常开月常圆人常久

此祝:天长地久!