2006年02月20日

        1.不要为了寂寞去恋爱,时间是个魔鬼,天长日久,如果你是个多情的人,即使不爱对方,到时候也会产生感情,最后你怎么办?
  2.不要为了负责而去结婚。要知道,不爱对方却和对方结婚是最不负责的。即使当时让对方很伤心,但是总比让他几年甚至一辈子伤心强。
  3.不管多大多老,不管家人朋友怎么催,都不要随便对待婚姻,婚姻不是打牌,重新洗牌要付出巨大代价。
  4.感情的事基本上没有谁对谁错,他(她)要离开你,总是你有什么地方不能令他满足,回头想想过去在一起的日子,总是美好的。当然,卑劣的感情骗子也有,他们的花言巧语完全是为了骗取对方和自己上床,这样的人还是极少数。
  5.和一个生活习惯有很多差异的人恋爱不要紧,结婚要慎重,想想你是否可以长久忍受彼此的不同。
  6.有人说恋爱要找自己喜欢的人,结婚要找喜欢自己的人,都是片面的。恋人不喜欢自己有什么可恋的?老婆自己不喜欢怎么过一辈子?
  7.真爱一个人,就要尽量让他开心,他开心了你就会开心,那么双方就有激情了。
  8. 不要因为自己长相不如对方而放弃追求的打算,长相只是一时的印象,真正决定能否结合主要取决于双方的性格。我见过的帅哥配丑女,丑女配帅哥的太多了。
  9. 女人要学会扮靓自己,不要拿朴素来做挡箭牌,不要拿家务做借口,不懂时尚,你就不是一个完整的女人。
  10. 恋爱的时间能长尽量长。这最少有两点好处:一,充分、尽可能长的享受恋爱的愉悦,婚姻和恋爱的感觉是很不同的。二,两人相处时间越长,越能检验彼此是否真心,越能看出两人性格是否合得来。这样婚后的感情就会牢固得多。
  11. 男人不坏,女人不爱,这坏不是指心肠狠毒,自私无情什么的。而是指油嘴滑舌,花言巧语。一般的好男人以为说情话是油嘴滑舌,轻浮肉麻的表现,所以不愿去做。对别人这样说是不对,可是对自己老婆,就要油嘴滑舌一点。为什么不能做个心好嘴滑的男人呢?
  12. 都说婚姻是爱情的坟墓,那是因为婚前已经往去坟墓的路上走着。就算不结婚也会在坟墓前分手。为什么不先分手就一头钻进坟墓呢?
  13. 只会读书的女人是一本字典,再好人们也只会在需要的时候去翻看一下,只会扮靓的女人只是一具花瓶,看久了也就那样。服饰美容是做好一个女人的必要条件,不是充要条件。你还需要多看书。这样你会发现生活更加美好。
  14. 平平淡淡才是真,没错,可那应该是激情过后的平淡,然后再起激情,再有平淡。激情平淡应呈波浪形交替出现。光有平淡无激情的生活有什么意思?只要你真心爱他,到死你也会有激情的。
  15. 你爱他吗?爱就告诉他,何必把思念之苦藏在心底深处。怕样子,地位,身份不相配?别怕,爱一个人是美好的。
  16. 老婆和老妈掉进了河里,我先救老妈,因为是老妈给了我生命,我找不到任何理由丢下她不管。老婆如果没救上来,我可以再给她陪葬,在墓里继续我们的爱情。
  17. 草率地结了婚已经是错了,再也不要草率地去离婚。先试试看,真的不行再离也不迟。
  18. 经常听说男人味女人味,你知道男人味是一种什么味道,女人味又是一种什么味道吗?男人味就是豁达勇敢,女人味就是温柔体贴。
  19. 魅力是什么?魅力不是漂亮,漂亮的女人不一定能吸引我,端庄幽雅的女人我才喜欢。所以你不用担心自己不够漂亮。
  20. 初恋都让人难忘,觉得美好。为什么?不是因为他(她)很漂亮或很帅,也不是因为得不到的就是好的,而是因为人初涉爱河时心里异常纯真,绝无私心杂念,只知道倾己所有去爱对方。而以后的爱情都没有这么纯洁无瑕了。纯真是人世间最为可贵的东西。我们渴求的就是她。
  21. 初恋的人大多都不懂爱,所以初恋失败的多,成功的少。结婚应该找个未婚的,因为谁都喜欢原装。而恋爱,还是找个恋爱过的人才好。因为经历过恋爱的人才知道什么是爱,怎么去爱。
  22. 男人有钱就变坏,是的,很多男人这样,不过,一有钱就变坏的男人就算没钱,也好不到哪里去。
  23. 一个男人能不能给你安全感,完全不取决于他的身高,而取决于他的心高。高大而窝囊的男人我见过不少。矮小而昂扬的男人我也见过。一个男人要心高气傲,这样才像男人。当然,前提是要有才华。
  24. 天长地久有没有?当然有!为什么大多数人不相信有?因为他们没有找到人生旅途中最适合自己的那一个。也就是冥冥中注定的那一个。为什么找不到?茫茫人海,人生如露,要找到最合适自己的那一个谈何容易?你或许可以在40岁时找到上天注定的那一个,可是你能等到40岁吗?在20多岁时找不到,却不得不结婚,在三四十岁时找到却不得不放弃。这就是人生的悲哀。
  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. 如果你赞同以上说法,就请留言,这样可以让更多的人知道这些道理。
  如果你还有补充,更要留言,可以让人知道更多的道理。
  如果你不赞同,同样要留言,因为你更有道理。

1:能不抽烟最好不抽,它或许可以帮助你吸引一些女生,但不抽绝不会招来厌烦,表现男子气概的途径有很多,没必要拿健康做赌注。
2:给自己定目标,一年,两年,五年,也许你出生不如别人好,通过努力,往往可以改变70%的命运。破罐子破摔只能和懦弱做朋友。
3:找女朋友外表是第一关,但要了解她的品行之后再做打算也不迟。
4:不要在乎小钱,工作的人都后悔从前对自己的GF不够好。记住你们的重要日子,你们的谈话,女生要敏感得多,这样做,至少可以证明你对她的重视。
5:爱她,但别怕她,你们是恋人,也是朋友,她要的不是宠物,这样的感情,走不长远。
6:她要是病了,带她去医院,她害怕时,找个人少的地方抱着她,给她勇气,帮她排队,挂号,放下你那点可悲的面子,周围人只会向她投来羡慕的眼光,不会对你说三道四。
7:别把两个人的生活绞在一起,空间才是爱情的长寿药。不要经常吃醋,谁都有异性朋友,该吃的时候才吃,并且让她知道。
8:善待她的朋友,即使她讨厌的人,你也没资格说坏话,你要做的,就是静静的听她倾诉。适当给她安慰。有时候,她们更需要依靠,即使你们都还是学生。
9:不要问她过去,时机到了,她会毫无保留的告诉你。她要是想见从前那位,让她去,原因是你不让她去,她也会去。为何不表现得大度点,但要让她知道你相当的郁闷。
10:珍惜身边人,不要见异思迁,大家都需要安定。即使对方比你GF漂亮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:少玩游戏,这不是韩国,你打不出房子车子还有女人。进了大学,就进了社会,这是场马拉松。北京现在一个砖头抛上去,砸下来7,8个研究生受伤,现在的你是否该有点打算。
36:拿出极限,尽力而为。你要想的是成功,而不是失败。所以面对困难首先就是拿出信心。除了你心爱女人的鼓励,这应该是最有用的东西。
37:定时整理书桌书柜,良好的精神面貌可以让你事半功倍。
38:学好英语,那些说学英语没用的暂时可以不去管,他们要么年纪大了,要么就是自己早过了CET6准备托福了,在这里哗众取宠。你可以不拿证,但一定要学好。
39:不是足够热爱你的专业,并且学出来前途不够光明,趁早转业。现在更多人更看重“钱途”
40:知道自己要干什么,寝室的卧谈会的确可以帮你磨嘴皮子,夜深人静,问问自己,将来的打算,并朝着那个方向去实现。
41:偶尔翻翻时尚类的杂志,女朋友和女性朋友都可以从中受益。
42:尽量少看A片,正常的男人即使是单身,也不会成天迷恋肉欲。而每次你SY后都会有大量锌元素流失,此元素与你大脑活动有密切联系。
43:坚持做运动,俯卧撑可以锻炼你的胸肌和腹肌,请记住游泳圈是成功人事才有资格拥有的

奢侈品。
44:每天早上一杯水,预防胆结实。睡前一小时不要喝水,否则会过早出现眼袋。
45:宁愿要深色袜子也少买白色的,这样会让人觉得你不够成熟,学生朋友自便。
46:新同事或新朋友请你吃饭,不要觉得理所当然,请礼尚往来,否则你的名声会越来越臭。无论是大学还是公司,很多故事都是听来的。
47:有男友的女人不要碰,即便你想one night stand 也要做好心理准备。后果同上,严重时会出现体肤之痛。
48:朋友的女人不要碰,无论是现在的还是曾经的,后果同上。要知道,男人经营自己就像经营一个公司,要树立品牌文化。想问为什么的朋友请先做个你是受害者的假设,再跟我发短消息讨论。
49:博爱的女人不要碰,往往这种女人连自己要什么都不知道,我想没人愿意和若干成年男性分享自己的爱。天作孽,犹可活,自作孽,不可活。
50:没主见,不上进,懒惰的女人不要碰。就算你有钱,你有的是钱,最终你也会厌烦其中。何况不见得她们个个都是花瓶,何况你还有审美疲劳。
51:不以物喜,不以己悲,我知道这很难,但要成功,这是必修课。
52:空闲时间不要全拿去泡BAR,读点文学作品,学习一些经营流程,管理规范,国际时事,法律常识。这能保证你在任何聚会都有谈资。
53:每年回母校看看那些为你付出过的老师,走上社会你才了解她们才是无私的,比起那点学费,她们简直太伟大了。学会感恩。
54:回家帮父母做点简单家务,陪他们买菜,做饭,逛街,冬天送他们一人一件羽绒服,他们并不奢望什么,但他们需要得到你的承认,中国的父母是最苦的,孩子是最幸福的。(离婚除外)
55:大家都年轻,没什么钱,不用太在意谁谁又穿AD ,NIKE ,或者其他。而GF对于PRADA,蓝寇,CD,LV,的热恋,你也不必放在心上,女人天生和美挂上了勾,她们只是宁愿相信你能够为她们买一样昂贵的礼物,以满足她们的虚荣心,然后在同伴面前炫耀一番。实则她们也是热爱生活的,而当你有能力完成时,也会觉得把她包装得漂漂亮亮的很是欣慰。
56:告诉她,你喜欢的内衣。或者在UNDERWEAR店要打佯时陪她去买。
57:欺负她时,请带上套子,如不习惯,请自行解决,直到无法忍受为止,或者泼自己一身冷水。流产很痛苦,我只是听说。
58:有正义感,但请更理智,你父母不希望养育二十多年的儿子化为泡影。但当她遇到流氓时,请你勇敢的挡在她前面。大声说话可以让你的勇气迅速提升。(这是我答辩时认识到的)
59:最好不要在她面前玩天真,多数MM都不喜欢,除非她要求。
60:接吻前先嚼块口香糖,接吻时请闭上眼睛,睁开时,告诉你有多爱她。
61:尊重每一个人,包括为你擦鞋的,卖报的,环卫工人……等等。
62:接到陌生电话请先说,“你好,找哪位”
63:想发脾气时,尽量忍,忍不住就去厕所蹲半个小时,或是找个海拔较高的地方站半个小时。
64:如果GF要跟你吵,尽量克制,不能避免时,跟她吵,吵架是最好的交流方式。你们互相可以得到心里渴望已久的答案。但别带脏字,别把对方的亲戚带出来,最后尽量妥协,有些事,只能暂时妥协。不要把问题留过夜。
65:出差回来给她一个惊喜,并带上礼物。(想在这条上跟我开玩笑的朋友,请私下跟我开)
66:在她兜里放些零钱,在她不常用的兜里放张一百。
67:尊老爱幼,帮助别人后,你会感到无比轻松快乐。
68:去市场买东西,杀价先杀四分之三,现在杀一半行不通了。
69:如果可以,给你的对手留条生路,钱是赚不完的。这个世界上,没有天生的敌人。
70:饮水思源,永远做一个有思想的人,即使你不会有大成就,钱也会足够花。
71:为她学一首歌,如果可能,结婚时当着大家的面唱给她听。
72:要做一件事,成功之前,没必要告诉其他人,成功之后,和他们分享快乐。
73:每年去寺庙为家人点几盏油灯,告诉自己的良心,你不在的时候,同样是爱他们的。
74:学会察言观色,不要意气用事,否则会有许多不必要的麻烦。
75:享受孤独,地球不会因为只有你一个人而停止转动,也许她会很晚才出现,在此之前,你要学会正确利用时间,并且让自己发光发亮。
76:从前的她,深夜给你打电话,如果你还爱她,接电话。如果你不爱她,关机。(没听见不在讨论范围之内)
77:夜里的约会最好不要选择偏僻的地方,有些突发事件,会让你痛不欲生。如果你还爱她的话。
78:公司的东西不要带回家,即使有小便宜,也别参与,在你成为领导前,也别指责,这是你管不到的。
79:开发你的另外一个情感宣泄功能,倾听。
80:朋友之间不要合作做生意,或者办公司。麻烦会接踵而来。你要减轻负担,减小风险,可以,找陌生人。
81:今日事,今日毕,不要日复一日,年复一年。不然到你60岁,你还告诉孙子,爷爷明年一定要毕业!
82:感谢曾经爱过你的人,她祝福你的短信,一定要回。
83:朋友之间发生争执,不要次次都是忍让,每个人都有坚持自己的权利,让他们知道你的想法,你所坚持的。没关系,不出两天,你们又是好朋友。
84:说该说的,不说不该说的。对朋友,也应当有所保留。对她,不要把她和从前那个相提并论,谁都受不了。
85:头发,指甲,胡子,打理好。社会是个排斥性的接受体,这个星球所需要的艺术家极其有限,请不要冒这个险,就算你留长头发比较好看,也要尽量给人干净的感觉。
86:牌局可有可无,那不是年轻人该干的,除非工作需要,否则不要培养这种兴趣。当你看见GF坐在赌博机面前聚精会神的呐喊着某张牌时,你是什么感觉?
87:每学期给自己写总结,上课认真学习,所谓的好好学习,天天向上,学好了,就是最管用的绝招。机会常常伪装成麻烦,从你身边路过,也只会留给做好准备的人。上班的朋友同理。
88:不要整天把国家大事摆在嘴上,去改变你能改变的,接受你无法改变的。
89:选一个生日陪你母亲过,那也是她的受难日。不要年年都和同样一群人过。到头来,全心为你的,只有她。
90:有了手机,尽量少上网,就算你仅仅是看新闻,读文章,大把时间也会不经意从你身边流失。
91:不要以为你是个男人,就不需要保养。至少饮食方面不能太随便,多吃番茄,海产品,韭菜,香蕉,都是对男性健康有益处的食物。你要是看不到价值,我可以告诉你。至少你能把看病节约下来的钱给她多买几盒DIOR。
92:善待小动物,你以后也有子子孙孙,同样是生命,培养一下自己的爱心吧。这并不表示你懦弱。
93:如果考公务员,要有十足的心理准备。早点开始托关系吧,还不见得一定就有收效。
94:力求上进的人,不要总想着靠谁谁,人都是自私的,自己才是最靠得住的人。
95:如果你们相处几年下来,她开始冷落你,对你不闻不问,请别怪她,让她离开。给不了她幸福,给她自由。
96:如果你想和她说分手,请在考试之后,人都是脆弱的。
97:她给你买礼物,你可以高兴,但不要太高兴。人生就是场经营,有人经营感情,有人经营利益,有人经营幸福,而有人经营阴谋。
98:面对失败,不要太计较,天将降大任于斯人也,必先苦其心志,劳其筋骨,饿起体肤……但要学会自责,找到原因,且改掉坏习惯。二十岁没钱,那很正常,三十岁没钱,那是宿命。
99:学会微笑,以后在很多场合都用得上它。如何让微笑好看,首先你得拥有健康的牙齿。如何保证牙齿健康,一,早晚,饭后刷牙;二,每年去探望一次牙科医生;三,少管闲事。
100:男人做什么事心中要有8个大字:坚持到底,充满信心。
2006年02月03日

(特别声明:本文章来源于最富有争议性的凤凰网,我仅仅是转载了其中一篇最有争议性和挑战性的文章,供大家来讨论和分析,我本人并没有任何的歧视和诋毁的意思,出发点仅仅是灌水和转帖。)       ■※■※■※■※■※■※      我个人认为这样的帖子从一个侧面真实的反映了社会的残酷,所有的有争议性话题的帖子,都不应该删除。这样的帖子就应该有自己自由呼吸的声音,那是时代的进步。。。      一个好的论坛就应该是包罗万象,百花齐放,无论是什么样的声音,都应该在这里产生共鸣,这才是一个好的论坛,民主和自由的真正体现。能够包容那些敏感话题,犀利观点,尖锐立场的所有不同声音,那才是一个真正的,进步的,有活力的论坛。      嫁人千万不要嫁农村的姐妹们,为了大家的终生幸福,嫁人不要嫁农村的!      当你谈恋爱的时候,你会觉得无比的甜蜜和幸福。因为能够到城里来念大学、和你相识的男孩子,都是从农村跳龙门走出来的优秀分子,他们坚定、上进、能吃苦、有一定的阅历,和他们在一起总是会对生活充满憧憬。   但这一切只能局限在纯粹的感情层面上,当你们要从恋爱进入“结婚”,走进现实生活的时候,就会发现原来感情在现实的力量面前远没有你所想象的那么强大。      第一眼见到他,你可能已经被他迷住了。   终于,你们走到一起了,走过了4年被象牙塔包围不受现实力量侵害的日子,终于要开始面对现实了。他凭着他的优秀,能拿到较高的工资。他凭着他的上进心,能够在事业上开创一番局面。他凭着他从农村出来的淳朴和责任心,让你绝对没有情敌的威胁。你会感到那么心醉,觉得世间好男人就是这样的了。   事实真的是这样的么?      首先是生活观念的冲突。   当你们小时候被父母带着去游乐园吃棉花糖坐过山车的时候,你们的男朋友是在地里耍泥巴。当你们7岁生日礼物是一架钢琴——也许你并不喜欢,他们可能在那个时候才得到人生第一双属于自己的袜子。当你们高三时候父母总是会鼓励你们考好成绩上好大学,他们的父母在忧心成绩优秀的儿子能不能交上昂贵的学费。   由于这样生活环境的不同,在将来漫长的生活当中你才会发现你们之间共同语言有多么苍白,话题渐渐稀少。他或许更喜欢沉溺在网络游戏或者哥们喝酒聊天当中,你也许和女伴一道逛街买名牌时间更多。就连看一部电影你们的着眼点都会有很大差异,你才会发现——沟通原来这么困难。      然后就是不可避免的经济问题。   你是家里的姣姣女,你的父母靠着一辈子的辛苦有了一定的积蓄和家底,当然不会让自己心爱的女儿吃苦。所以他们会把你的男朋友照顾得很好,给你们足够多的钱,甚至让你们娱乐的时候也不用担心花费太大。你的他花钱可能会很节省,但如果他的家庭向工资高的他伸手的时候,无论是你还是他都没有任何理由拒绝。基于对他的爱,可能连你都会鼓励他给更多的钱。      结婚,前奏就是房子。你的父母也许可以和你坐下来谈谈,拿出5万10万来,作为房子的首付款或者装修钱。   他呢?你完全不必奢望。他连上大学的钱都是困难重重,已经工作并且成为顶梁柱的他怎么可能从父母手中再拿钱,而且是大笔的钱?那他住着你父母提供的屋子心里会平衡么?   他也许会有很多的兄弟姐妹,都期待着能够进入城市——城市和农村收入差距已经是一个不容忽视的社会问题。他有义务一个又一个的解决。一个兄弟要念职业学校了,学费要指望他。一个妹妹要念美容班了,学费指望他。接踵而至的花销可以把他的工资和本来就不多的存款用得七七八八,最可怕的就是你的小家里必须时不时接待来自乡下的等待救济的亲戚们。      然后呢?生病了怎么办?   现在进医院价钱高到离谱已经是众人皆知的了。你和他生病自然不会很担心,有医疗保险,当然,同为市民的你的父母也完全没有这方面的担心。但是他的父母呢?在农村,一辈子操劳的老人多容易检查出大病。医院肯定不会管你是高工资的还是年收入不会超过5000的农村家庭的,大棒照样举起来。他们没有任何这样的保障啊。而当他的父母生病了,尤其是大病,他这个“有出息”的儿子难道可以袖手旁观么?那就绝对不是他的工资可以负担的了,十万八万可以眼睛都不眨一下就流出去,而已经身为他另一半的你自然必须同样承担这样的命运。你还想奢望你们的小孩上好学校?你还想奢望存钱买一辆属于自己的车?你还想换一间大一点的热闹地段的房子?      如果你生了女孩?   不要以为他是农村的娶了你这个有学历有家底的城市女孩就会有什么中奖的想法。重男轻女的想法就算他没有,他的父母肯定也是根深蒂固的。我认识的一个人,嫁了个农村的男孩子,本来两人生活挺幸福的,互相牺牲都很多,不过对生活还是非常有信心。后来她生个女儿,她老公的妈妈当着她的面对他儿子说:“和你老婆离婚,咱们花钱去娶一个黄花闺女来给咱们家生儿子!”还在坐月子的她当时就冲出去,她说她第一次有了和老公离婚的想法。固然她老公百般安慰,但如果是你,你能接受这样的公婆?      就说这些了,我相信会遭到很多来自农村的男人的猛烈攻击,也会遭到很多正在恋爱的女人的唾骂和怀疑。不过这仅仅代表我的想法,因为我实实在在生活在这个现实社会里,而现实绝对不是凭一个人的信念就能轻易改变的。   如果看文章的你已经充分认识到这些问题并且有了充足的心理准备和信心,那我衷心祝福你得到真正的幸福生活,因为你是爱情的勇者!

2006年01月11日

总览

许多刚刚学习Struts的程序员在使用Struts的MessageResources特性的时候会遭遇很多困难。本文将试图阐述MessageResources特性的优点并给出了具体的例子说明它的用法。

作者: Nick Heudecker, System Mobile Inc.

概述

类MessageResources可以使开发者方便地支持多语言,包括支持多时间格式和数字格式。使用资源包的另一个好处是允许开发者将标签字符串集中存储在一个位置,而不是分散在不同的JSP页面里。例如,对于每个用户的名字的标签"First Name" ,我们可以将它写在资源包中,在适当的地方通过Struts标签简单的进行引用:

<bean:write key="label.first.name"/>

这样做将会让你对程序的更改变的简单容易,你不必在每一个JSP页面里更改标签的内容了。

用法

使用消息资源包需要你做下面的事情:

1. 为你想要支持的地方创建一个消息资源包。
2. 配置WEB应用,加载消息资源包。
3. 使用相应的JSP标签加载资源或者…
4. …在一个Action类中加载资源。

创建资源包

MessageResources 类的默认的实现是一个包含"key=value" 对的文件,下面的一个消息资源包文件的例子。

label.username=Username
label.password=Password
label.first.name=First Name
label.last.name=Last Name
label.email=Email Address
label.phone.number=Phone Number
label.welcome=Welcome back {0} {1}!
error.min.length=The input must be at least {0} characters in length.
error.max.length=The input cannot be longer than {0} characters in length.

大括号包围的整数是对java.text.MessageFormat 类的支持,程序员可以向value字符串中传递参数,对每个value字符串你最多可以传递4个参数。

配置

有两种途径通知Struts你的资源包的位置:web.xml 文件或者struts-config.xml 文件。首先来看web.xml 文件的配置:

<servlet>
<servlet-name>action</servlet-name>
<servlet-class>
    org.apache.struts.action.ActionServlet
</servlet-class>
<init-param>
<param-name>
    application
</param-name>
<param-value>
    com.systemmobile.example.ApplicationResources
</param-value>
</init-param>
</servlet>

这个配置说明你的资源包的名字是ApplicationResources.properties,它位于com.systemmobile.example 包中。后缀".properties" 是隐含的,你不必显式地写出来。如果你还有另一个资源文件在相同的包中,例如ApplicationResources_fr.properties ,用来支持法语,你只需要象上面定义的那样列出文件名字即可。

定义资源文件的第二中方法(上面已经提到),是在struts-config.xml 文件中配置:

<message-resources parameter="com.systemmobile.example.ApplicationResources"/>

属性parameter 是必须的。和在web.xml文件中配置一样, 需要注意的是文件在包中的位置。

使用struts-config.xml 文件来配置消息资源文件是推荐的做法,因为它更有可扩展性,更灵活。

  • 你可以使用message-resources 标签从不同的资源文件取不同的消息,前提是在配置的时候为不同的资源文件给出不同的key 属性的值。例如:
    <message-resources key="myResources" parameter="com.systemmobile.example.
    ApplicationResources"/> <message-resources key="moreResources" parameter="com.systemmobile.example.
    MoreApplicationResources"/>

    然后你必须这样使用bean:message 标签:

    <bean:message bundle="moreResources" key="some.message.key"/>
  • 设置属性null 的值为"false" 后,如果某个资源字符串不存在将返回???key??? 而不是仅仅显示null。这样很容易在JSP页面中看到你没有定义的资源,使得内部测试的速度更快。(关于如何从资源文件中获得消息的详细内容参见国际化 一节)
    <message-resources parameter="com.systemmobile.example.ApplicationResources" null="false"/>
  • 另外,message-resources 标签允许你使用自己实现的MessageResourcesFactory 接口,这不在本文的讨论范围。

资源文件放在哪里

关于资源文件最常见的问题是将资源文件放在WAR文件的哪里。简单的回答是该文件必须放在你的classpath下面,这意味着将资源文件放在一个JAR 文件中,或者放在/WEB-INF/classes 目录极其子目录下。下表给出了资源文件的位置,在message-resources 标签中"parameter" 属性的值以及简短的说明。

Resources Location parameter Value Description
/WEB-INF/classes/ApplicationResources.properties ApplicationResources 文件放在classes 目录下, 该目录在web应用的classpath中.
/WEB-INF/classes/resources/ApplicationResources.properties resources.ApplicationResources 该文件放在"resources" 目录下, 所以包名也就是路径名要给出。
In the app.jar file, in the com.systemmobile.example package/directory. com.systemmobile.example.ApplicationResources 文件在JAR文件中的全路径。

Tags

最常用Struts 标签是bean:message 标签。使用这个标签的"key" 可以从资源文件中读特定的消息资源。你还可以传入四个参数中的一个或全部:

<bean:message key="label.password"/>
<bean:message key="error.min.length" arg0="6"/>
<bean:message key="label.welcome" arg0="Ralph" arg1="Nader"/>

html:message 可以让你向用户显示错误信息(默认)或消息信息,而html:errors 只显示错误信息。很明显,错误信息或消息信息一定要保存在request里,否则就什么也不会显示。这里有一个显示消息信息的例子:

<logic:messagesPresent message="true">
  <html:messages id="msg" message="true">
    <div class="success">
      <bean:write name="msg"/>
    </div><br/>
  </html:messages>
</logic:messagesPresent>

还有一些标签也有限地支持消息资源,比如html:link。html:link标签通过定义"titleKey" 属性来显示标题文本。许多html 使用 "altKey" 属性从资源文件里获得alternate(替代)文本。

Actions

你还可以在Action 类中使用消息资源文件。Action 类有两个方法得到一个MessageResource 类的实例:

// 返回一个request里的资源文件
protected MessageResources getResources(HttpServletRequest request);
// 返回一个request里的资源文件,
// 该资源文件的标志上<message-resources/> 元素的内容
protected MessageResources getResources(javax.servlet.http.HttpServletRequest request,
java.lang.String key);

MessageResources类可以让你从资源文件中得到本地化的消息。The API for MessageResources 可以在资源中找到。比较常用的方法有:

// these methods load a resources key for the given locale
public String getMessage(java.util.Locale locale, java.lang.String key);
public String getMessage(java.util.Locale locale, java.lang.String key,
       java.lang.Object arg0);
public String getMessage(java.util.Locale locale, java.lang.String key,
       java.lang.Object[] args);
public String getMessage(java.util.Locale locale, java.lang.String key,
       java.lang.Object arg0, java.lang.Object arg1)
public String getMessage(java.util.Locale locale, java.lang.String key,
       java.lang.Object arg0, java.lang.Object arg1, java.lang.Object arg2);
public String getMessage(java.util.Locale locale, java.lang.String key, java.lang.Object arg0,
       java.lang.Object arg1, java.lang.Object arg2, java.lang.Object arg3);
// these methods load a resources key for the locale retrieved
// from the HttpServletRequest
public String getMessage(java.lang.String key);
public String getMessage(java.lang.String key, java.lang.Object arg0);
public String getMessage(java.lang.String key, java.lang.Object[] args);
public String getMessage(java.lang.String key, java.lang.Object arg0,
       java.lang.Object arg1);
public String getMessage(java.lang.String key, java.lang.Object arg0,
       java.lang.Object arg1, java.lang.Object arg2);
public String getMessage(java.lang.String key, java.lang.Object arg0,
       java.lang.Object arg1, java.lang.Object arg2, java.lang.Object arg3);

这些返回的字符串可以被设置成request 或 session 的参数并串会表现层。你可能注意到了一些重载方法getMessage(…) 选择了参数Object,而另外一些采用了参数arg0…arg3。这和 bean:message arg0…arg3 属性等价。

除了MessageResources 类,还有一些类使用了资源文件。ActionMessage类被用来从action 向JSP之间传递消息资源中的keys 。消息被用来作为bean 的属性。ActionError, ActionMessage的子类,使用消息资源中的keys 存储验证失败后的错误信息。

国际化

从资源文件中提取一个本地化信息可以由类MessageResources 来处理,或者由它的直接子类PropertyMessageResources类处理。既然类PropertyMessageResources 等经常地被用到,那么我们就来看看它是怎样使用getMessage(Locale, String) 方法来从资源文件中读取消息的。

举例说明:

1. 如果你在ApplicationResources_pt_br.properties (Brazilian Portuguese)中没有发现消息的定义,系统将在ApplicationResources_pt.properties 文件中找,如果ApplicationResources_pt.properties 文件不存在或者也没有该消息,那就去ApplicationResources.properties 文件里查找了。
2. 如果消息找到了,它就被加到本地化的缓存中并返回java.lang.String型数据。
3. 如果消息没有找到,此时如果returnNull 属性被为默认的true,将返回 null。 否则将返回类似 ???key??? 的字符串,key 就是那个被传递的参数。

JSTL

JSTL (JavaServer Pages Standard Tag Library) 的fmt标签最近开始流行起来,用来向JSP中显示资源文件的信息。它能很好地和Struts结合在一起。使用它非常简单,只要下载JSTL 的jar 文件和TLDs 并把它们拷贝到你的应用的正确的位置,然后在web.xml文件中加入下面的内容:

<context-param>
  <param-name>javax.servlet.jsp.jstl.fmt.localizationContext</param-name>
  <param-value>ApplicationResources</param-value>
</context-param>

上面的配置是假定你的ApplicationResources.properties文件是放在/WEB-INF/classes 目录下的。 参见above 更多情况。

然后将这个标签库直接放在你的JSP中:

<%@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt" %>

最后,下面的标签将显示资源文件的内容:

<fmt:message key="label.first.name"/>

还有一个使用fmt 标签获得资源的例子。(注意: 该段程序取自Jakarta JSTL examples。)

// loading a resource from a specific bundle and populating a parameter
<fmt:message key="currentTime" bundle="${deBundle}">
 <fmt:param value="${currentDateString}"/>
</fmt:message>
// using the forEach iterator to populate paramters
<fmt:message key="serverInfo" bundle="${deBundle}">
 <c:forEach var="arg" items="${serverInfoArgs}">
  <fmt:param value="${arg}"/>
 </c:forEach>
</fmt:message>

结论

在向JSP文件方便地传递消息的同时,Struts使用消息资源文件还帮助我们创建国际化的Web应用。我们既可以使用正在快速发展中的JSTL标签,也可以使用Struts标签,或直接在action中得到一条具体的消息。我希望这篇文章为您阐明了一些Struts中常用的但有时会混淆的东西。

关于作者

Nick Heudecker 是一位软件开发人员,具有6年的企业应用的开发经验。 他所在的公司, System Mobile, Inc.,专门从事应用集成,定制软件开发和无线应用。 他还是Sun认证JAVA程序员,现在居住在Ann Arbor, Michigan。

资源

下面的资源也许对您了解更多的关于Struts资源文件有帮助:

注意事项

Packages are just directory structures used to avoid naming conflicts. If you put a message bundle in a directory named resources under /WEB-INF/classes, this is the equivalent of putting the file in a package named resrouces. While basic, this point seems to trip up many new programmers.

2005年12月19日

java语言已经内置了多线程支持,所有实现Runnable接口的类都可被启动一个新线程,新线程会执行该实例的run()方法,当run()方法执行完毕后,线程就结束了。一旦一个线程执行完毕,这个实例就不能再重新启动,只能重新生成一个新实例,再启动一个新线程。

Thread类是实现了Runnable接口的一个实例,它代表一个线程的实例,并且,启动线程的唯一方法就是通过Thread类的start()实例方法:

Thread t = new Thread();
t.start();

start()方法是一个native方法,它将启动一个新线程,并执行run()方法。Thread类默认的run()方法什么也不做就退出了。注意:直接调用run()方法并不会启动一个新线程,它和调用一个普通的java方法没有什么区别。

因此,有两个方法可以实现自己的线程:

方法1:自己的类extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。例如:

public class MyThread extends Thread {
    public run() {
        System.out.println("MyThread.run()");
    }
}

在合适的地方启动线程:new MyThread().start();

方法2:如果自己的类已经extends另一个类,就无法直接extends Thread,此时,必须实现一个Runnable接口:

public class MyThread extends OtherClass implements Runnable {
    public run() {
        System.out.println("MyThread.run()");
    }
}

为了启动MyThread,需要首先实例化一个Thread,并传入自己的MyThread实例:

MyThread myt = new MyThread();
Thread t = new Thread(myt);
t.start();

事实上,当传入一个Runnable target参数给Thread后,Thread的run()方法就会调用target.run(),参考JDK源代码:

public void run() {
    if (target != null) {
        target.run();
    }
}

线程还有一些Name, ThreadGroup, isDaemon等设置,由于和线程设计模式关联很少,这里就不多说了。

由于同一进程内的多个线程共享内存空间,在Java中,就是共享实例,当多个线程试图同时修改某个实例的内容时,就会造成冲突,因此,线程必须实现共享互斥,使多线程同步。

最简单的同步是将一个方法标记为synchronized,对同一个实例来说,任一时刻只能有一个synchronized方法在执行。当一个方法正在执行某个synchronized方法时,其他线程如果想要执行这个实例的任意一个synchronized方法,都必须等待当前执行 synchronized方法的线程退出此方法后,才能依次执行。

但是,非synchronized方法不受影响,不管当前有没有执行synchronized方法,非synchronized方法都可以被多个线程同时执行。

此外,必须注意,只有同一实例的synchronized方法同一时间只能被一个线程执行,不同实例的synchronized方法是可以并发的。例如,class A定义了synchronized方法sync(),则不同实例a1.sync()和a2.sync()可以同时由两个线程来执行。

多线程同步的实现最终依赖锁机制。我们可以想象某一共享资源是一间屋子,每个人都是一个线程。当A希望进入房间时,他必须获得门锁,一旦A获得门锁,他进去后就立刻将门锁上,于是B,C,D…就不得不在门外等待,直到A释放锁出来后,B,C,D…中的某一人抢到了该锁(具体抢法依赖于 JVM的实现,可以先到先得,也可以随机挑选),然后进屋又将门锁上。这样,任一时刻最多有一人在屋内(使用共享资源)。

Java语言规范内置了对多线程的支持。对于Java程序来说,每一个对象实例都有一把“锁”,一旦某个线程获得了该锁,别的线程如果希望获得该锁,只能等待这个线程释放锁之后。获得锁的方法只有一个,就是synchronized关键字。例如:

public class SharedResource {
    private int count = 0;

    public int getCount() { return count; }

    public synchronized void setCount(int count) { this.count = count; }

}

同步方法public synchronized void setCount(int count) { this.count = count; } 事实上相当于:

public void setCount(int count) {
    synchronized(this) { // 在此获得this锁
         this.count = count;

    } // 在此释放this锁
}

红色部分表示需要同步的代码段,该区域为“危险区域”,如果两个以上的线程同时执行,会引发冲突,因此,要更改SharedResource的内部状态,必须先获得SharedResource实例的锁。

退出synchronized块时,线程拥有的锁自动释放,于是,别的线程又可以获取该锁了。

为了提高性能,不一定要锁定this,例如,SharedResource有两个独立变化的变量:

public class SharedResouce {
    private int a = 0;
    private int b = 0;

    public synchronized void setA(int a) { this.a = a; }

    public synchronized void setB(int b) { this.b = b; }
}

若同步整个方法,则setA()的时候无法setB(),setB()时无法setA()。为了提高性能,可以使用不同对象的锁:

public class SharedResouce {
    private int a = 0;
    private int b = 0;
    private Object sync_a = new Object();
    private Object sync_b = new Object();

    public void setA(int a) {
        synchronized(sync_a) {
            this.a = a;
        }
    }

    public synchronized void setB(int b) {
        synchronized(sync_b) {
            this.b = b;
        }
    }
}

通常,多线程之间需要协调工作。例如,浏览器的一个显示图片的线程displayThread想要执行显示图片的任务,必须等待下载线程 downloadThread将该图片下载完毕。如果图片还没有下载完,displayThread可以暂停,当downloadThread完成了任务后,再通知displayThread“图片准备完毕,可以显示了”,这时,displayThread继续执行。

以上逻辑简单的说就是:如果条件不满足,则等待。当条件满足时,等待该条件的线程将被唤醒。在Java中,这个机制的实现依赖于wait/notify。等待机制与锁机制是密切关联的。例如:

synchronized(obj) {
    while(!condition) {
        obj.wait();
    }
    obj.doSomething();
}

当线程A获得了obj锁后,发现条件condition不满足,无法继续下一处理,于是线程A就wait()。

在另一线程B中,如果B更改了某些条件,使得线程A的condition条件满足了,就可以唤醒线程A:

synchronized(obj) {
    condition = true;
    obj.notify();
}

需要注意的概念是:

# 调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) {…} 代码段内。

# 调用obj.wait()后,线程A就释放了obj的锁,否则线程B无法获得obj锁,也就无法在synchronized(obj) {…} 代码段内唤醒A。

# 当obj.wait()方法返回后,线程A需要再次获得obj锁,才能继续执行。

# 如果A1,A2,A3都在obj.wait(),则B调用obj.notify()只能唤醒A1,A2,A3中的一个(具体哪一个由JVM决定)。

# obj.notifyAll()则能全部唤醒A1,A2,A3,但是要继续执行obj.wait()的下一条语句,必须获得obj锁,因此,A1,A2,A3只有一个有机会获得锁继续执行,例如A1,其余的需要等待A1释放obj锁之后才能继续执行。

# 当B调用obj.notify/notifyAll的时候,B正持有obj锁,因此,A1,A2,A3虽被唤醒,但是仍无法获得obj锁。直到B退出synchronized块,释放obj锁后,A1,A2,A3中的一个才有机会获得锁继续执行。


前面讲了wait/notify机制,Thread还有一个sleep()静态方法,它也能使线程暂停一段时间。sleep与wait的不同点是: sleep并不释放锁,并且sleep的暂停和wait暂停是不一样的。obj.wait会使线程进入obj对象的等待集合中并等待唤醒。

但是wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedException。

如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在 wait/sleep/join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。

需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用 interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join()后,就会立刻抛出InterruptedException。


GuardedSuspention模式主要思想是:

当条件不满足时,线程等待,直到条件满足时,等待该条件的线程被唤醒。

我们设计一个客户端线程和一个服务器线程,客户端线程不断发送请求给服务器线程,服务器线程不断处理请求。当请求队列为空时,服务器线程就必须等待,直到客户端发送了请求。

先定义一个请求队列:Queue

package com.crackj2ee.thread;

import java.util.*;

public class Queue {
    private List queue = new LinkedList();

    public synchronized Request getRequest() {
        while(queue.size()==0) {
            try {
                this.wait();
            }
            catch(InterruptedException ie) {
                return null;
            }
        }
        return (Request)queue.remove(0);
    }

    public synchronized void putRequest(Request request) {
        queue.add(request);
        this.notifyAll();
    }

}

蓝色部分就是服务器线程的等待条件,而客户端线程在放入了一个request后,就使服务器线程等待条件满足,于是唤醒服务器线程。

客户端线程:ClientThread

package com.crackj2ee.thread;

public class ClientThread extends Thread {
    private Queue queue;
    private String clientName;

    public ClientThread(Queue queue, String clientName) {
        this.queue = queue;
        this.clientName = clientName;
    }

    public String toString() {
        return "[ClientThread-" + clientName + "]";
    }

    public void run() {
        for(int i=0; i<100; i++) {
            Request request = new Request("" + (long)(Math.random()*10000));
            System.out.println(this + " send request: " + request);
            queue.putRequest(request);
            try {
                Thread.sleep((long)(Math.random() * 10000 + 1000));
            }
            catch(InterruptedException ie) {
            }
        }
        System.out.println(this + " shutdown.");
    }
}

服务器线程:ServerThread

package com.crackj2ee.thread;
public class ServerThread extends Thread {
    private boolean stop = false;
    private Queue queue;

    public ServerThread(Queue queue) {
        this.queue = queue;
    }

    public void shutdown() {
        stop = true;
        this.interrupt();
        try {
            this.join();
        }
        catch(InterruptedException ie) {}
    }

    public void run() {
        while(!stop) {
            Request request = queue.getRequest();
            System.out.println("[ServerThread] handle request: " + request);
            try {
                Thread.sleep(2000);
            }
            catch(InterruptedException ie) {}
        }
        System.out.println("[ServerThread] shutdown.");
    }
}

服务器线程在红色部分可能会阻塞,也就是说,Queue.getRequest是一个阻塞方法。这和java标准库的许多IO方法类似。

最后,写一个Main来启动他们:

package com.crackj2ee.thread;

public class Main {

    public static void main(String[] args) {
        Queue queue = new Queue();
        ServerThread server = new ServerThread(queue);
        server.start();
        ClientThread[] clients = new ClientThread[5];
        for(int i=0; i<clients.length; i++) {
            clients[i] = new ClientThread(queue, ""+i);
            clients[i].start();
        }
        try {
            Thread.sleep(100000);
        }
        catch(InterruptedException ie) {}
        server.shutdown();
    }
}

我们启动了5个客户端线程和一个服务器线程,运行结果如下:

[ClientThread-0] send request: Request-4984
[ServerThread] handle request: Request-4984
[ClientThread-1] send request: Request-2020
[ClientThread-2] send request: Request-8980
[ClientThread-3] send request: Request-5044
[ClientThread-4] send request: Request-548
[ClientThread-4] send request: Request-6832
[ServerThread] handle request: Request-2020
[ServerThread] handle request: Request-8980
[ServerThread] handle request: Request-5044
[ServerThread] handle request: Request-548
[ClientThread-4] send request: Request-1681
[ClientThread-0] send request: Request-7859
[ClientThread-3] send request: Request-3926
[ServerThread] handle request: Request-6832
[ClientThread-2] send request: Request-9906
……

可以观察到ServerThread处理来自不同客户端的请求。

思考

Q: 服务器线程的wait条件while(queue.size()==0)能否换成if(queue.size()==0)?

A: 在这个例子中可以,因为服务器线程只有一个。但是,如果服务器线程有多个(例如Web应用程序有多个线程处理并发请求,这非常普遍),就会造成严重问题。

Q: 能否用sleep(1000)代替wait()?

A: 绝对不可以。sleep()不会释放锁,因此sleep期间别的线程根本没有办法调用getRequest()和putRequest(),导致所有相关线程都被阻塞。

Q: (Request)queue.remove(0)可以放到synchronized() {}块外面吗?

A: 不可以。因为while()是测试queue,remove()是使用queue,两者是一个原子操作,不能放在synchronized外面。

总结

多线程设计看似简单,实际上必须非常仔细地考虑各种锁定/同步的条件,稍不小心,就可能出错。并且,当线程较少时,很可能发现不了问题,一旦问题出现又难以调试。

所幸的是,已有一些被验证过的模式可以供我们使用,我们会继续介绍一些常用的多线程设计模式。

前面谈了多线程应用程序能极大地改善用户相应。例如对于一个Web应用程序,每当一个用户请求服务器连接时,服务器就可以启动一个新线程为用户服务。

然而,创建和销毁线程本身就有一定的开销,如果频繁创建和销毁线程,CPU和内存开销就不可忽略,垃圾收集器还必须负担更多的工作。因此,线程池就是为了避免频繁创建和销毁线程。

每当服务器接受了一个新的请求后,服务器就从线程池中挑选一个等待的线程并执行请求处理。处理完毕后,线程并不结束,而是转为阻塞状态再次被放入线程池中。这样就避免了频繁创建和销毁线程。

Worker Pattern实现了类似线程池的功能。首先定义Task接口:

package com.crackj2ee.thread;
public interface Task {
    void execute();
}

线程将负责执行execute()方法。注意到任务是由子类通过实现execute()方法实现的,线程本身并不知道自己执行的任务。它只负责运行一个耗时的execute()方法。

具体任务由子类实现,我们定义了一个CalculateTask和一个TimerTask:

// CalculateTask.java
package com.crackj2ee.thread;
public class CalculateTask implements Task {
    private static int count = 0;
    private int num = count;
    public CalculateTask() {
        count++;
    }
    public void execute() {
        System.out.println("[CalculateTask " + num + "] start…");
        try {
            Thread.sleep(3000);
        }
        catch(InterruptedException ie) {}
        System.out.println("[CalculateTask " + num + "] done.");
    }
}

// TimerTask.java
package com.crackj2ee.thread;
public class TimerTask implements Task {
    private static int count = 0;
    private int num = count;
    public TimerTask() {
        count++;
    }
    public void execute() {
        System.out.println("[TimerTask " + num + "] start…");
        try {
            Thread.sleep(2000);
        }
        catch(InterruptedException ie) {}
        System.out.println("[TimerTask " + num + "] done.");
    }
}

以上任务均简单的sleep若干秒。

TaskQueue实现了一个队列,客户端可以将请求放入队列,服务器线程可以从队列中取出任务:

package com.crackj2ee.thread;
import java.util.*;
public class TaskQueue {
    private List queue = new LinkedList();
    public synchronized Task getTask() {
        while(queue.size()==0) {
            try {
                this.wait();
            }
            catch(InterruptedException ie) {
                return null;
            }
        }
        return (Task)queue.remove(0);
    }
    public synchronized void putTask(Task task) {
        queue.add(task);
        this.notifyAll();
    }
}

终于到了真正的WorkerThread,这是真正执行任务的服务器线程:

package com.crackj2ee.thread;
public class WorkerThread extends Thread {
    private static int count = 0;
    private boolean busy = false;
    private boolean stop = false;
    private TaskQueue queue;
    public WorkerThread(ThreadGroup group, TaskQueue queue) {
        super(group, "worker-" + count);
        count++;
        this.queue = queue;
    }
    public void shutdown() {
        stop = true;
        this.interrupt();
        try {
            this.join();
        }
        catch(InterruptedException ie) {}
    }
    public boolean isIdle() {
        return !busy;
    }
    public void run() {
        System.out.println(getName() + " start.");       
        while(!stop) {
            Task task = queue.getTask();
            if(task!=null) {
                busy = true;
                task.execute();
                busy = false;
            }
        }
        System.out.println(getName() + " end.");
    }
}

前面已经讲过,queue.getTask()是一个阻塞方法,服务器线程可能在此wait()一段时间。此外,WorkerThread还有一个shutdown方法,用于安全结束线程。

最后是ThreadPool,负责管理所有的服务器线程,还可以动态增加和减少线程数:

package com.crackj2ee.thread;
import java.util.*;
public class ThreadPool extends ThreadGroup {
    private List threads = new LinkedList();
    private TaskQueue queue;
    public ThreadPool(TaskQueue queue) {
        super("Thread-Pool");
        this.queue = queue;
    }
    public synchronized void addWorkerThread() {
        Thread t = new WorkerThread(this, queue);
        threads.add(t);
        t.start();
    }
    public synchronized void removeWorkerThread() {
        if(threads.size()>0) {
            WorkerThread t = (WorkerThread)threads.remove(0);
            t.shutdown();
        }
    }
    public synchronized void currentStatus() {
        System.out.println("———————————————–");
        System.out.println("Thread count = " + threads.size());
        Iterator it = threads.iterator();
        while(it.hasNext()) {
            WorkerThread t = (WorkerThread)it.next();
            System.out.println(t.getName() + ": " + (t.isIdle() ? "idle" : "busy"));
        }
        System.out.println("———————————————–");
    }
}

currentStatus()方法是为了方便调试,打印出所有线程的当前状态。

最后,Main负责完成main()方法:

package com.crackj2ee.thread;
public class Main {
    public static void main(String[] args) {
        TaskQueue queue = new TaskQueue();
        ThreadPool pool = new ThreadPool(queue);
        for(int i=0; i<10; i++) {
            queue.putTask(new CalculateTask());
            queue.putTask(new TimerTask());
        }
        pool.addWorkerThread();
        pool.addWorkerThread();
        doSleep(8000);
        pool.currentStatus();
        pool.addWorkerThread();
        pool.addWorkerThread();
        pool.addWorkerThread();
        pool.addWorkerThread();
        pool.addWorkerThread();
        doSleep(5000);
        pool.currentStatus();
    }
    private static void doSleep(long ms) {
        try {
            Thread.sleep(ms);
        }
        catch(InterruptedException ie) {}
    }
}

main()一开始放入了20个Task,然后动态添加了一些服务线程,并定期打印线程状态,运行结果如下:

worker-0 start.
[CalculateTask 0] start…
worker-1 start.
[TimerTask 0] start…
[TimerTask 0] done.
[CalculateTask 1] start…
[CalculateTask 0] done.
[TimerTask 1] start…
[CalculateTask 1] done.
[CalculateTask 2] start…
[TimerTask 1] done.
[TimerTask 2] start…
[TimerTask 2] done.
[CalculateTask 3] start…
———————————————–
Thread count = 2
worker-0: busy
worker-1: busy
———————————————–
[CalculateTask 2] done.
[TimerTask 3] start…
worker-2 start.
[CalculateTask 4] start…
worker-3 start.
[TimerTask 4] start…
worker-4 start.
[CalculateTask 5] start…
worker-5 start.
[TimerTask 5] start…
worker-6 start.
[CalculateTask 6] start…
[CalculateTask 3] done.
[TimerTask 6] start…
[TimerTask 3] done.
[CalculateTask 7] start…
[TimerTask 4] done.
[TimerTask 7] start…
[TimerTask 5] done.
[CalculateTask 8] start…
[CalculateTask 4] done.
[TimerTask 8] start…
[CalculateTask 5] done.
[CalculateTask 9] start…
[CalculateTask 6] done.
[TimerTask 9] start…
[TimerTask 6] done.
[TimerTask 7] done.
———————————————–
Thread count = 7
worker-0: idle
worker-1: busy
worker-2: busy
worker-3: idle
worker-4: busy
worker-5: busy
worker-6: busy
———————————————–
[CalculateTask 7] done.
[CalculateTask 8] done.
[TimerTask 8] done.
[TimerTask 9] done.
[CalculateTask 9] done.

仔细观察:一开始只有两个服务器线程,因此线程状态都是忙,后来线程数增多,6个线程中的两个状态变成idle,说明处于wait()状态。

思考:本例的线程调度算法其实根本没有,因为这个应用是围绕TaskQueue设计的,不是以Thread Pool为中心设计的。因此,Task调度取决于TaskQueue的getTask()方法,你可以改进这个方法,例如使用优先队列,使优先级高的任务先被执行。

如果所有的服务器线程都处于busy状态,则说明任务繁忙,TaskQueue的队列越来越长,最终会导致服务器内存耗尽。因此,可以限制 TaskQueue的等待任务数,超过最大长度就拒绝处理。许多Web服务器在用户请求繁忙时就会拒绝用户:HTTP 503 SERVICE UNAVAILABLE

多线程读写同一个对象的数据是很普遍的,通常,要避免读写冲突,必须保证任何时候仅有一个线程在写入,有线程正在读取的时候,写入操作就必须等待。简单说,就是要避免“写-写”冲突和“读-写”冲突。但是同时读是允许的,因为“读-读”不冲突,而且很安全。

要实现以上的ReadWriteLock,简单的使用synchronized就不行,我们必须自己设计一个ReadWriteLock类,在读之前,必须先获得“读锁”,写之前,必须先获得“写锁”。举例说明:

DataHandler对象保存了一个可读写的char[]数组:

package com.crackj2ee.thread;

public class DataHandler {
    // store data:
    private char[] buffer = "AAAAAAAAAA".toCharArray();

    private char[] doRead() {
        char[] ret = new char[buffer.length];
        for(int i=0; i<buffer.length; i++) {
            ret[i] = buffer[i];
            sleep(3);
        }
        return ret;
    }

    private void doWrite(char[] data) {
        if(data!=null) {
            buffer = new char[data.length];
            for(int i=0; i<buffer.length; i++) {
                buffer[i] = data[i];
                sleep(10);
            }
        }
    }

    private void sleep(int ms) {
        try {
            Thread.sleep(ms);
        }
        catch(InterruptedException ie) {}
    }
}

doRead()和doWrite()方法是非线程安全的读写方法。为了演示,加入了sleep(),并设置读的速度大约是写的3倍,这符合通常的情况。

为了让多线程能安全读写,我们设计了一个ReadWriteLock:

package com.crackj2ee.thread;
public class ReadWriteLock {
    private int readingThreads = 0;
    private int writingThreads = 0;
    private int waitingThreads = 0; // waiting for write
    private boolean preferWrite = true;

    public synchronized void readLock() throws InterruptedException {
        while(writingThreads>0 || (preferWrite && waitingThreads>0))
            this.wait();
        readingThreads++;
    }

    public synchronized void readUnlock() {
        readingThreads–;
        preferWrite = true;
        notifyAll();
    }

    public synchronized void writeLock() throws InterruptedException {
        waitingThreads++;
        try {
            while(readingThreads>0 || writingThreads>0)
                this.wait();
        }
        finally {
            waitingThreads–;
        }
        writingThreads++;
    }

    public synchronized void writeUnlock() {
        writingThreads–;
        preferWrite = false;
        notifyAll();
    }
}

readLock()用于获得读锁,readUnlock()释放读锁,writeLock()和writeUnlock()一样。由于锁用完必须释放,因此,必须保证lock和unlock匹配。我们修改DataHandler,加入ReadWriteLock:

package com.crackj2ee.thread;
public class DataHandler {
    // store data:
    private char[] buffer = "AAAAAAAAAA".toCharArray();
    // lock:
    private ReadWriteLock lock = new ReadWriteLock();

    public char[] read(String name) throws InterruptedException {
        System.out.println(name + " waiting for read…");
        lock.readLock();
        try {
            char[] data = doRead();
            System.out.println(name + " reads data: " + new String(data));
            return data;
        }
        finally {
            lock.readUnlock();
        }
    }

    public void write(String name, char[] data) throws InterruptedException {
        System.out.println(name + " waiting for write…");
        lock.writeLock();
        try {
            System.out.println(name + " wrote data: " + new String(data));
            doWrite(data);
        }
        finally {
            lock.writeUnlock();
        }
    }

    private char[] doRead() {
        char[] ret = new char[buffer.length];
        for(int i=0; i<buffer.length; i++) {
            ret[i] = buffer[i];
            sleep(3);
        }
        return ret;
    }
    private void doWrite(char[] data) {
        if(data!=null) {
            buffer = new char[data.length];
            for(int i=0; i<buffer.length; i++) {
                buffer[i] = data[i];
                sleep(10);
            }
        }
    }
    private void sleep(int ms) {
        try {
            Thread.sleep(ms);
        }
        catch(InterruptedException ie) {}
    }
}

public方法read()和write()完全封装了底层的ReadWriteLock,因此,多线程可以安全地调用这两个方法:

// ReadingThread不断读取数据:
package com.crackj2ee.thread;
public class ReadingThread extends Thread {
    private DataHandler handler;
    public ReadingThread(DataHandler handler) {
        this.handler = handler;
    }
    public void run() {
        for(;;) {
            try {
                char[] data = handler.read(getName());
                Thread.sleep((long)(Math.random()*1000+100));
            }
            catch(InterruptedException ie) {
                break;
            }
        }
    }
}

// WritingThread不断写入数据,每次写入的都是10个相同的字符:
package com.crackj2ee.thread;
public class WritingThread extends Thread {
    private DataHandler handler;
    public WritingThread(DataHandler handler) {
        this.handler = handler;
    }
    public void run() {
        char[] data = new char[10];
        for(;;) {
            try {
                fill(data);
                handler.write(getName(), data);
                Thread.sleep((long)(Math.random()*1000+100));
            }
            catch(InterruptedException ie) {
                break;
            }
        }
    }
    // 产生一个A-Z随机字符,填入char[10]:
    private void fill(char[] data) {
        char c = (char)(Math.random()*26+’A');
        for(int i=0; i<data.length; i++)
            data[i] = c;
    }
}

最后Main负责启动这些线程:

package com.crackj2ee.thread;
public class Main {
    public static void main(String[] args) {
        DataHandler handler = new DataHandler();
        Thread[] ts = new Thread[] {
                new ReadingThread(handler),
                new ReadingThread(handler),
                new ReadingThread(handler),
                new ReadingThread(handler),
                new ReadingThread(handler),
                new WritingThread(handler),
                new WritingThread(handler)
        };
        for(int i=0; i<ts.length; i++) {
            ts[i].start();
        }
    }
}

我们启动了5个读线程和2个写线程,运行结果如下:

Thread-0 waiting for read…
Thread-1 waiting for read…
Thread-2 waiting for read…
Thread-3 waiting for read…
Thread-4 waiting for read…
Thread-5 waiting for write…
Thread-6 waiting for write…
Thread-4 reads data: AAAAAAAAAA
Thread-3 reads data: AAAAAAAAAA
Thread-2 reads data: AAAAAAAAAA
Thread-1 reads data: AAAAAAAAAA
Thread-0 reads data: AAAAAAAAAA
Thread-5 wrote data: EEEEEEEEEE
Thread-6 wrote data: MMMMMMMMMM
Thread-1 waiting for read…
Thread-4 waiting for read…
Thread-1 reads data: MMMMMMMMMM
Thread-4 reads data: MMMMMMMMMM
Thread-2 waiting for read…
Thread-2 reads data: MMMMMMMMMM
Thread-0 waiting for read…
Thread-0 reads data: MMMMMMMMMM
Thread-4 waiting for read…
Thread-4 reads data: MMMMMMMMMM
Thread-2 waiting for read…
Thread-5 waiting for write…
Thread-2 reads data: MMMMMMMMMM
Thread-5 wrote data: GGGGGGGGGG
Thread-6 waiting for write…
Thread-6 wrote data: AAAAAAAAAA
Thread-3 waiting for read…
Thread-3 reads data: AAAAAAAAAA
……

可以看到,每次读/写都是完整的原子操作,因为我们每次写入的都是10个相同字符。并且,每次读出的都是最近一次写入的内容。

如果去掉ReadWriteLock:

package com.crackj2ee.thread;
public class DataHandler {

    // store data:
    private char[] buffer = "AAAAAAAAAA".toCharArray();

    public char[] read(String name) throws InterruptedException {
        char[] data = doRead();
        System.out.println(name + " reads data: " + new String(data));
        return data;
    }
    public void write(String name, char[] data) throws InterruptedException {
        System.out.println(name + " wrote data: " + new String(data));
        doWrite(data);
    }

    private char[] doRead() {
        char[] ret = new char[10];
        for(int i=0; i<10; i++) {
            ret[i] = buffer[i];
            sleep(3);
        }
        return ret;
    }
    private void doWrite(char[] data) {
        for(int i=0; i<10; i++) {
            buffer[i] = data[i];
            sleep(10);
        }
    }
    private void sleep(int ms) {
        try {
            Thread.sleep(ms);
        }
        catch(InterruptedException ie) {}
    }
}

运行结果如下:

Thread-5 wrote data: AAAAAAAAAA
Thread-6 wrote data: MMMMMMMMMM
Thread-0 reads data: AAAAAAAAAA
Thread-1 reads data: AAAAAAAAAA
Thread-2 reads data: AAAAAAAAAA
Thread-3 reads data: AAAAAAAAAA
Thread-4 reads data: AAAAAAAAAA
Thread-2 reads data: MAAAAAAAAA
Thread-3 reads data: MAAAAAAAAA
Thread-5 wrote data: CCCCCCCCCC
Thread-1 reads data: MAAAAAAAAA
Thread-0 reads data: MAAAAAAAAA
Thread-4 reads data: MAAAAAAAAA
Thread-6 wrote data: EEEEEEEEEE
Thread-3 reads data: EEEEECCCCC
Thread-4 reads data: EEEEEEEEEC
Thread-1 reads data: EEEEEEEEEE

可以看到在Thread-6写入EEEEEEEEEE的过程中,3个线程读取的内容是不同的。

思考

java的synchronized提供了最底层的物理锁,要在synchronized的基础上,实现自己的逻辑锁,就必须仔细设计ReadWriteLock。

Q: lock.readLock()为什么不放入try{ } 内?
A: 因为readLock()会抛出InterruptedException,导致readingThreads++不执行,而readUnlock()在 finally{ } 中,导致readingThreads–执行,从而使readingThread状态出错。writeLock()也是类似的。

Q: preferWrite有用吗?
A: 如果去掉preferWrite,线程安全不受影响。但是,如果读取线程很多,上一个线程还没有读取完,下一个线程又开始读了,就导致写入线程长时间无法获得writeLock;如果写入线程等待的很多,一个接一个写,也会导致读取线程长时间无法获得readLock。preferWrite的作用是让读 /写交替执行,避免由于读线程繁忙导致写无法进行和由于写线程繁忙导致读无法进行。

Q: notifyAll()换成notify()行不行?
A: 不可以。由于preferWrite的存在,如果一个线程刚读取完毕,此时preferWrite=true,再notify(),若恰好唤醒的是一个读线程,则while(writingThreads>0 || (preferWrite && waitingThreads>0))可能为true导致该读线程继续等待,而等待写入的线程也处于wait()中,结果所有线程都处于wait ()状态,谁也无法唤醒谁。因此,notifyAll()比notify()要来得安全。程序验证notify()带来的死锁:

Thread-0 waiting for read…
Thread-1 waiting for read…
Thread-2 waiting for read…
Thread-3 waiting for read…
Thread-4 waiting for read…
Thread-5 waiting for write…
Thread-6 waiting for write…
Thread-0 reads data: AAAAAAAAAA
Thread-4 reads data: AAAAAAAAAA
Thread-3 reads data: AAAAAAAAAA
Thread-2 reads data: AAAAAAAAAA
Thread-1 reads data: AAAAAAAAAA
Thread-5 wrote data: CCCCCCCCCC
Thread-2 waiting for read…
Thread-1 waiting for read…
Thread-3 waiting for read…
Thread-0 waiting for read…
Thread-4 waiting for read…
Thread-6 wrote data: LLLLLLLLLL
Thread-5 waiting for write…
Thread-6 waiting for write…
Thread-2 reads data: LLLLLLLLLL
Thread-2 waiting for read…
(运行到此不动了)

注意到这种死锁是由于所有线程都在等待别的线程唤醒自己,结果都无法醒过来。这和两个线程希望获得对方已有的锁造成死锁不同。因此多线程设计的难度远远高于单线程应用。

2005年09月28日

<script language="javascript">
function loadFile(obj,fileType){
  var strPath=obj.value.toLowerCase();
  //配合风声无组件上传类相同FileType形式作replace处理
  var strExt=strPath.substr(strPath.length-4,4).replace(/./,"/")+"/";
  var strType="/"+fileType.toLowerCase()+"/";
  if(strType.indexOf(strExt)==-1){
    obj.outerHTML=obj.outerHTML;
    alert("仅可选择 "+fileType+" 文件");
  }
}
</script>
<input type="file" onchange="loadFile(this,’gif/jpg’);">

2005年09月20日

1990-1994:Java缘起
文/孟岩

Larry Wall说,优秀程序员应有的三个特点:懒惰、急躁和傲慢。Java就是诞生在一群懒惰
、急躁而傲慢的程序天才之中。
1990年12月,Sun的工程师Patrick Naughton被当时糟糕的Sun C++工具折磨的快疯了。他
大声抱怨,并威胁要离开Sun转投当时在Steve Jobs领导之下的NeXT公司。领导层为了留住
他,给他一个机会,启动了一个叫做Stealth(秘密行动)的项目。随着James Gosling等
人的加入,这个项目更名为Green。其目标是使用C++为嵌入式设备开发一种新的基础平台
技术,James Gosling本人负责开发一个SGML编辑器。正如人们事后分析的那样,这位天才
的程序员太懒惰,所以没有把C++学好,开发中碰了一头包;太急躁——所以不愿意停下来
读读Scott Meyers的新书《Effective C++》;太傲慢——所以轻易地决定开发一中新的编
程语言。他把这种语言命名为C++++–,意思是C++“加上一些好东西,减去一些坏东西”
。显然这个糟糕的名字不可能长命百岁,很快这种颇受同伴喜爱的小语言被命名为Oak。

到了1992年9月,Oak语言连同Green OS和一些应用程序一起发布在称做Start 7的小设备上
,从而使之有了第一次精彩的亮相。随后,Sun开了一家名为FirstPerson的公司,整个团
队被转移到这家公司里研发机顶盒,以投标时代华纳公司的一个项目。这帮天才被技术狂
热所鼓舞,开发出了一个高交互性的设备,结果没想到时代华纳公司和有线电视服务商并
不愿意用户拥有那么大的控制权,从而在竞标之战中败给了SGI。Oak的锋芒之锐,竟然把
客户都给吓懵了。Sun沮丧地关闭了FirstPerson,召回了整个团队。事实证明,传统行业
中那些脑满肥肠的保守主义者是腐朽没落的。回去!回到激情澎湃的IT产业,抓住互联网
的大潮,这才是出路!1994年,Oak被命名为Java,针对互联网的新一轮开发如火如荼,一
切已经就绪,熔岩在地下奔流,火山即将喷发。


1995: Java香浓世界
文/马伟

1995年,Sun正式对外公布了Java,并且发布了JDK 1.0。这种外形酷似C++,却包含一颗S
malltalk般纯洁的面向对象之心的全新程序设计语言及其平台,几乎在一夜之间就成为软
件产业的新宠儿。Java当时仅仅被用来为网站制作一些动态应用,诸如动画图片之类,但这
仍然引起了很多Web开发者们的注意,他们非常渴望有一种安全的语言,可以在静态的HTM
L网页上制作动画图片。Sun最终把Java集成到NetScape浏览器。同时因为它具有“只写一
次,随处运行”的特性,而引起了很多开发者的注意,他们可以再也不用为了使程序能够
在不同型号的硬件上运行而耗费大量的时间来编译代码了。
当时的Web浏览器的出现也为Java的出现起到了很好的推动作用,通过Java和Web浏览器的
结合,人们似乎看到了什么,有人甚至预言PC将在一两年内退出历史的舞台,取而代之的
是基于Java的浏览器应用程序,通过网络计算设备来进行应用。Java的出现为当时的软件
产业带来了无限的遐想。


1996:Java大跃进,盟主地位就此定
文/马伟

SUN在1996年一开始首先成立了JavaSoft组织,并在1月23日正式发布自己的Java 1.0,作
为20世纪业界出现的最重要的技术之一,Java引起了编程世界的革命。直到现在,Java仍
然是互联网上最流行的语言。
在Sun正式发布Java 1.0之后,Java这门新生的语言就拥有了自己的会议——JavaOne,这
次会议初试啼音就吸引了600多名参与者。除了拥有这么多的积极参与者来进行Java的开发
之外,各大知名公司也纷纷向Sun申请Java的许可。一时间,NetScape、惠普、IBM、Oral
ce、Sybase甚至当时刚推出Windows 95的微软都是Java的追随者。
Java的应用就像是世界上的顶级玩家们组成的一个公开联盟,告诉全世界我们大家就是都
在用着Java。也正是因为如此,Java也找到了自己的归宿。现在的J2EE已经成为中大型企
业级应用的标准,成为承接数据库和Web之间的一个重要桥梁。
当年Java的机会实在太多了,以至于很难知道到底该做什么。最终Java在应用服务器市场
获得了难以取代的地位,也确定了J2EE的发展方向,并且仍将延续下去。


1997-2001:  微软与Sun的Java官司
文/孟岩

Java诞生的1995年,正是微软在软件产业地位达到巅峰的时代,Windows 95发布时的风光
场面给人们留下的深刻印象至今难忘。尽管如此,作为最卓越的技术领袖,比尔?盖茨仍然
敏锐地注意到Java。当他了解了Java的一些细节之后,给予了这样的评价:“Java是很长时
间以来最优秀的程序设计语言。”基于此,微软于1996年3月申请并获得了Java许可证。微
软对于Java的这一热情态度在当时大大提高了人们对Java的兴趣和信心,但也有不少人担
心微软会依靠自己强大的影响力在标准之外另立标准,从而破坏Java的纯洁性。
果然,从1997年发布Visual J++的第一个版本开始,微软就开始在Java中掺入自己的私有
扩展。这毫无疑问引起Sun的高度重视。1997年10月,Sun向美国加州地方法院起诉微软公
司违反两公司就微软使用Java技术所签定的合同,指控微软公司在自己的Java产品中做了
“不恰当的修改”,违反了合同中承诺向用户提供Java兼容产品的条款。这一官司旷日持
久,直到2001年1月双方达成和解,微软将继续提供采用Sun开发的Java技术的现有产品(
包括测试版)。不过,Sun有限制地仅对包括Java 1.1.4的微软产品提供许可。到了2001年
7月,微软公布新版的Windows XP将不再支持Sun的JVM,并且推出了.NET平台与Java分庭抗
礼。
现在回过头去看,当时的这一场官司对Java世界产生了深远的影响。如果没有这一场官司
,也许很多Java程序员都在使用Visual J++,基于WFC开发Windows客户端程序,同时不得
不面对被两个不同的事实标准所分裂的Java世界。


1998:Java 2平台发布
文/陶文

1998年,Java 2平台正式发布。经过了三年时间的发展、热热闹闹的攻关宣传、红红火火
的众厂商的热情参与,Sun终于知道Java适合干什么了。对比Java刚发明时的技术定位,与
Java的戏剧性触“网”的那段历史,Java 2平台的发布可真算得上是有的放矢了。根据官
方的文档,Java 2是Sun意识到“one size doesn’t fit all”之后,把最初的Java技术
打包成三个版本的产物,也就是著名的J2ME、J2SE、J2EE。
之所以说Java自从Java 2平台发布之后,进入了现代。那是因为之前的历史怎么看来都和
现在程序员日常开发使用的技术无什么关系,比如Applet,已经很少有人使用了。Java 2
之后的历史就不一样了,至少人们在推崇轻量级开发,猛批EJB时还不时会引用J2EE这个词
是如何诞生的。而Java 2的三大版本中,除了J2EE得到了长足发展和广泛使用之外,J2ME
也在手机市场上取得了遍地开花的结果。相较之下,J2SE难免落寞,只剩SWT这个血统不纯
的家伙在Rich Client回归的时代吸引着人们的眼球了。无论今天看来当时的Java 2有多么
的不成熟,至少经过市场和时间的检验,Java 2规划出来的三大方向把Java技术指向了光
明的方向是勿庸置疑的。


1998:JCP成立并正式运作,
Java开源社群开始蓬勃发展
文/黄海波

1998年,JCP组织成立,并且开始把握Java的发展方向。JCP组织的开放性,不但使得所有
对Java感兴趣的商业公司可以参与Java的发展,更重要的是JCP允许个人、非盈利组织、学
校等加入,这就给Java带来了巨大的活力。随之兴起的Java开源运动的最大贡献是实现和
鼓励了知识共享,在众多热情的开源程序员们的努力和分享下,很多原先只被商业公司掌
握的技术、思想和产品可以被所有需要的开发人员免费或者以较低的价格获得使用权, 并
通过开放源代码更容易的获得反馈和改进意见从而进一步演化发展。我们知道,所谓知识
不是孤立发展认知,而是人们的经验,认识是思考交流和积累的产物。而开源运动所带来
的开放、反馈、交流的风气正是符合人类社会知识形成和发展的规律。
开源运动起源于西方的发达国家,有其现实背景和文化根源。1990年代可以说是IT产业的
一个黄金时代。信息时代的兴起对IT人员,特别是软件人员有着巨大的需求。而软件开发
又是一种类似艺术创作的脑力活动,和所有的艺术家、作家们一样,在作品打上自己的印
记并流传在世界上是每一个创作人员的梦想。互联网时代下的高收入的舒适生活,早九晚
五的编写公司的代码并不能满足很多有激情的软件开发人员的梦想,再加上西方传统的基
督教文化中十分推崇的分享和交流,开源的出现和兴起也就水到渠成了。今天,开源运动
已经不仅仅是一些个人天才程序员们的游乐园地,而是发展成为一项开源软件产业。


1998:WebLogic打开J2EE的魔匣
文/霍泰稳

Java语言的出现使得互联网络有了良好的交互性能,但这些很“酷”的技术仅被人们认为
是一些小花招,它还无法消除企业级用户对它的怀疑。1998年,BEA公司宣布收购WebLogi
c公司,并接着推出由Sun公司第一个授权使用J2EE许可证的WebLogic Server应用服务器,
这个Java版的AppServer一推出就引起业界极大的兴趣。WebLoigc Server以其对标准的支
持、强悍的运算能力和安全的架构设计等特性也很快征服了那些怀疑J2EE应用的人们。推
出市场后不到一年,WebLogic Server就成为业内第一Java应用服务器。
这里我们援引一些当时著名咨询公司的调查数据来说明问题,“在IDC的报告中,BEA在应
用服务器和交易服务器领域市场份额第一;在Gartner的报告中,BEA WebLogic Server拥
有业内最广泛的EJB应用安装基础;在Giga Group的报告中,BEA WebLogic Server市场份
额占32%”。
因为应用服务器市场极大的发展潜力,在WebLogic Server之后,其它的很多公司也推出了
自己的AppServer,如IBM的WebSphere、Sun公司的iPlanet等,逐渐地应用服务器取代了传
统意义上的各类中间件,成为企业应用的基础平台。应用服务器的出现使得Java有了真正
意义上的发展。
 

2002-2004: Sun与微软的法律碰撞最终以喜剧收场
文/恶魔

2003年4月2 日,Sun与微软达成16亿美元的法律和解。如果不是晚了一天,许多人会以为
这是一个在4月1日愚人节开的玩笑。尽管当时所有人都像是看到“太阳从西边出来了”那
样张大了嘴巴,但这的确是事实。
根据两家公司达成的版权协议,双方会为采用对方的技术而支付专利费用,微软向Sun提前
支付3.5亿美元使用费,Sun则承诺,如果Sun集成微软的某些技术,也会向微软付款。
毫无疑问,“私下了结”的方式对双方而言都是最好的结果。就在协议签署的当天,在美
国旧金山由Sun和微软为“抛弃十年恩怨、携手合作“举行的新闻发布会上,尽管比尔?盖
茨没有到场,但这并没有防碍现场看起来异常轻松的气氛。麦克尼利和鲍尔默各自穿了一
件密歇根州底特律“Red Wings”曲棍球队的运动服,并谈及了一起在哈佛大学读书的经历
,麦克尼利还说:“当时我们两人是非常要好的朋友,当然我们也有吵架的时候。”人与人
当然可能成为终生的知己,但是公司与公司之间有的只能是利益上的分分合合。


2000-2004: JBoss和Eclipse
——Java开源软件的王者
文/莫映

Java和开源几乎就是天生的一对,这可以从无比兴盛繁荣的Java开源软件社区得到佐证。
目前最有影响力的Java开源软件项目,要数JBoss和Eclipse。可以说,几乎所有的Java开
发人员都获多或少的听到过或接触和使用过它们。前者是目前最优秀、应用最为广泛的企
业级开源J2EE应用服务器,后者是功能完全可以替代商业产品的Java IDE。二者的覆盖功
能之全、支持工具之广、子项目之多,几乎可以仅凭借它俩来完成企业应用的开发构建到
部署实施的全过程,而软件开发者和客户也都可以最大程度上享受高质量,高可靠Java开
源软件所带来的低成本优势。
JBoss和Eclipse的巨大成功,几乎令各自领域的商用竞争者抓狂,其中BEA的WebLogic和I
BM的WebSphere在商业利润上受到JBoss的巨大侵蚀,而Borland的JBuilder、JetBrains的
IDEA等诸多优秀的商用开发工具也不得不面对Eclipse独大的现实。JBoss的CEO兼创始人
Marc Fleury曾直言不讳地表示,希望占据市场主导地位。“我们希望打败IBM,成为中间
件领域里最大的厂商。”JBoss在4.0以前还只是以一个Group存在,盈利手段主要靠服务和
销售文档。但在最近,JBoss已经发展成为一个有限公司,并吸纳多家风险投资,专注于获
取利润为目标之一的第二代开源软件模式(JBoss自己称为“Professional Open Source”
)的创新和运营。这区别于以理论研究为爱好的学院型开源或大公司为基础的非盈利组织
开源,如Linux和Apache。当然JBoss的这种运营方式势必会导致更多的代码控制和专有修
改权,但按JBoss的说法是这样更能获得企业客户的信赖。JBoss的这种模式是否能获得成
功还要我们拭目以待。
不管JBoss和Eclipse的未来发展如何,JBoss和Eclipse的成功已经让我们看到了Java开源
软件的威力,祝愿它们一路走好。


2004:Java 5.0
文/莫映

2004年9月30日,代号为“Tiger”,研发历时近三年的J2SE 5.0发布正式版本,这是Java
平台历来发布版本中改动面波及最大的一次。
纵观Tiger,“Ease of development”是其核心主题,这一点着重体现于语言特性上的改
进,这在很大程度上,简化了开发人员日常的编程任务,以往一些琐碎的手工劳动都代之
以轻松自然,而又安全可靠的自动化实现。其中的注解功能,以及随之而来的声明式编程
,还对构筑于J2SE 5.0之上的J2EE 5.0产生了巨大影响。尽管Tiger在语言特性上做了很大
的动作,但作为Java技术的基础支撑,这些改动都是深思熟虑的结果。
Tiger发布至今也有大半年了,那么Sun又是如何规划J2SE的未来蓝图的呢?据悉,J2SE的
下两个版本分别是代号为“Mustang”的J2SE 6.0和代号为“Dolphin”的J2SE 7.0,预计
Mustang将于明年发布。在吸取了Tiger研发周期过长的教训之后,Sun副总裁Graham Hami
lton表示,Mustang的发布周期将不会那么长。并且,Sun还将“Becoming more open” 作
为Mustang的主题之一。未来JCP对Java技术的影响将会愈加深入,而整个研发过程也将会
愈加透明。Mustang在正式发布前的内部版本也会陆续见诸于众,如此,广大Java开发者便
可以更加及时的了解到Java发展的最新情况。在语言层面上的扩展依然会比较谨慎,比如
像AOP这样的当下热门技术,依然不太可能会见诸其中。据Hamilton所言,一个有可能被引
入的语法特性被称作“friends”import机制,它将使由多个包组成的大型项目变得易于管
理。

 

 

十大人物

James Gosling : Java之父
文/陶文

作为Java之父,James Gosling的名字可谓是耳熟能详。当人们评论一种编程语言时,总喜
欢捎带着把下蛋的母鸡一起带上。Java做为中国的编程语言学习者餐桌上有限的那么几样
餐点中的流行款式,自然是让James Gosling风光不已。虽然James Gosling现在已经不是
领导Java发展潮流的领军人物了,做为Sun的开发者产品组的CTO,怎么算来也是身居高位
了,俗事缠身吧,但是这并不妨碍其对于Java一如既往的爱护,表达着各式各样鲜明的观
点,引发一场又一场的争论。
James Gosling是很爱Java的——是啊,哪有当父母的不爱自己的孩子的呢。James Gosli
ng也是很爱Sun的——是啊,哪有当领导的不爱自己的公司的呢。于是我们在批评.NET的安
全性的队伍前头,在褒扬Java性能的队伍前头,在抨击SWT开倒车的队伍前头,在给NetBe
ans大唱赞歌的队伍前头,我们都看到了James Gosling的身影。无论对错、偏见或者固执
,至少说明了Gosling的鲜明个性丝毫没有受到年龄的影响。也许也只有这种天才而偏执的
人物才能创造出Java这般伟大的语言来吧。
 

Bill Joy : 软件业的爱迪生
文/徐昊

Joy生于1954年,1982年与Vinod Khosla, Scott McNealy和Andy Bechtolsheim一起创建了
Sun Microsystems,并从那时起担任首席科学家,直到2003年离开。他是一位令人崇敬的
软件天才,他在软件和硬件的历史上留下了无数令人仰止的传奇。
在上个世纪80年代早期,DARPA与BBN达成协议,准备将Vinton Cerf和Bob Kahn设计的TCP
/IP协议添加到Berkeley UNIX中。Bill Joy被委派来完成这项任务,然而他却拒绝将BBN的
TCP/IP协议栈添加到BSD中,因为在他的眼中BBN的TCP/IP实现还远不够好,于是他就写了
一个高性能的TCP/IP协议栈。John Gage回忆道,“BBN和DARPA签署了巨额合同来实现TCP
/IP协议,然而他们的员工所编写的代码远没有一个研究生所做的好。于是他们邀请Bill
Joy参加他们的一个会议,这位研究生穿着一件T-Shirt就出现了,他们询问他,‘你是如
何做到的呢?’Bill回答说,‘这是非常简单的一件事,你读一下协议然后就可以编码了
’”。除了TCP/IP协议,基于分页的虚拟内存系统最早也是由Bill Joy添加到Berkeley U
NIX内核当中的。同时他还是vi、csh、早期Pascal编译器的作者。
关于Bill Joy惊人的软件才能流传最广的一个传奇是,据说他在上研究生的时候,想看看
自己能不能写一个操作系统出来,于是就在三天里写了一个非常简陋,但是可以使用的Un
ix系统, 传说就是BSD的前身。虽然如此夸张的才情令人难以置信,但是考虑到主角是Bil
l Joy,还是有一定的可信度的。Bill Joy硕士毕业之后,决定到工业界发展,于是就到了
当时只有一间办公室的Sun, 他作为主要设计者参与了SPARC微处理器的设计,负责设计最
为关键的一部分电路。这样兼精软硬件的天才实在是让人不得不佩服啊。1995年,Sun发布
了轰动世界的Java语言。当然,Bill Joy对Java也作出了不少的贡献,首先是JINI——一
种针对分布式服务的基础连接技术。任何可以内嵌JVM的电子设备都可以通过JINI相互连接
;JXTA是基于Java的P2P协议,允许互联网上的软件进行点对点交流和协作。
这个其貌不扬的瘦高个,有着凌乱的亚麻色头发,被《财富》杂志誉为“网络时代的爱迪
生”的技术狂人,在短短的二十年间,创造了无数令人心动的软件。在MIT的BBS上曾有一
个帖子,说微软电话面试有一道题,问“Who do you think is the best coder, and wh
y?”虽然回复的帖子中大家都声明列举的best coder排名不分先后,然而大多数人仍把Bi
ll Joy列在第一位,或许可以从一个侧面验证Bill Joy在广大Programmer心目中的地位吧



Joshua Bloch :  Java 2 元勋
文/莫映

早在1996年,适逢Java刚刚崭露头角,年内好事连连。先是1月份发布JDK 1.0,然后是5月
底在旧金山召开首届JavaOne大会,年末又是JDK 1.1紧跟其后。正是在Java技术如火如荼
、大展拳脚的背景之下,Joshua Bloch来到了Sun,开始了他带领Java社区步入“迦南美地
”的漫长历程。
很快,他被从安全组调入核心平台组,从事底层API设计。至此以后,每逢JDK的重大版本
发布,总能在其中见到Joshua的“妙笔”。JDK 1.1中的java.math、1.4中的assertions,
还有大家所熟识的Collections Framework皆是Joshua一手打造。其中的Collections Fra
mework还获得了当年的Jolt大奖。到了J2SE 5.0研发阶段,身为平台组构架师的Joshua接
掌了Tiger大旗,其核心地位已然无人可以替代。作为Tiger的代言人和领路人,没有谁比
Joshua更清楚Tiger。相信大家一定还记得Joshua当年仿效英国诗人William Blake所做的
咏Tiger诗八首,优雅的笔调,透出大师深厚底蕴的同时,也道出了Tiger的几大重要特性
,这些特性是自JDK 1.1引入Inner Class以来,Java最大的语法改进。
Java风雨十年,从JDK 1.1到J2SE 5.0,Joshua实在功不可没。难怪有人戏言,假如将Jam
es Gosling比作Java之父,那么Joshua就是一手将Java “哺育”成人的Java之母。Joshu
a对Java的贡献还不止于JDK,提起他的大作《Effective Java》(Addison Wesley, 2001
),相信Java粉丝们一定耳熟能详。该书荣膺2002年度Jolt大奖,且备受James Gosling推
崇。书中57条颇具实用价值的经验规则,来自Joshua多年来在JDK开发工作中,尤其是Col
lections Framework设计中的实践心得,各个有理有据,剖析深入,也足见其深厚功力。
该书对Java社群的影响,犹如C++社群中的《Effective C++》。Joshua对JCP的贡献也不小
。他是JSR201和JSR175的领导者,前者包含了Tiger四大语言特性,后者则为Java提供了元
数据支持。此外,他还是JSR166的发起人之一(该JSR由Doug Lea领导),并且是许多其他
JSR的参与者。Joshua目前是JCP为数不多的几个执行委员会成员之一。
Joshua Bloch给人的印象是谦逊平和,行事低调而不喜抛头露面,一个典型的技术人员和
实干家。不过即便如此,也丝毫不会减弱他对Java技术的卓越贡献和对Java社区的绝对影
响力。有人说,如果他能更彰显一些,就很有可能成为Java开发者中的领军人物,就有如
Don Box之于微软社群。
2004年7月初,就在Tiger发布在即之时,就在Jusha Bloch刚刚荣获Sun“杰出工程师(Di
stinguished Engineer)”的称号之时,他突然离开Sun而去了正值发展态势迅猛的Googl
e。当他离开Sun的消息在TSS发布之后,众多拥趸表达了怀念与不舍之情。一年过去了,我
们还没有获知Joshua的任何近闻,似乎又是他行事低调的一贯作风所致,不知他在Google
状况如何。希望Joshua依然能继续“摩西未尽的事业”,以他的影响力推动Java社群继续
前行。据称,《Effective Java》的下一版会加入Java 5.0的部分,让我们翘首以待吧。



Bruce Eckel : 功勋卓著的机会主义分子
文/孟岩

Bruce Eckel原本是一位普通的汇编程序员。不知道是什么因缘际会,他转行去写计算机技
术图书,却在此大红大紫。他成功的秘诀不外乎两点:超人的表达能力和捕捉机会的能力
。他最早的一本书是1990年代初期的《C++ Inside & Out》,随后,在1995年他写出了改
变自己命运的《Thinking in C++》。如果说这本书充分表现了他作为优秀技术作家的一面
,那么随后他写作《Thinking in Java》并因此步入顶级技术作家行列,则体现了他作为
优秀的机会主义分子善于捕捉机会的另一面。写作中擅长举浅显直接的小例子来说明问题
,语言生动,娓娓道来,特别适合于缺乏实践经验的初学者。因此《Thinking in Java》
俨然成为天字第一号的Java教科书,对Java的普及与发展发挥着不可忽略的作用。不过公
允地说,Bruce Eckel的书欠深刻。比如在“Thinking in…”系列中对设计模式的解说就
有失大师水准。这一方面是因为书的定位非常清晰,另一方面也是因为Bruce太过分心赶潮
流,未能深入之故。TIJ之后,他预言Python将火,就匆匆跑去写了半本《Thinking in P
ython》。后来Python并未如期而旺,于是他也就把书稿撂在那里不过问了,机会主义的一
面暴露无遗。我们也可以善意的猜测一下,他的下一个投机对象会是什么呢?Ruby?.NET
?MDA?总之,是什么我都不奇怪。


Rickard Oberg :J2EE奇才
文/熊节

Oberg的作品很多,流行的代码生成工具XDoclet和MVC框架WebWork都出自他的手笔。这两
个框架有一个共同的特点,即它们的功能虽然简单,但设计都非常优雅灵活,能够很方便
地扩展新功能甚至移植到新环境下使用。优雅的设计源自Oberg的过人才华,简单的功能则
折射出他玩世不恭的人生态度。正是这两种特质的融合,才造就了这个不世出的奇才。
1999年,JDK 1.3发布,其中带来了一个重要的新特性:动态代理(Dynamic Proxy)。当
所有人都还在对这项新技术的用途感到迷惑时,Oberg发现用它便可以轻松攻克EJB容器实
现中的一些难关。这一发现的产物就是一本《Mastering RMI》,以及大名鼎鼎的JBoss应
用服务器。但Oberg很快又让世人见识了他的玩世不恭。由于和总经理Marc Fleury在经营
理念上不合,Oberg抱怨“法国的天空总让我感到压抑”,甩手离开了自己一手打造的JBo
ss。此后的几年里,他和老友Hani Suleiman不断地对JBoss的“专业开源”模式和Marc F
leury的商人味道冷嘲热讽,让众人为他的孩子气扼腕叹息。
2002年10月,微软推出Petstore示例应用的.NET版本,并宣称其性能比Java Petstore高出
数倍。正是Oberg深入分析这个示例应用的源代码,在第一时间指出它大量运用了SQL Ser
ver专有的特性,性能对比根本不具参考价值。后来Oberg又先后关注了AOP和IoC容器,两
者都成为了J2EE架构的新宠。
 

Doug Lea : 世界上对Java影响力最大的个人
文/KIT

如果IT的历史,是以人为主体串接起来的话,那么肯定少不了Doug Lea。这个鼻梁挂着眼
镜,留着德王威廉二世的胡子,脸上永远挂着谦逊腼腆笑容,服务于纽约州立大学Oswego
分校计算器科学系的老大爷。
说他是这个世界上对Java影响力最大的个人,一点也不为过。因为两次Java历史上的大变
革,他都间接或直接的扮演了举足轻重的脚色。一次是由JDK 1.1到JDK 1.2,JDK1.2很重
要的一项新创举就是Collections,其Collection的概念可以说承袭自Doug Lea于1995年发
布的第一个被广泛应用的collections;一次是2004年所推出的Tiger。Tiger广纳了15项J
SRs(Java Specification Requests)的语法及标准,其中一项便是JSR-166。JSR-166是来
自于Doug编写的util.concurrent包。
值得一提的是: Doug Lea也是JCP (Java小区项目)中的一员。
Doug是一个无私的人,他深知分享知识和分享苹果是不一样的,苹果会越分越少,而自己
的知识并不会因为给了别人就减少了,知识的分享更能激荡出不一样的火花。《Effectiv
e JAVA》这本Java经典之作的作者Joshua Blosh便在书中特别感谢Doug是此书中许多构想
的共鸣板,感谢Doug大方分享丰富而又宝贵的知识。这位并发编程的大师级人物的下一步
,将会带给Java怎样的冲击,不禁令人屏息以待。


Scott McNealy :SUN十年来的掌舵者
文/KIT

McNealy,Sun的CEO、总裁兼董事长。他曾经狂傲的说:“摧毁微软是我们每个人的任务。
”这位英勇的硅谷英雄,似乎带头起义,试图组织一个反微软阵线联盟,以对抗微软这股
庞大的托拉斯恶势力。他时常口出惊人之语,在公开场合大肆的批评微软,并曾经说微软
的.NET是.NOT。
Scott McNealy先后毕业于哈佛大学及史丹佛大学,分别持有经济学学士学位及企管硕士。
1982年MBA毕业的他和三个同学共同合伙创建了Sun,并于1984年成为Sun的执行官。“要么
吞了别人,不然就被别人吞了”是Scott McNealy的名言录之一。他擅长以信念带动员工,
鼓舞士气。极富自信的他,对于认定的事,总是坚持自己的想法,因此有人形容他是一个
刚愎自用的决策者。
身为Sun这艘船的掌舵者,Scott McNealy能够看多远,Sun就能走多远。Scott McNealy认
为将来软件界是一个只有服务,没有产品的世代。他希望打造出Sun不是一个纯靠硬件赚钱
的公司。从Open Source到Open Solaris,Sun希望可以成为提供整合性解决方案的服务厂
商。Solaris 10 + UltraSPARC是否可以像Scott McNealy希望的是下一匹世纪黑马呢?Su
n是否能以股价来证明华尔街分析师及普罗大众的诽短流长?Scott McNealy是否能带领着
Sun成为继微软之后的下一个巨人,一场场IT界的争霸战值得我们拭目以待。


Rod Johnson : 用一本书改变了Java世界的人
文/ 刘铁锋

Rod在悉尼大学不仅获得了计算机学位,同时还获得了音乐学位。更令人吃惊的是在回到软
件开发领域之前,他还获得了音乐学的博士学位。有着相当丰富的C/C++技术背景的Rod早
在1996年就开始了对Java服务器端技术的研究。他是一个在保险、电子商务和金融行业有
着丰富经验的技术顾问,同时也是JSR-154(Servlet 2.4)和JDO 2.0的规范专家、JCP的
积极成员。
真正引起了人们的注意的,是在2002年Rod Johnson根据多年经验撰写的《Expert One-on
-One J2EE Design and Development》。其中对正统J2EE架构的臃肿、低效的质疑,引发
了人们对正统J2EE的反思。这本书也体现了Rod Johnson对技术的态度,技术的选择应该基
于实证或是自身的经验,而不是任何形式的偶像崇拜或者门户之见。正是这本书真正地改
变了Java世界。基于这本书的代码,Rod Johnson创建了轻量级的容器Spring。Spring的出
现,使得正统J2EE架构一统天下的局面被打破。基于Struts+Hibernate+Spring的J2EE架构
也逐渐得到人们的认可,甚至在大型的项目架构中也逐渐开始应用。
Rod Johnson的新作《Expert One-on-one J2EE Development without JEB》则更让人吃惊
,单单“Without EJB”一词就会让大多数J2EE架构师大跌眼镜了。不过Rod Johnson可能
仅仅是想通过“Without EJB”一词表明应该放开门户之见。这也是Rod Johnson一贯的作
风,。也许正是这种思想,促使得Rod Johnson创建了Spring,真正改变了Java世界。

 

Alan Kay :Java的精神先锋
文/徐昊

Sun的官方Java教材中有一句话,说Java是“C++的语法与Smalltalk语义的结合”。而Sma
lltalk的创造者就是Alan Kay。
Alan Kay于1970年加入Xerox公司的Palo Alto研究中心。早在70年代初期,Alan Kay等人
开发了世界上第二个面向对象语言Smalltalk,因此,Alan Kay被誉为Smalltalk之父。20
03年,Alan Key因为在面向对象程序设计上的杰出贡献,获得了有计算机界的诺贝尔奖之
称的ACM Turing Award。
Alan Kay成名于Smapltalk和OOP,而Java虽然在语言上类似于C,但是在语义上非常接近S
malltalk,很多Java中的设计思想在Alan Kay的文献中找到根源,也有些人将Alan Kay尊
为Java思想的先驱。不过遗憾的是似乎Alan Kay老先生对Java并不买账,反倒攻击说Java
是存在致命缺陷的编程语言,Java的成功不是由于Java本身的内在价值,而是其商业化的
成功。Alan Kay欣赏的是Lisp,他认为Lisp是软件的麦克斯韦方程,其中的许多想法是软
件工程和计算机科学的一部分。看来拥有Alan Kay这样一位重量级的Java先驱仍是我们Ja
va一厢情愿的单恋吧。

 

Kent Beck : 领导的敏捷潮
文:刘铁锋

Beck全家似乎都弥漫着技术的味道。生长在硅谷, 有着一个对无线电痴迷的祖父,以及一
个电器工程师父亲。从小就引导Kent Beck成为了业余无线电爱好者。
在俄勒冈州大学读本科期间,Kent Beck就开始研究起模式。然而在他最终拿到计算机学位
之前,他却是在计算机和音乐中交替学习。似乎Java大师都能够有这样的能耐,另一Java
大牛Rod Johnson同样也拥有音乐学的博士学位。
Kent Beck一直倡导软件开发的模式定义。早在1993年,他就和Grady Booch(UML之父)发
起了一个团队进行这个方面的研究。虽然著有了《Smalltalk Best Practice Patterns》
一书,但这可能并不是Kent Beck最大的贡献。他于1996年在DaimlerChrysler启动的关于
软件开发的项目,才真正地影响后来的软件开发。这次的杰作就是XP(极限编程)的方法
学。
和软件开发大师Martin Fowler合著的《Planning Extreme Programming》可谓是关于XP的
奠基之作。从此,一系列的作品如《Test Driven Development: By Example》,《Extre
me Programming Explained: Embrace Change》让更多的人领略到了极限编程的精髓,也
逐步导致了极限编程的流行。
Kent Beck的贡献远不仅如此。对于众多的Java程序员来说,他和Erich Gamma共同打造的
JUnit,意义更加重大。也许正式这个简单而又强大的工具,让众多的程序员更加认可和信
赖极限编程,从而引起了Java敏捷开发的狂潮吧。


 十大产品

Sun JDK :Java的基石
文/莫映

众所周知,流传于市的JDK不单Sun一家,比如IBM的JDK、BEA的JRocket、GNU的GCJ,以及
如Kaffe这样的开源实现,不一而足。但是,根正苗红的Sun官方JDK一直以来都是备受瞩目
的主流,它对Java社区的影响也是举足轻重。
1996年1月,Sun在成立了JavaSoft部门之后,推出了JDK 1.0,这是Sun JDK(Java Devel
opment Kit)的首个正式版本;当年12月,JDK1.1出炉。该版除了对前序版本部分特性做了
改进以外,重写了AWT,采用了新的事件模型。1998年12月,JDK 1.2正式发布。此时的类
库日臻完善,API已从当初的200个类发展到了1600个类。在1.2版本中引入了用100%纯Jav
a代码写就的Swing,同时,Sun将Java更名为Java 2。
1999年,Java 技术形成了J2SE、J2EE和J2ME三大格局。Sun向世人公布了Java HotSpot性
能引擎技术的研究成果。HotSpot旨在进一步改善JVM性能,提高Java ByteCode的产生品质
,加快Java应用程序的执行速度。J2SE 1.3发布于2000年;2002年2月间,J2SE 1.4问世,
这是有JCP参与以来首个J2SE的发行版本。2004年9月30日,代号为“Tiger”的J2SE 5.0终
于出笼了,这次发布被誉为Java平台历来发布中特性变动最大的一次。包括泛型在内的若
干重大语法改进、元数据支持,包括多线程、JDBC在内的多项类库改进,都令广大Java程
序员激动不已。自此,Sun的官方JDK(J2SE Development Kit)已经步入了一个新的高度


 

Eclipse :以架构赢天下
文/恶魔

IBM是在2001年以4000万美元种子基金成立Eclipse联盟,并且捐赠了不少程序代码。如今
,该组织有91个会员,包含许多全球最大的软件商。根据Evans Data公司的资料,Eclips
e是目前最受欢迎的Java开发工具。
Java厂商若要共同对抗微软,彼此之间就要有共同的开发工具才行。
在Eclipse平台上,程序员可使用好几种不同的语言。在前端方面,用户可整合多种工具来
撰写Plug-in程序或Unit Test。Eclipse最大的特色就在于其完全开放的体系结构,这代表
任何人都可下载并修改程序代码,给Eclipse写插件,让它做任何你能想到的事情,即所谓
“Design for everything but nothing in particular”。
Eclipse基金会的架构比较特别,反映出企业现今对于开放原始码计划也越来越积极主动。
Eclipse不像一般开放源码软件容许个人的捐献程序,该基金会是由厂商主导。不论是董事
会成员或者是程序赞助者几乎都来自于独立软件开发商(ISVs)的员工。
Eclipse首席执行官Mike Milinkovich说,这种厂商会员制是特意设计的;他说Eclispe软
件开发快速就是因为会员制的关系,同时又加上开放源码开发模式的临门一脚。这与一般
透过标准组织的做法全然不同。 这其实正好验证了一句老话:“开放即标准”。


JUnit/Ant : 让Java自动化的绝代双骄
文/刘铁锋

在Java程序员必备的工具中,共 同拥有且交口称赞的恐怕就非JUnit、Ant莫属了。一个是
单元测试的神兵利器,一个是编译部署的不二之选,它们让Java的开发更简单。
JUnit由XP和TDD的创始人、软件大师Kent Back以及Eclipse架构师之一、设计模式之父Er
ich Gamma共同打造。名家的手笔和理念使得JUnit简单而强大,它将Java程序员代入了测
试驱动开发的时代。JUnit连任了2001、2002年“Java World编辑选择奖”以及2003年“J
ava World最佳测试工具”和2003年“Java Pro最佳Java测试工具”等众多奖项,深受Jav
a程序员好评。
Ant是开源项目的典范,它让IDE的功能更加强大,从Sun的NetBeans到JBuilder,主流的ID
E中处处都有它的身影。“Another Neat Tool”原是它的本名,但这已经渐渐不为人知。
它彻底地让部署自动化,而程序员需要做的仅仅是几条简单的配置命令。和JUnit一样,A
nt也荣获了众多的殊荣:2003年JavaWorld“最有用的Java社区开发的技术编辑选择奖”,
2003年Java Pro“最有价值的Java部署技术读者选择奖”,2003年“JDJ编辑选择奖”,也
让Ant受到的多方的认可。
Ant对JUnit的全面集成,则使得一切都变得更加完美。只需简单地配置,从自动测试到报告
生成,从编译到打包部署均可自动完成。强大的功能,简单的配置,让Java程序员高枕无
忧。实可谓让Java自动化的绝代双骄。

Websphere : 活吞市场的大鲸
文/jini

1999年, IBM与Novell签订合作协议,成功地提供电子商务的解决方案给予原先使用NetW
are的用户。同年更是推出了WebSphere Application Server 3.0,并且推出WebSphere S
tudio与VisualAge for Java让工程师可以快速开发相关的程序。2001年,IBM更是宣布将
应用服务器、开发工具整合在一起,与DB2、 Tivoli及Lotus结合成为一套共通解决方案,
如今、IBM更是并入了Rational Rose ( UML tools )让开发流程更是完整化。
Sun在Web Services的策略方面远远落后于微软与IBM, 当他们手拉手在研订Web Services
规范, 加上IBM买硬件送软件或是买WebSphere送DB2的策略让企业大佬们纷纷转向IBM的阵
营, Sun才惊觉大势已去。WebSphere复杂的安装,深奥的设定,难以理解的出错讯息不断
地挑战开发者的耐心与毅力。
IBM如今已经不是将WebSphere定义为单一产品,它已经是一个平台的代名词。它里面的产
品目前包含了应用服务器、商业整合、电子商务、 数据讯息管理、网络串流、软件开发流
程、系统管理、无线语音等等。非常多样化,也让企业界愿意相信WebSphere可以带给他们
一套完整的解决方案。同时, IBM也在推广SOA的概念, 简单来说, 利用Web Service的耦合
性与工作流程的整合, 为企业内部打造以服务为导向的架构。
IBM捐献出Eclipse带给Java开发人员对IDE的重新掌握。未来是否会捐献出WebSphere的哪
一个部分成为OpenSources, 或许, 又是改写Java世界的时刻了。

 

WebLogic : 技术人的最爱
文/jini

1995年, BEA成立了, 初期以Tuxedo数据转换的产品为基础, 成长之迅速是历年来最强的企
业。 1998年, BEA推出以Java为基础的网络解决方案, 提供了完整的中间层架构, 更同时
支持EJB 1.0 及微软的COM组件, 方便的管理接口掳掠了工程师的心。 在IBM和Oracle尚未
准备好迎击的时候, BEA已经席卷企业应用平台的市场。 WebLogic无论在市场领先度与技
术领导性与策略远观性都优于当年的所有应用服务器厂商。
如今WebLogic不仅仅是应用平台服务器的名称, 而是BEA对于整个企业解决方案的总称, 无
论是WebLogic Portal或是WebLogic Integration配合着Workshop开发环境, 来自微软的U
I开发团队让Workshop几乎达到所见即所得。 接着, 在下一个版本之中, BEA的BeeHive开
放源代码计划将释出中间层控件的开发模块, 并且与Eclipse合作共同打造新一代的开发环
境。 如此强而有力的技术支持, 更是让顾客愿意使用WebLogic平台的最大原因。
代号为“Diablo”的 WebLogic Server 9.0小恶魔已经出现了, 目前虽然仅仅是BETA版,
以Portlet 方式打造的管理接口与完整且美妙的WebServices支持, 实在很难找到可以挑剔
的地方, 虽然去年被IBM的技术性推销超越了市场占有率, 不过接下来SOA的平台竞争现在
才开始, BEA的LOGO也加入“Think liquid”并且推出新的AquaLogic平台做为数据服务平
台, 可见, Java的应用服务器的战争, 还会继续进行着。

 

JBuilder : Java开发工具的王者
文/刘铁锋

Java的开发工具中,最出名的莫过于Borland公司的JBuilder了。对于一些没有弄清楚开发
工具与JDK的区别的Java入门者来说,JBuilder就如同Visual C++之于C++,以为JBuilder
就是Java的全部。比起捆绑在服务器上销售的JDeveloper,JBuilder应该是唯一的仅靠自
身的实力而占领了大部分市场的Java商用开发工具了。而JBuilder作为Java 开发工具的王
者,其夺冠之路并非一帆风顺。直到Java的天才Blake Stone成为JBuilder的Architect之
后,JBuilder 2.0以及3.0才逐渐推出。2000年3月14日,JBuilder 3.5的推出别具意义,
它成为了业界第一个用纯Java打造的开发工具,也风靡了整个Java开发工具市场。在同年
11月份推出的JBuilder 4.0乘胜追击,冲破了50%的市场占有率,成为了真正Java开发工具
的王者。
Borland以每半年左右推出一个新版本的速度,让众多的对手倒在了沙场。而Microsoft因
为与Sun的官司,也使得一个强大的对手退出了战争。2001年,加入了对企业协作支持的J
Builder 5以及强化了团队开发工具的JBuilder 6打败了最后一个对手Visual Age For Ja
va。2002年JBuilder 7推出之后,再也没有其他厂商与JBuilder竞争。
孤独的王者并没有停下脚步,在2003年到2005年间,JBuilder也仍然延续了其半年一个版
本的速度,推出了8、9、10、2005四个版本。强大的功能以及持续的改进,也让Java程序
员多了一分对能够在开发工具市场上与Microsoft血拼十数年的Borland的敬仰。

 

Oracle : Java人永远的情结
文/熊节

在林林总总的数据库之中,有一种尤其令人又爱又恨、印象深刻,那就是关系型数据库市
场的“大佬”——Oracle。
从公司的角度,Oracle和Sun有着诸多相似之处,例如:两家公司都拥有一位个性鲜明的C
EO。早在Java诞生之初的1995年,Oracle就紧随NetScape从而第二个获得了Java许可证。
从那以后,Oracle对Java的鼎力支持是Java能够在企业应用领域大获成功的重要原因之一

所有J2EE程序员都知道,Oracle的JDBC驱动虽然与Oracle数据库配合良好,但在不少地方
使用了专有特性。其中最为著名的就是“CLOB/BLOB问题”,诸如此类的问题给开发者带来
了很多麻烦。为了同时兼顾不同的数据库,他们不得不经常把自己的一个DAO(数据访问对
象)写成两份版本:针对Oracle的版本和针对其他数据库的版本。有不少人为了开发便利
,舍弃了数据库之间的可移植性,将自己的产品绑定在Oracle的专有特性上。
Oracle提供的Java开发工具也与此大同小异。不管是数据库内置的Java支持还是JDevelop
er IDE, Oracle的Java工具都和Oracle数据库有着千丝万缕的联系。看起来,只要Oracl
e还是数据库市场上的“头牌”,了解、学习Oracle的专有特性,周旋于Oracle特有的问题
和解决方案之中,就将仍旧是J2EE程序员在数据库基础和SQL之外的必修功课。对Oracle的
爱与恨,也将仍旧是Java人心头一个难解的情结。

 

Struts、Hibernate : 让官方框架相形失色的产品
文/刘铁锋

好的框架能够让项目的开发和维护更加便捷和顺利。相比Sun官方标准的迟钝以及固执,开
源框架也更得到Java程序员的共鸣。Struts以及Hibernate就是这样一类产品,它们简单、
优雅,更让官方的产品相形失色。
谈起Struts,不可避免地就要提及MVC(Model-View-Controller)的理念。而准确地讲,
MVC的提出却最早源于JSP的标准。在1998年10月7号,Sun发布的JSP的0.92的规范中提出的
Model 2就是MVC的原型。在1999年12月Java World的大会中,Gavind Seshadri的文章最早
阐述了Model 2就是一种MVC的架构,同时也提及了MVC架构是一种最好的开发方法。2000年
3月,由Craig McClanahan发布的Struts成为了最早支持MVC的框架。Struts在设计上虽然
存在一些诟病,但是不可否认的是,它使得Java Web应用的开发更加简洁和清晰,也让更
多的程序员爱上了Java,并开始遗忘官方的JSP。时至今日,比起如WebWork、Tapestry以
及Sun官方的JSF,Struts或多或少存在些不足,但是众多成功项目的实施,仍然使其牢牢
占据的Java Web应用框架的首位。
Hibernate则在某种程度上改变了人们对构建J2EE的思路。相比其EJB的Entity Bean的映射
技术,Hibernate则显得更加简洁和强大。五分钟就能把Hibernate跑起来,让更多的Java
程序员享受到了开发的乐趣。第15届Jolt大奖中,最优秀数据库、框架以及组件的奖项中
,Hibernate当仁不让获得头筹;不仅如此, Hibernate甚至还影响了官方的标准。在众多
Java程序员翘首以待的EJB 3.0的规范中,Hibernate得到了支持。
Java开源的繁荣不仅让众多Java的开发者享受到了更多的便利,甚至影响了官方的标准。
恐怕这也是作为Java人独有的乐趣之一吧。

 

PetStore : J2EE人的必修课
文/陶文

很少有一个例子项目如PetStore这 般广为人知,而这很大程度上要归功于Sun很“英明”
地把PetStore做成一个只展示架构而在性能调优上留下了大大余地的例子。围绕着性能话
题,产生了颇为有趣的厂商之间以及平台之间的Pet Wars。除去这些关于性能的流言蜚语
乃至中伤,PetStore在展示J2EE1.3平台的架构、演示什么叫分层方面还是有着很大的功劳
的。而且PetStore在架构方面的丰富性使得其成为J2EE的那些轻量级小兄弟们展示自身的
一个必选科目。
不谈那些围绕PetStore的口水,那些数不尽的盗版,PetStore给开发新手带来的最重大的
影响,我想应该是架构的观念而不是性能,也不是业务。做为一种技术的Demo,这无可非
议。但是如果你是一个新手,跟着PetStore亦步亦趋地学习J2EE开发,难免会陷入过度设
计、华而不实之类的困境。围绕着.NET的PetStore的克隆PetShop展开的架构与性能的大讨
论,是不是也在促使我们学习新技术时应该以解决问题为导向呢?特别是当你想把一个如
PetStore这般的Sample Project的技术照搬到你的现实世界的Real Project来时。

 

十大组织

Sun : 因为Java而永被荣光
文/孟岩

Sun是1980年代初期由斯坦福大学三位年轻学生创立的公司。与一般人的印象不同,“SUN
”的本意并不是企图剽窃天上那颗温暖的恒星的威名,而是“斯坦福大学网络”的意思。
Sun在“前Java”时代就因为SPARC芯片、Solaris操作系统和“网络就是计算机”的口号而
为人所知。1990年12月,Sun启动了一个看上去没什么意思的嵌入式软件项目。然而,基于
C++的开发很快遇到了麻烦。一个创新型技术公司的特色立刻显示出来,一群天才不是去深
入C++,而是另辟蹊径,发明了Java。这个传奇故事已经尽人皆知,但是其中所包含的精神
却始终令人望空凝思。
Java的发明,使得Sun真正有机会在软件的历史天空中放射出太阳的光芒。Sun发明了Java
,并且在长达十年的时间里始终走在Java大潮的最前端。Sun是Java的老家,是Java慈爱的
母亲,这一切任何人都改变不了。虽然Sun似乎没能够从Java中获得应有的金钱回报,但这
丝毫没有挫伤Sun对于Java的母爱,还有对于Java大潮的舍我其谁的领导气概。
所有人都迷恋富有的感觉,但是也迟早会意识到钱不是世上最宝贵的东西。这个世界并不
缺少会赚钱的公司,但是能够靠着创新型技术推动整个世界进步的公司却是凤毛麟角。Su
n应该感到骄傲,他们将因为Java而在历史的天空里发射出太阳的光芒。

 

IBM : Java经济的最大受益人
文/恶魔

Sun公司是Java的发明人,但IBM却是Java最大的受益者。是IBM抢占了利润丰厚的应用服务
器市场的头把交椅,是IBM在Java技术上投入最多的金钱,拥有最大的影响力和最好的开发
者社区。可以毫不夸张地说,Java使IBM的软件体系得到复兴,在某种意义上,甚至可以说
,是Java创造了这种复兴。Java之后又来了Linux,这种建造在不属于自己的平台上以获得
成功的理念更是变得非常有影响力。正是这种理念铸就了今天IBM “按需计算,服务为王
”的王者风范。
2004年三月,IBM以Java的解放者的姿态借机向Sun发难。IBM公司负责新兴技术的副总裁史
密斯在一封公开信中表示,IBM愿意与Sun合作成立一个项目,意在通过开放源代码开发模
式管理Java的开发工作。
墙内开花,墙外香。面对IBM的成功,到底是谁妒嫉呢?或许去程序的社区中逛逛聊聊,明
眼人是不难发现事实真相的。也许Sun应该好好向IBM学习经营之道。尽管利润额不如硬件
及服务部门,但IBM软件部门的利润率是最高的——高达85%的利润率足以令人惊叹。在最
近的一个季度里,IBM软件部的利润率上升了8%,其中WebSphere产品组的利润率上升了14
%。
正是IBM在开源和Java上的全身心地投入又秉承开放性的原则,今日的Java才能以日进千里
的速度将许多竞争对手远远抛在后面。Java 10年,IBM功不可没。

 

BEA : 用AppServer影响Java阵营
文/霍泰稳

十年前诞生的Java并不是一开始 就那么引人注目的,虽然用Applet也曾为互联网络带来一
抹亮色,但毕竟只是Toy。在企业级应用市场上,Java一直没有什么起色,虽然Java的支持
者一直在鼓吹它有着大型企业级应用的强悍功能。过高的期望与低能的产品,一时间颇让
人怀疑Java的路是否已经走到了尽头?可以说是WebLogic Server的出现逐渐打消了人们的
顾虑,BEA公司慧眼独具在2001年收购的这个产品将人们的目光吸引到电信、金融、政府等
Java企业级应用方面,WebLogic Server以其优良的性能让人们看到Java应用广阔的未来。
虽然随后在Java应用服务器方面出现了像IBM公司的WebSpere、开源软件JBoss等Java应用
服务器,但WebLogic Server几乎占领世界前500强所有企业的应用服务器市场地位依然无
法撼动。
Java现在已经不单纯是一个语言,从另一方面它也代表着开放与创新。很多以Java产品为
基础的公司或者从事Java开发的程序员骨子里都有着开放与创新的烙印,BEA公司的发展深
深地印证了这一点。与合作伙伴的密切合作向Java社区贡献产品基础源代码、加入权威开
源组织参与Java标准的制定等证实着BEA的开放,而其产品从WebLogic Server一种拓展到
WebLogic Platform、WebLogic Portal、WebLogic Workshop等其它领域又证实着它的创新
能力。

 

Oracle : 早起的鸟儿有虫吃
文/孟岩
Oracle的老板拉里?艾利森是有名的混世魔王和花花公子,所以尽管他也是软件产业成功人
士的代表,却绝不是程序员们心目中的英雄,程序员们毕竟不是央视《对话》节目里群众
演员,没必要为了节目需要而对权贵财阀们做出一副贱骨头状。但是,任何人都不能不钦
佩Oracle在技术上的前瞻性和坚决性。Oracle是1996年获得Java许可证的,紧接着就大胆
地将Java作为战略性的发展方向而予以全面支持。要知道当时Java的前景并不是十分确定
的,而Oracle的坚决投入,使得它在后来的Java世界中抢得一席之地。1998年9月发布的O
racle 8i为数据库用户提供了全方位的Java支持。Oracle 8i成为第一个完全整合了本地J
ava运行时环境的数据库,开发者用Java就可以编写Oracle的存储过程,这意味着可以仅在
Oracle数据库中就完成几乎全部的应用开发。J2EE兴起后,Oracle更是有心进入开发工具
市场,因而购买了JBuilder的源码,并在此基础上开发出JDeveloper。如今Oracle除了数
据库稳居第一之外,在Java开发工具世界里也自成一派。这一切不能不归功于当初的眼光
远大。


Apache : 开源软件的品牌保证
文/陶文

Java程序员的日常工具箱中,我们可以发现Ant、Tomcat、Log4、Lucene这些鼎鼎大名的开
源产品。而它们的共同点在于,都是由Apache Software Foundation社群中杰出的开发者
开发的开源项目。Apache这个名字在Java的世界中实在太出名了,以至于“Apache”这六
个字母成为开源项目品质保证的代名词。Apache是自由开源的一面旗帜,其Apache Licen
se更是成为商业友好的License的首选,只SourceForge上就有1000多个以Apache License
授权的项目,其流行程度可见一斑。
但是,如我们所知,Apache最早闻名IT界是靠高性能的Web服务器,其历史甚至和Java一样
长。Apache对于Java的偏爱,以及其发展的速度也映射出了Java繁荣的一角。现在去它的
主页上看看,满目望去全部都是Java的开源项目,早就不光是其C服务器的老本行了。Apa
che对Java最大的贡献就是提供了这么一个精品的开放舞台,让杰出的开发者和成熟的开源
项目走到一起,共同给Java语言提供一个丰富的工具仓库。对于一种语言、一个平台来说
,其库的丰富程度对于开发者来说的重要性再怎么强调也不为过。勿庸置疑,Aapache上会
出现越来越多的Java开源项目,而我们开发者也将更多地得益于这令人目不暇接的繁荣。


 

TheServerSide : 论坛的专业精神
文/刘天北

成立于2000年5月,TSS最初以一本书而广为人知。它的创始人Ed Roman同时也是J2EE名著
《Mastering EJB》的作者;Roman运营着一个J2EE咨询/培训公司TheMiddlewareCompany(
简称TMC),TSS当时是TMC的下属部门;为了扩大企业的影响,Roman在TSS网站上免费发布
了那本书的电子版。J2EE程序员要吃下这个香饵,就得在论坛中注册;注册的同时,多半也
会看一眼论坛的内容;一看之下,大部分人都被吸引住,成了社区的忠实成员。
TSS究竟有什么吸引人的秘诀?首先,它有一支能力过人的运营团队,除了Roman本人之外
,其中还有好几人都是J2EE领域的顶尖专家;第二,TSS和TMC定期会推出专家研讨会/视频
访谈、技术白皮书、评测报告,通读TSS提供的这些内容,基本上就可以把握技术的当前趋
势。但这还不是全部。最可贵的还是TSS的社区风格:他们深谙技术,但不盛气凌人;思想
敏锐,但不因此缺乏审慎和大局感。其中大多数人都已在自己的开发领域颇有建树,在TS
S上的活动既给他们提供了与同行进行深度交流的机会。一个新成员进入社区,就像参加了
一个起点很高的专业俱乐部,这不是一个求解“怎样设置JAVA_HOME环境变量”之类问题的
地方。事实上,在J2EE技术发展的若干转折点上,TSS都起到了关键的推动作用。
几经易主之后,J2EE咨询培训公司TMC在2004年关闭;TSS则被IT媒体集团TechTarget收购
。我们期待着它更加繁荣的未来。

JBoss : 职业开源软件组织
文/刘天北

J2EE的婴儿期,“应用服务器”原本是“昂贵”的代名词。但从1999年起,Marc Fleury和
Rickard Oberg等人就已经着手改变这种状况。他们开发的开源EJB容器当时叫做“EJBoss
”,在Sun公司的干预下(注意,“EJB”是注册商标),JBoss获得了今天的名字。虽然从
问世起就一直受到关注,但JBoss第一个达到产品化标准的版本可能是它的2.2版。它的易
用让人一见难忘:除了标准部署描述符,无需编写专用的xml配置文件。Oberg自豪地说,
“我们的架构并不是按照EJB规范指定的路线设计的,因此也没有走大多数应用服务器走过
的弯路。”
Jboss 3.x版本保持了一贯的创新精神,在用户中间获得了更广泛的认可。但是,文档要收
费下载、在邮件列表上提问常常会遭到Fleury等人的斥责。无疑,JBoss的创始者也意识到
了自己的幼稚:开源软件只能靠服务盈利,卖文档赚钱有限、骂用户当然更损害企业形象

虽然以Oberg为首的许多程序员退出了开发队伍(其中很多人成了JBoss的死敌),在开源
软件领域也面临JOnAS Geronimo等新老对手的竞争,但JBoss还是以不断推出的新版本站稳
了脚跟。在技术上,它是策动J2EE演进的重要力量:拟议中的EJB 3也要追随Jboss 4倡导
的开发范式,以至于二者的代码样本之间的差别几乎难以分辨;在商业上,JBoss与Sun公
司言和修好,甚至还获得了数量可观的风险投资。JBoss已经像拥护者预期的那样,成为了
应用服务器领域的Linux。

 

Borland : 深度介入Java
文/左轻候


除了Sun以外,也许没有一家公司 像Borland这样深层地介入Java。Borland开发了最早的
Java编译器之一,Borland的工程师参与了早期JDK的设计,Borland的JBCL(JavaBeans Co
mponent Library) 技术也成为后来Java Bean规范的基础。但是Borland对Java世界最大的
影响还是JBuilder。
1997年11月,Borland JBuilder 1.0发布。虽然第一个版本相对于竞争对手并没有表现出
明显的优势,但是Borland凭借深厚的技术实力和正确的市场策略,不断地超越了对手。J
Builder 3.5成为业界第一个100%基于Java架构的开发工具,并且市场份额很快超过了50%
。在随后的版本中,JBuilder持续改进对团队开发、J2EE架构、Mobile技术等方面的支持
,最终成为了Java开发工具市场,特别是大型企业级Java开发市场中的霸主。JBuilder的
成功,很大一个原因来自于Borland坚持的平台中立性,即对不同厂商的解决方案提供一视
同仁的支持。
2005年初,随着Eclipse社区的迅速崛起,Borland进入了Eclipse的董事会,成为战略开发
者(Strategy Developer) ,并宣布将推动Borland的其它产品与Eclipse的集成。在随后发
布的一份文件中,Borland宣称JBuilder的未来版本将放弃原有的PrimeTime架构,而基于
Eclipse架构。这个代号为“Peloton”的版本预计于2006年下半年发布。
Borland对Java的另外两个主要贡献来自Together和BES(Borland Enterprise Server)。T
ogether是著名的建模工具,能够与包括JBuilder在内的许多开发工具进行集成,全球市场
份额占有率排名第二。BES AppServer是一种J2EE服务器,在全球市场份额占有率上次于W
ebLogic和WebSphere,排名第三。

 

JCP : Java世界的联合国
文/黄海波


当联合国正在为安理会改革问题 吵得如火如荼时,Java世界的“联合国安理会”已经成功
地运作了七个年头。JCP(Java Community Process)在1998年由Sun发起成立,目标是通
过一个开放、合作和鼓励参与的非盈利组织来发展和推进Java和相关的技术。正是由于JC
P计划的推出可以让所有对Java感兴趣的软硬件厂商,个人和组织都能参与到技术规范的制
定和发展过程中,协调各方的兴趣和利益、集思广益,才可以让Java在短短的几年内异军
突起,成为可以和微软开发平台抗衡的一个主流开发语言。JCP计划既然是一个组织,自然
也有一定的架构。JCP组织架构主要包括PMO(Program Management Office)、JCP成员、EC
、EG。事实上,JCP的架构就好像一个Java世界的联合国。虽然也有不少人批评JCP成为各
派利益的角力场,因而效率低下;但是,它毕竟为Java的顺利发展很好地掌握了方向。

 

微软与Java : 不得不说的故事
文/孟岩


微软跟Java不对付,地球人都知 道。跟Sun和解了又怎么样?  .NET跟Java就是竞争对手
,没什么说的。但是有点IT掌故的人都知道,微软并非一开始就跟Java过不去。当年比尔
?盖茨盛赞Java是“长期以来最好的程序设计语言”,而且很早就购买了Java许可证。但是
微软作为村里的老大,看着人家的儿子茁壮呈长,不由得生了私心杂念,搞起了小动作,
在Visual J++中加入了一些破坏纯洁性的东西。单独来看,Visual J++是COM时代微软最棒
的开发工具,用WFC写Windows应用程序和COM组件实在是一种享受。但是放在Java大家庭里
,这个家伙就显得多少有点不怀好意。一场官司下来,微软被逐出Java大家庭,Visual J
++无疾而终。以后的事情尽人皆知,.NET出笼,利齿直指Java,几年撕咬下来,没占着便
宜也没吃大亏,如今也算是南北朝对峙,二分天下有其一。设想如果当时微软能够摒弃帝
国主义心态,正确对待Java,与其他人一起共建美好的Java“共产主义社会”,那么今天
我们的软件开发世界应该会美好得多。可惜黄粱一梦,终究是蚂蚁的喜事。2004年,微软
与Sun实现了和解,但愿到Java 20周年的时候,我们能更正面地描述微软对Java发挥的作
用。

2005年08月25日

JSP和Servlet对中文的处理

  世界上的各地区都有本地的语言。地区差异直接导致了语言环境的差异。在开发一个国际化程序的过程中,处理语言问题就显得很重要了。
  这是一个世界范围内都存在的问题,所以,Java提供了世界性的解决方法。本文描述的方法是用于处理中文的,但是,推而广之,对于处理世界上其它国家和地区的语言同样适用。

在JDK中,与中文相关的编码有:

  表1 JDK中与中文相关的编码列表

编码名称
说明
ASCII
7位,与ascii7相同
ISO8859-1
8-位,与 8859_1,ISO-8859-1,ISO_8859-1,latin1…等相同
GB2312-80
16位,与gb2312,gb2312-1980,EUC_CN,euccn,1381,Cp1381, 1383, Cp1383, ISO2022CN,ISO2022CN_GB…等相同
GBK
与MS936相同,注意:区分大小写
UTF8
与UTF-8相同
GB18030
与cp1392、1392相同,目前支持的JDK很少


  在实际编程时,接触得比较多的是GB2312(GBK)和ISO8859-1。


  世界上的各地区都有本地的语言。地区差异直接导致了语言环境的差异。在开发一个国际化程序的过程中,处理语言问题就显得很重要了。

  这是一个世界范围内都存在的问题,所以,Java提供了世界性的解决方法。本文描述的方法是用于处理中文的,但是,推而广之,对于处理世界上其它国家和地区的语言同样适用。

  汉字是双字节的。所谓双字节是指一个双字要占用两个BYTE的位置(即16位),分别称为高位和低位。中国规定的汉字编码为GB2312,这是强制性的,目前几乎所有的能处理中文的应用程序都支持GB2312。GB2312包括了一二级汉字和9区符号,高位从0xa1到0xfe,低位也是从0xa1到0xfe,其中,汉字的编码范围为0xb0a1到0xf7fe。

  另外有一种编码,叫做GBK,但这是一份规范,不是强制的。GBK提供了20902个汉字,它兼容GB2312,编码范围为0×8140到0xfefe。GBK中的所有字符都可以一一映射到Unicode 2.0。

  在不久的将来,中国会颁布另一种标准:GB18030-2000(GBK2K)。它收录了藏、蒙等少数民族的字型,从根本上解决了字位不足的问题。注意:它不再是定长的。其二字节部份与GBK兼容,四字节部分是扩充的字符、字形。它的首字节和第三字节从0×81到0xfe,二字节和第四字节从0×30到0×39。

  本文不打算介绍Unicode,有兴趣的可以浏览"http://www.unicode.org/"查看更多的信息。Unicode有一个特性:它包括了世界上所有的字符字形。所以,各个地区的语言都可以建立与Unicode的映射关系,而Java正是利用了这一点以达到异种语言之间的转换。

  在JDK中,与中文相关的编码有:

  表1 JDK中与中文相关的编码列表

编码名称
说明
ASCII
7位,与ascii7相同
ISO8859-1
8-位,与 8859_1,ISO-8859-1,ISO_8859-1,latin1…等相同
GB2312-80
16位,与gb2312,gb2312-1980,EUC_CN,euccn,1381,Cp1381, 1383, Cp1383, ISO2022CN,ISO2022CN_GB…等相同
GBK
与MS936相同,注意:区分大小写
UTF8
与UTF-8相同
GB18030
与cp1392、1392相同,目前支持的JDK很少


  在实际编程时,接触得比较多的是GB2312(GBK)和ISO8859-1。

  为什么会有"?"号

  上文说过,异种语言之间的转换是通过Unicode来完成的。假设有两种不同的语言A和B,转换的步骤为:先把A转化为Unicode,再把Unicode转化为B。

  举例说明。有GB2312中有一个汉字"李",其编码为"C0EE",欲转化为ISO8859-1编码。步骤为:先把"李"字转化为Unicode,得到"674E",再把"674E"转化为ISO8859-1字符。当然,这个映射不会成功,因为ISO8859-1中根本就没有与"674E"对应的字符。

  当映射不成功时,问题就发生了!当从某语言向Unicode转化时,如果在某语言中没有该字符,得到的将是Unicode的代码"\uffffd"("\u"表示是Unicode编码,)。而从Unicode向某语言转化时,如果某语言没有对应的字符,则得到的是"0×3f"("?")。这就是"?"的由来。

  例如:把字符流buf ="0×80 0×40 0xb0 0xa1"进行new String(buf, "gb2312")操作,得到的结果是"\ufffd\u554a",再println出来,得到的结果将是"?啊",因为"0×80 0×40"是GBK中的字符,在GB2312中没有。

  再如,把字符串String="\u00d6\u00ec\u00e9\u0046\u00bb\u00f9"进行new String (buf.getBytes("GBK"))操作,得到的结果是"3fa8aca8a6463fa8b4",其中,"\u00d6"在"GBK"中没有对应的字符,得到"3f","\u00ec"对应着"a8ac","\u00e9"对应着"a8a6","0046"对应着"46"(因为这是ASCII字符),"\u00bb"没找到,得到"3f",最后,"\u00f9"对应着"a8b4"。把这个字符串println一下,得到的结果是"?ìéF?ù"。看到没?这里并不全是问号,因为GBK与Unicode映射的内容中除了汉字外还有字符,本例就是最好的明证。

  所以,在汉字转码时,如果发生错乱,得到的不一定都是问号噢!不过,错了终究是错了,50步和100步并没有质的差别。

  或者会问:如果源字符集中有,而Unicode中没有,结果会如何?回答是不知道。因为我手头没有能做这个测试的源字符集。但有一点是肯定的,那就是源字符集不够规范。在Java中,如果发生这种情况,是会抛出异常的。
  什么是UTF

  UTF,是Unicode Text Format的缩写,意为Unicode文本格式。对于UTF,是这样定义的:

  (1)如果Unicode的16位字符的头9位是0,则用一个字节表示,这个字节的首位是"0",剩下的7位与原字符中的后7位相同,如"\u0034"(0000 0000 0011 0100),用"34" (0011 0100)表示;(与源Unicode字符是相同的);

  (2)如果Unicode的16位字符的头5位是0,则用2个字节表示,首字节是"110"开头,后面的5位与源字符中除去头5个零后的最高5位相同;第二个字节以"10"开头,后面的6位与源字符中的低6位相同。如"\u025d"(0000 0010 0101 1101),转化后为"c99d"(1100 1001 1001 1101);

  (3)如果不符合上述两个规则,则用三个字节表示。第一个字节以"1110"开头,后四位为源字符的高四位;第二个字节以"10"开头,后六位为源字符中间的六位;第三个字节以"10"开头,后六位为源字符的低六位;如"\u9da7"(1001 1101 1010 0111),转化为"e9b6a7"(1110 1001 1011 0110 1010 0111);

  可以这么描述JAVA程序中Unicode与UTF的关系,虽然不绝对:字符串在内存中运行时,表现为Unicode代码,而当要保存到文件或其它介质中去时,用的是UTF。这个转化过程是由writeUTF和readUTF来完成的。

  好了,基础性的论述差不多了,下面进入正题。

  先把这个问题想成是一个黑匣子。先看黑匣子的一级表示:

input(charsetA)->process(Unicode)->output(charsetB)

  简单,这就是一个IPO模型,即输入、处理和输出。同样的内容要经过"从charsetA到unicode再到charsetB"的转化。

  再看二级表示:

SourceFile(jsp,java)->class->output

  在这个图中,可以看出,输入的是jsp和java源文件,在处理过程中,以Class文件为载体,然后输出。再细化到三级表示:

jsp->temp file->class->browser,os console,db

app,servlet->class->browser,os console,db

  这个图就更明白了。Jsp文件先生成中间的Java文件,再生成Class。而Servlet和普通App则直接编译生成Class。然后,从Class再输出到浏览器、控制台或数据库等。

  JSP:从源文件到Class的过程

  Jsp的源文件是以".jsp"结尾的文本文件。在本节中,将阐述JSP文件的解释和编译过程,并跟踪其中的中文变化。

  1、JSP/Servlet引擎提供的JSP转换工具(jspc)搜索JSP文件中用<%@ page contentType ="text/html; charset=<Jsp-charset>"%>中指定的charset。如果在JSP文件中未指定<Jsp-charset>,则取JVM中的默认设置file.encoding,一般情况下,这个值是ISO8859-1;

  2、jspc用相当于"javac -encoding <Jsp-charset>"的命令解释JSP文件中出现的所有字符,包括中文字符和ASCII字符,然后把这些字符转换成Unicode字符,再转化成UTF格式,存为JAVA文件。ASCII码字符转化为Unicode字符时只是简单地在前面加"00",如"A",转化为"\u0041"(不需要理由,Unicode的码表就是这么编的)。然后,经过到UTF的转换,又变回"41"了!这也就是可以使用普通文本编辑器查看由JSP生成的JAVA文件的原因;

  3、引擎用相当于"javac -encoding UNICODE"的命令,把JAVA文件编译成CLASS文件;

  先看一下这些过程中中文字符的转换情况。有如下源代码:

<%@ page contentType="text/html; charset=gb2312"%>
<html><body>
<%
 String a="中文";
 out.println(a);
%>
</body></html>


  这段代码是在UltraEdit for Windows上编写的。保存后,"中文"两个字的16进制编码为"D6 D0 CE C4"(GB2312编码)。经查表,"中文"两字的Unicode编码为"\u4E2D\u6587",用 UTF表示就是"E4 B8 AD E6 96 87"。打开引擎生成的由JSP文件转变而成的JAVA文件,发现其中的"中文"两个字确实被"E4 B8 AD E6 96 87"替代了,再查看由JAVA文件编译生成的CLASS文件,发现结果与JAVA文件中的完全一样。

  再看JSP中指定的CharSet为ISO-8859-1的情况。

<%@ page contentType="text/html; charset=ISO-8859-1"%>
<html><body>
<%
 String a="中文";
 out.println(a);
%>
</body></html>


  同样,该文件是用UltraEdit编写的,"中文"这两个字也是存为GB2312编码"D6 D0 CE C4"。先模拟一下生成的JAVA文件和CLASS文件的过程:jspc用ISO-8859-1来解释"中文",并把它映射到Unicode。由于ISO-8859-1是8位的,且是拉丁语系,其映射规则就是在每个字节前加"00",所以,映射后的Unicode编码应为"\u00D6\u00D0\u00CE\u00C4",转化成UTF后应该是"C3 96 C3 90 C3 8E C3 84"。好,打开文件看一下,JAVA文件和CLASS文件中,"中文"果然都表示为"C3 96 C3 90 C3 8E C3 84"。

  如果上述代码中不指定<Jsp-charset>,即把第一行写成"<%@ page contentType="text/html" %>",JSPC会使用file.encoding的设置来解释JSP文件。在RedHat 6.2上,其处理结果与指定为ISO-8859-1是完全相同的。

  到现在为止,已经解释了从JSP文件到CLASS文件的转变过程中中文字符的映射过程。一句话:从"JspCharSet到Unicode再到UTF"。下表总结了这个过程:

  表2 "中文"从JSP到CLASS的转化过程

Jsp-CharSet
JSP文件中
JAVA文件中
CLASS文件中
GB2312
D6 D0 CE C4(GB2312)
从\u4E2D\u6587(Unicode)到E4 B8 AD E6 96 87 (UTF)
E4 B8 AD E6 96 87 (UTF)
ISO-8859-1
D6 D0 CE C4
(GB2312)
从\u00D6\u00D0\u00CE\u00C4 (Unicode)到C3 96 C3 90 C3 8E C3 84 (UTF)
C3 96 C3 90 C3 8E C3 84 (UTF)
无(默认=file.encoding)
同ISO-8859-1
同ISO-8859-1
同ISO-8859-1


  下节先讨论Servlet从JAVA文件到CLASS文件的转化过程,然后再解释从CLASS文件如何输出到客户端。之所以这样安排,是因为JSP和Servlet在输出时处理方法是一样的。
  Servlet:从源文件到Class的过程

  Servlet源文件是以".java"结尾的文本文件。本节将讨论Servlet的编译过程并跟踪其中的中文变化。

  用"javac"编译Servlet源文件。javac可以带"-encoding <Compile-charset>"参数,意思是"用< Compile-charset >中指定的编码来解释Serlvet源文件"。

  源文件在编译时,用<Compile-charset>来解释所有字符,包括中文字符和ASCII字符。然后把字符常量转变成Unicode字符,最后,把Unicode转变成UTF。

  在Servlet中,还有一个地方设置输出流的CharSet。通常在输出结果前,调用HttpServletResponse的setContentType方法来达到与在JSP中设置<Jsp-charset>一样的效果,称之为<Servlet-charset>。

  注意,文中一共提到了三个变量:<Jsp-charset>、<Compile-charset>和<Servlet-charset>。其中,JSP文件只与<Jsp-charset>有关,而<Compile-charset>和<Servlet-charset>只与Servlet有关。

  看下例:

import javax.servlet.*;

import javax.servlet.http.*;

class testServlet extends HttpServlet
{
 public void doGet(HttpServletRequest req,HttpServletResponse resp)
 throws ServletException,java.io.IOException
 {
  resp.setContentType("text/html; charset=GB2312");
  java.io.PrintWriter out=resp.getWriter();
  out.println("<html>");
  out.println("#中文#");
  out.println("</html>");
 }
}


  该文件也是用UltraEdit for Windows编写的,其中的"中文"两个字保存为"D6 D0 CE C4"(GB2312编码)。

  开始编译。下表是<Compile-charset>不同时,CLASS文件中"中文"两字的十六进制码。在编译过程中,<Servlet-charset>不起任何作用。<Servlet-charset>只对CLASS文件的输出产生影响,实际上是<Servlet-charset>和<Compile-charset>一起,达到与JSP文件中的<Jsp-charset>相同的效果,因为<Jsp-charset>对编译和CLASS文件的输出都会产生影响。

  表3 "中文"从Servlet源文件到Class的转变过程

Compile-charset
Servlet源文件中
Class文件中
等效的Unicode码
GB2312
D6 D0 CE C4
(GB2312)
E4 B8 AD E6 96 87 (UTF)
\u4E2D\u6587 (在Unicode中="中文")
ISO-8859-1
D6 D0 CE C4
(GB2312)
C3 96 C3 90 C3 8E C3 84 (UTF)
\u00D6 \u00D0 \u00CE \u00C4 (在D6 D0 CE C4前面各加了一个00)
无(默认)
D6 D0 CE C4 (GB2312)
同ISO-8859-1
同ISO-8859-1


  普通Java程序的编译过程与Servlet完全一样。

  CLASS文件中的中文表示法是不是昭然若揭了?OK,接下来看看CLASS又是怎样输出中文的呢?

  Class:输出字符串

  上文说过,字符串在内存中表现为Unicode编码。至于这种Unicode编码表示了什么,那要看它是从哪种字符集映射过来的,也就是说要看它的祖先。这好比在托运行李时,外观都是纸箱子,里面装了什么就要看寄邮件的人实际邮了什么东西。

  看看上面的例子,如果给一串Unicode编码"00D6 00D0 00CE 00C4",如果不作转换,直接用Unicode码表来对照它时,是四个字符(而且是特殊字符);假如把它与"ISO8859-1"进行映射,则直接去掉前面的"00"即可得到"D6 D0 CE C4",这是ASCII码表中的四个字符;而假如把它当作GB2312来进行映射,得到的结果很可能是一大堆乱码,因为在GB2312中有可能没有(也有可能有)字符与00D6等字符对应(如果对应不上,将得到0×3f,也就是问号,如果对应上了,由于00D6等字符太靠前,估计也是一些特殊符号,真正的汉字在Unicode中的编码从4E00开始)。

  各位看到了,同样的Unicode字符,可以解释成不同的样子。当然,这其中有一种是我们期望的结果。以上例而论,"D6 D0 CE C4"应该是我们所想要的,当把"D6 D0 CE C4"输出到IE中时,用"简体中文"方式查看,就能看到清楚的"中文"两个字了。(当然了,如果你一定要用"西欧字符"来看,那也没办法,你将得不到任何有何时何地的东西)为什么呢?因为"00D6 00D0 00CE 00C4"本来就是由ISO8859-1转化过去的。
  给出如下结论:

  在Class输出字符串前,会将Unicode的字符串按照某一种内码重新生成字节流,然后把字节流输入,相当于进行了一步"String.getBytes(???)"操作。???代表某一种字符集。

  如果是Servlet,那么,这种内码就是在HttpServletResponse.setContentType()方法中指定的内码,也就是上文定义的<Servlet-charset>。

  如果是JSP,那么,这种内码就是在<%@ page contentType=""%>中指定的内码,也就是上文定义的<Jsp-charset>。

  如果是Java程序,那么,这种内码就是file.encoding中指定的内码,默认为ISO8859-1。

  当输出对象是浏览器时

  以流行的浏览器IE为例。IE支持多种内码。假如IE接收到了一个字节流"D6 D0 CE C4",你可以尝试用各种内码去查看。你会发现用"简体中文"时能得到正确的结果。因为"D6 D0 CE C4"本来就是简体中文中"中文"两个字的编码。

  OK,完整地看一遍。

  JSP:源文件为GB2312格式的文本文件,且JSP源文件中有"中文"这两个汉字

  如果指定了<Jsp-charset>为GB2312,转化过程如下表。

  表4 Jsp-charset = GB2312时的变化过程

序号
步骤说明
结果
1
编写JSP源文件,且存为GB2312格式
D6 D0 CE C4
(D6D0=中 CEC4=文)
2
jspc把JSP源文件转化为临时JAVA文件,并把字符串按照GB2312映射到Unicode,并用UTF格式写入JAVA文件中
E4 B8 AD E6 96 87
3
把临时JAVA文件编译成CLASS文件
E4 B8 AD E6 96 87
4
运行时,先从CLASS文件中用readUTF读出字符串,在内存中的是Unicode编码
4E 2D 65 87(在Unicode中4E2D=中 6587=文)
5
根据Jsp-charset=GB2312把Unicode转化为字节流
D6 D0 CE C4
6
把字节流输出到IE中,并设置IE的编码为GB2312(作者按:这个信息隐藏在HTTP头中)
D6 D0 CE C4
7
IE用"简体中文"查看结果
"中文"(正确显示)


  如果指定了<Jsp-charset>为ISO8859-1,转化过程如下表。

  表5 Jsp-charset = ISO8859-1时的变化过程

序号
步骤说明
结果
1
编写JSP源文件,且存为GB2312格式
D6 D0 CE C4
(D6D0=中 CEC4=文)
2
jspc把JSP源文件转化为临时JAVA文件,并把字符串按照ISO8859-1映射到Unicode,并用UTF格式写入JAVA文件中
C3 96 C3 90 C3 8E C3 84
3
把临时JAVA文件编译成CLASS文件
C3 96 C3 90 C3 8E C3 84
4
运行时,先从CLASS文件中用readUTF读出字符串,在内存中的是Unicode编码
00 D6 00 D0 00 CE 00 C4
(啥都不是!!!)
5
根据Jsp-charset=ISO8859-1把Unicode转化为字节流
D6 D0 CE C4
6
把字节流输出到IE中,并设置IE的编码为ISO8859-1(作者按:这个信息隐藏在HTTP头中)
D6 D0 CE C4
7
IE用"西欧字符"查看结果
乱码,其实是四个ASCII字符,但由于大于128,所以显示出来的怪模怪样
8
改变IE的页面编码为"简体中文"
"中文"(正确显示)


  奇怪了!为什么把<Jsp-charset>设成GB2312和ISO8859-1是一个样的,都能正确显示?因为表4表5中的第2步和第5步互逆,是相互"抵消"的。只不过当指定为ISO8859-1时,要增加第8步操作,殊为不便。

  再看看不指定<Jsp-charset> 时的情况。

  表6 未指定Jsp-charset 时的变化过程

序号
步骤说明
结果
1
编写JSP源文件,且存为GB2312格式
D6 D0 CE C4
(D6D0=中 CEC4=文)
2
jspc把JSP源文件转化为临时JAVA文件,并把字符串按照ISO8859-1映射到Unicode,并用UTF格式写入JAVA文件中
C3 96 C3 90 C3 8E C3 84
3
把临时JAVA文件编译成CLASS文件
C3 96 C3 90 C3 8E C3 84
4
运行时,先从CLASS文件中用readUTF读出字符串,在内存中的是Unicode编码
00 D6 00 D0 00 CE 00 C4
5
根据Jsp-charset=ISO8859-1把Unicode转化为字节流
D6 D0 CE C4
6
把字节流输出到IE中
D6 D0 CE C4
7
IE用发出请求时的页面的编码查看结果
视情况而定。如果是简体中文,则能正确显示,否则,需执行表5中的第8步


  Servlet:源文件为JAVA文件,格式是GB2312,源文件中含有"中文"这两个汉字

  如果<Compile-charset>=GB2312,<Servlet-charset>=GB2312

  表7 Compile-charset=Servlet-charset=GB2312 时的变化过程

序号
步骤说明
结果
1
编写Servlet源文件,且存为GB2312格式
D6 D0 CE C4
(D6D0=中 CEC4=文)
2
用javac -encoding GB2312把JAVA源文件编译成CLASS文件
E4 B8 AD E6 96 87 (UTF)
3
运行时,先从CLASS文件中用readUTF读出字符串,在内存中的是Unicode编码
4E 2D 65 87 (Unicode)
4
根据Servlet-charset=GB2312把Unicode转化为字节流
D6 D0 CE C4 (GB2312)
5
把字节流输出到IE中并设置IE的编码属性为Servlet-charset=GB2312
D6 D0 CE C4 (GB2312)
6
IE用"简体中文"查看结果
"中文"(正确显示)


  如果<Compile-charset>=ISO8859-1,<Servlet-charset>=ISO8859-1

  表8 Compile-charset=Servlet-charset=ISO8859-1时的变化过程

序号
步骤说明
结果
1
编写Servlet源文件,且存为GB2312格式
D6 D0 CE C4
(D6D0=中 CEC4=文)
2
用javac -encoding ISO8859-1把JAVA源文件编译成CLASS文件
C3 96 C3 90 C3 8E C3 84 (UTF)
3
运行时,先从CLASS文件中用readUTF读出字符串,在内存中的是Unicode编码
00 D6 00 D0 00 CE 00 C4
4
根据Servlet-charset=ISO8859-1把Unicode转化为字节流
D6 D0 CE C4
5
把字节流输出到IE中并设置IE的编码属性为Servlet-charset=ISO8859-1
D6 D0 CE C4 (GB2312)
6
IE用"西欧字符"查看结果
乱码(原因同表5)
7
改变IE的页面编码为"简体中文"
"中文"(正确显示)


  如果不指定Compile-charset或Servlet-charset,其默认值均为ISO8859-1。

  当Compile-charset=Servlet-charset时,第2步和第4步能互逆,"抵消",显示结果均能正确。读者可试着写一下Compile-charset<>Servlet-charset时的情况,肯定是不正确的。

  当输出对象是数据库时

  输出到数据库时,原理与输出到浏览器也是一样的。本节只是Servlet为例,JSP的情况请读者自行推导。

  假设有一个Servlet,它能接收来自客户端(IE,简体中文)的汉字字符串,然后把它写入到内码为ISO8859-1的数据库中,然后再从数据库中取出这个字符串,显示到客户端。

  表9 输出对象是数据库时的变化过程(1)

序号
步骤说明
结果

1
在IE中输入"中文"
D6 D0 CE C4
IE
2
IE把字符串转变成UTF,并送入传输流中
E4 B8 AD E6 96 87
3
Servlet接收到输入流,用readUTF读取
4E 2D 65 87(unicode)
Servlet
4
编程者在Servlet中必须把字符串根据GB2312还原为字节流
D6 D0 CE C4
5
编程者根据数据库内码ISO8859-1生成新的字符串
00 D6 00 D0 00 CE 00 C4
6
把新生成的字符串提交给JDBC
00 D6 00 D0 00 CE 00 C4
7
JDBC检测到数据库内码为ISO8859-1
00 D6 00 D0 00 CE 00 C4
JDBC
8
JDBC把接收到的字符串按照ISO8859-1生成字节流
D6 D0 CE C4
9
JDBC把字节流写入数据库中
D6 D0 CE C4
10
完成数据存储工作
D6 D0 CE C4 数据库
以下是从数据库中取出数的过程
11
JDBC从数据库中取出字节流
D6 D0 CE C4
JDBC
12
JDBC按照数据库的字符集ISO8859-1生成字符串,并提交给Servlet
00 D6 00 D0 00 CE 00 C4 (Unicode)
 
13
Servlet获得字符串
00 D6 00 D0 00 CE 00 C4 (Unicode)
Servlet
14
编程者必须根据数据库的内码ISO8859-1还原成原始字节流
D6 D0 CE C4
 
15
编程者必须根据客户端字符集GB2312生成新的字符串
4E 2D 65 87
(Unicode)
 
Servlet准备把字符串输出到客户端
16
Servlet根据<Servlet-charset>生成字节流
D6D0 CE C4
Servlet
17
Servlet把字节流输出到IE中,如果已指定<Servlet-charset>,还会设置IE的编码为<Servlet-charset>
D6 D0 CE C4
18
IE根据指定的编码或默认编码查看结果
"中文"(正确显示)
IE


  解释一下,表中第4第5步和第15第16步是用红色标记的,表示要由编码者来作转换。第4、5两步其实就是一句话:"new String(source.getBytes("GB2312"), "ISO8859-1")"。第15、16两步也是一句话:"new String(source.getBytes("ISO8859-1"), "GB2312")"。亲爱的读者,你在这样编写代码时是否意识到了其中的每一个细节呢?

  至于客户端内码和数据库内码为其它值时的流程,和输出对象是系统控制台时的流程,请读者自己想吧。明白了上述流程的原理,相信你可以轻松地写出来。

  行文至此,已可告一段落了。终点又回到了起点,对于编程者而言,几乎是什么影响都没有。

  因为我们早就被告之要这么做了。

  以下给出一个结论,作为结尾。

  1、 在Jsp文件中,要指定contentType,其中,charset的值要与客户端浏览器所用的字符集一样;对于其中的字符串常量,不需做任何内码转换;对于字符串变量,要求能根据ContentType中指定的字符集还原成客户端能识别的字节流,简单地说,就是"字符串变量是基于<Jsp-charset>字符集的";

  2、 在Servlet中,必须用HttpServletResponse.setContentType()设置charset,且设置成与客户端内码一致;对于其中的字符串常量,需要在Javac编译时指定encoding,这个encoding必须与编写源文件的平台的字符集一样,一般说来都是GB2312或GBK;对于字符串变量,与JSP一样,必须"是基于<Servlet-charset>字符集的"。

2005年08月11日

发信站: 北大未名站 (2005年08月09日16:23:10 星期二)
【摘要】我们清华的在校生和刚毕业的学生创业的很多, 可是几乎没有成功的! 根据清华
科技圆的统计, 以前进驻清华科技圆的187家企业, 185家已经倒闭了, 只剩下两个公司
, 一个公司叫视美乐, 已经卖给青岛一家家电企业, 还一个公司因为是联想公司高层的亲
戚开的, 勉强活着! 开公司必须具备3个条件:1) 资金, 2)独特的不容易被模仿的产品,
3) 人才. 中关村年薪100万的个人远比年利润100万的企业多得多! 我建议在35岁前, 谨
慎创业! 除非你输得起!  
从来没有想过自己会加入这一行, 从开始自己喜欢的专业通讯,到后来喜欢的管理,幻
想过是专业高手,幻想过管理专家,却从来没有想过进入这一行,但真的在我刚刚离开校
园的时候发生了,短短几天,对这个行业有了一个感性认识,其实最让自己伤感的不是自
己没有干这一行的经验,而是代理的人,要找的人都是薪水100万,现在才发觉IT一行,
有钱人真多!

想想大家都在讨论一个月3000还是4000的时候,别人都是100万,而且多数都是没有结婚
的28-29岁的年轻人,我在感叹做人的差距好大啊! 最让人伤心的是,当代理100万的
CASE打个电话过去,很自豪的说帮你推荐职位,年薪100万,那边传来的是,轻蔑的笑声
:我现在都150万,你认为我会去考虑100万?

收集300个人资料,查他们的经验背景,更让我接受不了的,里面只有一个清华, 一个北
邮,我还熟悉,其它的都是很烂的大学,而交大一个也没有,和经理聊这个,他说如果你
要找好大学的,在搞技术的20-30万的很多好大学,不解。。。
同志们…….有钱人很多啊!

其实我开始也不相信,开始经理拉我去那边的时候说他们都代理百万的职位,我还以为他
吹牛,因为我们刚离开大学的时候大家都在为多拿几百快钱的时候,别人的薪水怎么能数
量级上升。

有位网友让我拿出证据,其实还需要拿嘛,今天我特地又把大概的资料翻了一下,更确信
我昨天的说法。给大家讲今天的一个笑话,每次约condidate 过来的时候,他们都开车过
来,而今天收到一个清华的GG的简历,感觉他技术基础很扎实,想把一个技术主管的位置
介绍给他,让他来我们OFFICE,我打听到他住的地方和我们的OFFICE 大概开车10分钟的
路程,我说我们半个小时后见,他说可能赶不到,这位哥们一句话差点把我喝在口中的氺
喷出来,他说,我骑自行车过去怕赶不上。。。老兄你花10块钱打个车不行嘛,你应聘的
可是70万的职位啊。。。。

其实大家都认为猎头就是中介,那可大错特错,猎头的信息;量大的让我刚刚进去的时候
吓了一跳,他们几乎有所有IT公司的人员联系方式,而且是手机号码。我在想(我后来明
白他们是怎么搞到的, 主要看报刊和网络, 特别是ICXO.COM)兄弟们,大家想赚大钱的话
,那么自己创业把,要么去去那些高端技术企业的销售(想想EMC高端storage一台
1million, 你认为利润是多少呢?),在大公司里面搞科研,可以让你不饿着,但永远富
不起来(当然有例外的)!

真的,我也想不明白怎么我看拿百万的好大学少的可怜,原来我想有点少,但至少不会少
到我看一百份只有2 3份是好大学的吧??我同意好大学出来的素质高, 但竞争能力怎么
那么差呢?

还有现在国外大IT来中国的企业一般都是扁平管理, 只有四层, 最上面2层, 80%是香

和台湾人,我就想不明白了,我们已经开放20多年,怎么老美还是不愿意来雇用本地人呢
? 而我们只能在最下边2层徘徊。唉。。


不知道大家感兴趣的是猎头的工作还是那一群拿高薪的人?

原来也接触过一些有钱人,但每次都告诉自己每个社会都有一些暴发户来聊以自慰,可第
一次而且是天天都在和这一群高收入者接触, 暴发户的现象完全消失, 自卑感可想而知
, 其实这种自卑不是来自他们的高收入, 而是他们对行业的理解, 对市场的嗅觉,对
整个行业食物链的把脉,他们和我们理 解的单片机, MOS逻辑电路完全是不同的概念,
我们都知道一个公司要发展,市场和技术缺一不可, 问题是技术方面有我们这些好大学
的学生来填补这些空缺而弱化了技术人员的薪资, 而使搞技术的处于饿不着也富不起来
的限尉地,而对公司来说,来钱的地方只有市场,我们当然知道这没有技术支撑都是扯谈
,这没有错,错的是我们认为大家都这么想,其实也只有我们搞技术的会这么想!

想想,一个卖通讯计费软件的销售人员2个月搞定的一个单子是2000万,而利润是1000万
(这完全是真实的事情,如果有些人还让我拿出论据,我只能笑你对这一行太不了解),
你说这个销售人员是应该拿300万还是400万的年薪呢? 而他手下没有管理一个人,而只
是一个一线销售人员。我们搞通讯的都知道CDMA比GSM优越的多, 而且都在预计它会取代
GSM,茫模停恋募 术早就成熟, 以前MOTOTROLA(中国)匆匆上马CDMA, 最后失败,引
起MOTOROLA高层的震荡, 而血洗了一片决策高层。 每个公司都投入大量资金给科研,如
果你以为是为了给科研人员的薪水,那又错了,那些钱都是做实验的, 而投入到市场上
面的钱,那都是进入老板的腰包!

面谈一个在著名IT公司搞 Marketing 的福州大学的29岁的很干练的经理人,我们不知怎
么聊到SUN的,他的公司代理的和SUN的产品几乎完全不同, 我以前也读了很多关于SUN的
文章, 从它的发家到成长,到丑闻,而这位仁兄的了解让我直冒冷汗,他连当天美国
SUN的股价都说的很清楚,更别提SUN的系列产品和对它未来的预测。而他仅仅是福州大学
的小本。上面有个网友让我描述一下他们的背景,这个统计起来有点难度,不过我可以肯
定的告诉你们, 他们有一个共同的东西,那就是人格魅力。

大家一谈创业都在犹豫,其实你犹豫的时候你已经失败一半了!
不信你去http://bbs.icxo.com上面的创业版看看,那里面多数人都在问:有谁创业成
功的吗?怎么搞第一桶金?你们问的时候你已经失败了,我肯定的告诉你,你问的同时,
好多人已经开始做了,创业成功的人不会来这儿的。。。。


对于那些拿高薪的人除了,他们的共性,人格魅力外,还有就是他们都有完整的职业规划
,这是他们成功的先决条件,如果一个人开始的希望都在为能不能找到工作,那么他也
don t have长远的规划。猎头们看简历candidate的时候首先看的是他们工作的公司和跳
槽的经历,如果你每2-3年都跳一次,那么我告诉你被猎头看上去的机会很少,专业的猎
头公司清楚的明白在代理这一行那个公司的员工最有竞争力,有些同学都认为去大公司为
以后的工作有好处,这不竟然,大家喜欢去四大,那是因为四大会帮你综合培养的很有竞
争力,所以很少有猎头去IBM挖人,那边出来的人没有太大的竞争力,这个庞大的组织里
面分的太细,出来的员工基本不会是多面手,这可能也是IBM高明的地方,所以IBM相
对员工比较稳定,但对于一个员工的职业规划,IBM不是一个好的去处。

关键是对人综合素质的培养,同时我们应该知道公司永远都是市场驱动而不是产品驱动,
这个先决条件决定搞管理,销售的人拿高薪的可能性大的多,有好多人都说,先搞两年技
术在转管理OR销售,其实你错了你两年的技术对你将来的管理和销售有点帮助,毕竟你花
了两年的时间在技术上面,而别人已经直接在管理和销售上面干了两年,所以我刚刚在电
脑上面大概统计了一下,高薪中管理和销售方面的人才开始做技术转过来的年龄都偏大,
所以如果你致力于搞管理和销售,那么直接去找这方面的工作,而不要浪费时间去搞2年
技术,一到管理层和销售对技术背景就会淡化很多, 而且愈高愈明显。

当然你的工科背景对你以后的管理和销售绝对是个很大的帮助,(现在我们那个几个专业
猎头,他们都是很有经验的专业顾问,但他们都不是工科背景,我的工科背景在那边做的
就很占优势,而且猎取的成功率高的多),如果有些人又不想放弃技术,又不想一心搞技
术,那么技术支持就不错,技术支持分pre-sales, and post sales, 我的建议如果你外
向一点选择pre-sales, 那边很锻炼人,不仅要技术,还要很多的和人大交道, 而且多数
薪水比post sales要高 这那边滚打5-6年
薪水都会到20-30万,以后跳槽的机会比较多,而售后这块薪水不是很高, 但技术含量

较高点, 以后跳槽也不容易。

当然想搞技术的,研发部门最好的啦,每个人都有自己的喜爱,而且钱不是衡量个人成功
唯一标准, 反正研发部里面高薪的情况很少, 除非是那种很核心技术的, 不过国外IT
公司的核心技术都放在总部研发, 不过研发主管的薪水都比较可观,不过研发部里面都
是牛校的PHD,一个MASTER和小本在那边爬上去的可能性太小。。。

下午在ICXO.COM 逛了1个小时, 看看有没有新的candidate ! 本来今天想写点猎人的程序
方面的,不过下午发生的事情让我really shock,我想把她写下来,也许今天不会好看,
但我还是想记下今天的故事:

她35,工学研究生,北大EMBA工商硕士,一个本土IT公司总经理,年营业额4亿。本来她
把简历投过来的时候,她告诉我因为和董事长经营理念不是很相同,想走。手头没有和她
很区配的职位,主要是开始很重要的职位(AP, CM)老板都没有给我做,其实我是个新
手, 什么东西都在学,但她希望和我保持联系,今天早上,她打过来电话说下午会路过
我们公司这边,希望和我见个面,本来我下午3点约了一个客户经理,后来我说3点半吧,
3点半到的时候,她很准时,我约的那个人还没有走,让她等了10分钟,会客厅被其它同
事用着, 我们就一起去了公司下面的一个咖啡厅,开始聊的很投机,因为我手里现在
sales mgr 职位比较多点,她想要CEO COO的位置,然后我们一起聊她现在的公司.

她从助理做起到最后总经理,给我讲她出去拿单的经历,说和员工的沟通,和老板的交流
,说的很感性,语速很慢,我们都要了冷咖啡,她告诉我不喜欢加糖,她喜欢慢慢品尝,
当讲到她陪客户喝酒, 陪客户疯狂, 她声音变的很小, 她说起她读EMBA的时候的抱负
,还有自己的经营理念,她突然问我:我们活着为了什么?然后哭了,对于她的突然变化
,我有点出手不及,不知道怎么办,我很坦诚的告诉她,我每天都在面对比自己优秀的多
人中间, 我很康奋,甚至有点紧张,不过我很少和candidate 从心理去沟通过,我都是
想用最专业的眼光和规划和他们交流,因为我一直想使自己变的更professional, 我说我
的缺点很明显,我普通话不怎么好,但我尝试和用
不同方式与别人交流,让别人认可我,我是一个从不放弃的一个,我的努力让我踏实,对
事业方面, 我们没有可比性,你很成功,你开的是宝马,如果不是工作的关系,这么高
档的咖啡厅我要等几年后才可以光顾。

这个时候她平静了许多,她告诉我,她很少流泪,甚至在丈夫面前,让她苦恼的是,这个
多变的社会大家都不按理出牌,你永远不知道别人下一张出什么牌。前面大家都是正人君
子,背后刀光键影。 她说不是因为压力,每个行业都有压力,普通的engineer 也有压力
,而是在思考选择什么样的生活方式。 只是觉得工作里面有很多恶心的地方。女性在IT
这个群体里面还处于弱势群体, 而且永远会是这样。最后她告诉我希望她不是我的
candidate,而是她的朋友。咖啡厅里放着轻音乐, 相差一个轮回的两个人就在那边无语
的座了半个小时。

学生真的太单纯了,我们大家都没有了解社会恶心的事情,我以后会专门写一些从我的
candidates口里听到高层恶心的事情。 其实很有趣, 我是感觉每天我都在拍电影,我终
于明白电影来源于生活这句话了。

和网友的交流让我很感动,大家对这群人的陌生,对他们的好奇,还有我们这个不够专业
的猎头公司代理的却是最高的职位, 唯一的理由是他们和每个IT公司的高层都很熟,这
也是本土猎头公司没发竞争的地方。

取得高薪的主要途径是先在创业型公司锻炼, 最好什么都干, 然后干到中高层!
如果你去联想, 没有10年你干不到manager, 如果你去start-up公司也许年就能实现这个
关键的一跳! 如果公司上市了, 奖励100-500万的很普遍, 反正是老外的钱! 现在去SOHU
的清华学生月薪在2000-3500元, 以前也是这个数, 但期权让他们发了!

今天上午比较忙,连续约了好几个candidate 谈,下午有点困就没有约人过来,老板那边
又接到一个单子,180万的年薪,唉!我自己要开公司能接到这样的单子就爽死了,一般
一个月就可以搞定,就可以拿到他们年薪的30%,就是54万,而一个公司10个员工总的开
支包含office 电话费也就3万,每月平均可以10个case, 当然一般都是60-100万的,赫赫
,钱基本被老板拿去, 唉,不平衡,我信有多职位都很有压力,但也有好多职位!

每天就是数钱,例如我的老板,资本家啊,不平衡。今天就谈一下猎头的程序吧,因为我
也是新手,了解的也只是很小的一个部分,不过这样我更会真实的记录下来。

猎头英文是headhunter, 专门找head(头)的行业,这里面的头有两方面的意思,一个是智
慧,就是那些有才华的人,一个就是头目,一般都是经理,总裁级的人,所以大家会误会
猎头公司只是高级中介,其实有点错误,我开始想尝试找一个确切的猎头公司的定义,不
过比较难,这个行业一直都是很模糊的定义。猎头找的是那些永远不愁没有工作的人,而
中介只是帮那些在找工作和找不到工作的人找工作。

美国发展最规范,而中国只停留在很狭小的范围,而美国,正规的都会包含人员的科学测
试和培训,公司机构和人力的咨询等,中国的相对粗造的多,象上海这类公司有400多家
,于是就导致好多公司很底的职位都会去代理,而沦落为简单的中介,几个月都拿不到一
个大的单子。

好多人都想刚刚毕业就进入这一个行业,几天粗造的了解给我的印象是一个专业的猎头人
,要具备Knowledge, Professional, and discernment. 这个行业对猎头的知识的要求是
对整个行业的了解, 给你一个case, 你要在半个小时内就要确定搜寻的范围,包含那些
公司,那些部门,可以挖到相关的人,然后是professional, 这个不仅仅是猎头这行业,
其它所有行业都这样,外在要穿着方面,内在沟通方式,你的职业性会让公司对你有信心
,给你更多的单子,让你的candidate更愿意把自己托付给你。最后的discernment是出成
果最重要的一环,你要对你推荐的candidate给出至少70%以上的判断, 这方面每个人用的
方法不同,有的喜欢打听candidate 的身边的人,有的喜欢用test, 而我个人比较喜欢面
视时的聊天,当然有些人很会谈,很会包装自己,不过猎头都比较喜欢没有包装的真实的
你,其实这样对公司也是对个人负责,因为工作是长期的 所以我一般都
会选择那些just be yourself的人。所以刚刚毕业的学生可以做这一行,但很有难度,例
如我,几乎每天都在学习,和每个经理人聊天都是在给自己上课,只是我适应的比较快。


还有我特地想提到的是大家不要频繁跳巢,昨天我推给公司的两个人都是每年跳一次的理
由被公司退了回来,越到高层对频繁跳越敏感。上次有些人问我给大家建议的直接去做销
售和管理, 怀疑刚开始去做管理不可能,其实你错了,我最近就接触好几个刚刚毕业的
本本去做项目主管,这太有可能了,一般的工程项目 技术含量要求不高,有一定的组织
能力完全可以胜任,做项目灰色收入也可管哟,上次有人提到销售淘汰很高,这是事实,
本来人生就是金字塔,你要想在上面就要接受残酷的淘汰,而做技术淘汰低是因为你在好
大学,本身就是在金字塔的上面, 销售只不过是不同大学刚刚毕业重新洗牌而已。而对
于管理,天赋占很大比重,对资源的调配,对市场的灵敏度,对员工的沟通,这方面的要
求是全面的。
怪了, 这些经理们很少看SINA/SOHU/TOM/163/等门户网站, 基本只看专业网站和管理类网
站!他们认为10天不看SINA.COM没问题! 但10天不看ICXO.COM 感觉不放心, 就怕业界的资
讯不知道,被同行笑话!

今天有点累,本来上午想统计一下,这群上层人物的工作经历,统计一半,后来接连面了
好几个人,就没有统计完,希望明天可以搞定,给大家看看他们的工作历程,怎样在短短
几年可以拿那么多:

下午和一个microsoft 的经理级人物面谈,然后就一起吃了饭,一个很魅力型的人,两个
人点了那么多菜,喝了点红酒。当时直犯愁,钱包里面只有300块钱,最后他很礼貌的买
单,我强烈要求我买,他拿出信用卡,说这是Gates 的钱,我们大笑,920快,我现在还
后怕,他如果真让我付,我可怎么收场啊,钱包只有300快啊。

其实和公司几个很有经验的consultant比我的能力只会比他们强,但他们面试时表现出来
的老道却是我让我甘拜下风, 因为做IT行业就那么几个人(我说的那几个牛人),每次
面试的时候很少涉及到技术方面的, 都是在讨论, 那个公司(管理)重新洗牌,那片战
场(销售)还有打的可能,哪个人从那个公司跳到另一个公司带走一班人马,他的那个朋
友在那个部门可以给他第一单,那个公司更有挑战性, 那个公司可以让你折寿几年,
Oracle 的单打独斗和霸道, IBM的team work, HP游离于中间,SUN 的日啦西山,EMC的
坟场,DELL的 嘟逼人,Hitachi的教条, 就那么点人在IT公司之间不停的换位,几句聊
天中,你就可以知道candidate 的人脉关系和管理和营销理念。因为我们代理的都是经理
级的人,所以不管挖他们去做技术主管还是公司主管还是销售主管,技术都不是我们问的
范围。

我现在在写的时候收到一个网友很诚恳的探讨,这位朋友, 原谅我没有得到你的容许就
直接应用你的话,原话:以前在学校找工作面试的时候,有幸跟东芝的中国区经理谈了一
个多小时。当时记得比较清楚的就是,这位经理告诉我,工作之后,有一个高质量的朋友
圈相当重要。因为这样就可以更有效地交流和学习。自己交往的人真的可以决定你成功多
大, 仅是给你的帮助还是给你的资讯,更重要的是给你潜移默化的影响, 我敢说干这么
短时间的猎头, 以后我不管做什么都是我一生巨大的财富希望明天给出的是真实的
case 和数据,以前写的都很泛。。。。

今天来点数据吧, 不过我觉得收集数据确实是枯燥无味的工作, 我看资料的时候, 经

过来问你今天打的电话好少哟。。。无语,

本来拿了一百份,不过到现在统计了80份,实在坚持不下去了,算了,就拿这80份做
sample 吧, confidence level 肯定比100份少,赫赫,我主要是从他们的薪水(年薪)
,学历,年龄,工作年龄方面统计的。因为这是对那个阶层的,不具有代表性(而且只是
IT 行业),只是给大家看一下, 给大家一个现象,评论是大家的事, 而且最近我也在
想自己的职业规划, 所以不知道哪一天就会改变自己的职业。不过还是希望坚持多写点
, 毕竟做高端猎头的人不多,有这个机会了解他们也不容易
薪水:40-60万, 60-80万 80-100万, 100万 以上
人数 19 22 23 16
80人中, 女性16, 男性64
公司管理人员,32 人, 销售经理 40 技术主管 8
学历: 本科 56, 研究生18人 (其中MBA 8人) PHD 6 人
没有人是本科以下的,可见本科还是大家成功的窍门砖。
好大学(我说的好大学就是我们大家心理明白的那几所:)7, 其他的:73。
工科: 62 人, 非工科方法18人,
工科是压倒性多数
平均年龄(这个开始我还用计算机算,很笨的说,后来用excel 直接就可以拿到)31,
最小的25, 最大的42,
跳槽的频率是2次,也就是4年跳一次,一般IBM, HP 的时间都有7-8的,不过其它的2年
多的, 2年以下的很少,到经理级别的26-29最多,所以27(一般工作2-4年)是大家的一
道坎, 这个时候跳一下,promote 到经理级,以后机会就不多了,然后是32—35 又一到
坎 能promote 到director, 这关过不去,那么你在小经理这边呆着吧。。。慢慢熬时间
拿点福利, 35也是一个Promote 到 VP, GM 的最好时间, 一般过了40猎头不会推荐你
的, 除非你特牛(特牛的人年纪对他没有障碍。

上次有人问我,魅力究竟是什么,其实我也说不明白,魅力究竟是什么,我的理解是他散
发那种气质,那种素养,让你不自觉的想去学习,模仿。今天都在搞这个数据, 被老板
批评了,不爽,另一家公司也不停打电话让我去签, 矛盾中,,祝大家快乐。。。。

很多人关心我们猎头公司如何寻找候选人, 说实话, 各个猎头公司的sources是不共享的
, 只能靠我们的长期积累. 以前icxo.com的世界经理人俱乐部的10多万会员还对我们开放
, 现在看管得很严! 据说icxo.com即将出版《世界经理人黄页》这个独特的职业经理黄页
, 到时候我们猎头公司和候选人就可以比较轻松的取得资讯了!

因为代理的是高端职位,对普通人比较遥远,可是我们每个人不都是一直向往吗?当然钱
不是衡量成功的唯一标准, 但我描述的是那一群职业的经理人,职业的销售人员,而不
是暴发户,他们身上迸发出来的是一种力量,一种信心, 我不知道大家爱不爱看中央台
的《《对话》节目, 其实每天我都在一群人对话,所以我每次晚上回来的时候都在想,
那些主持对话节目的人, 他们每次谈话结束后也会有和我一样的失落感吧,用一句人格
魅力去描绘这群人太虚, 我也一直在寻找他们成功的理由, 我们知道一个成才无非是实
力加运气,这样的描述对于我们普通人没有如何意义。

星期四上午在coffee bar面试了一个IT元老,原来他已经隐退,不过最后那个职位没有其
他人合适,和同事商量还是决定挖他,约他出来的时候没有什么难度,他驾车带我们去他
认为是最好的coffee bar,安静而且优雅,他说以前每次累的时候都会一个人来这儿,聊
起现在的IT市场,他说他还是很怀恋以前,可惜中国最早搞IT的那批人基本隐退了,而现
在市场竞争激烈,利润下滑,大量的新人出现。

最后他还是没同意出山,我们代理的那个位置还空着,也许他同意和我们见面,只想聊个
天吧。最后我要他给我点建议,我他问我什么方面的建议,我说职业经理人成功方面的,
 他想了想说:无止境地追求卓越,这种人要求自己是英雄,这样也是给别人尊重和自己
信心的唯一方法。 也严格要求别人达到他的水准。在工作上,他们会要求自己与部属“
更多、更快、更好 IT行业大家都在和时间赛跑, 不管是技术还是市场。

说实在的,我们这边代理的销售方面的人才多点,主要是销售方面的 mobility高点,好
多人不喜欢销售,可是不菲的薪酬终究还是有很大的吸引力。当然,除此之外,销售这份
具有挑战性的职业也让人有很多其它方面的收获:眼界开阔了,能力提高了,而且激发了
不少自己原来没有发现的潜能。

最近公司的单子很多,并不是IT市场好转,而是几大IT公司都在洗牌,我知道几家著名的
IT公司 上半年都在震荡, 所以有经验的人,最近一段时间出手是很好的时机,过了这段
时间想对会清淡些, 但中国经济的巨大磁场作用, 好多IT公司都来开办事处, 去那些
办事处比去大公司有前途,他们设办事处一旦发觉中国市场巨大, 就都会大举进入中国
市场, 那个时候你就是元老了, 一般公司都是设2年办事处, 然后大举进攻。

星期天,没面candidate,也没有新的case 和大家分享,有人在风中哭泣,有人迎风放屁
,对于没有工作经验的我们来说,你最需要适应的,一是听别人的声音,二是闻别人的味
道。其实好多公司不招应届的原因除了没有工作经验,就是跳槽频繁,我们公司内部的一
份数据也可以清楚的看出刚刚工作的人跳槽频率最高,这也可能和大家开始对职场的模糊
认识有关, 太多的是关注薪水而不是长远发展, 所以我建议大家找工作的时候尤其花很
多时间在找面试技巧, 谈判技巧,不如花多点时间考虑自己的工作的切入点。

和大公司的HR经理聊的时候,他们多数也都喜欢真实的你, 我上次也提到,面试需要准
备,但不需要包装, just be yourself, 你的包装在有经验的HR和部门经理面前,很容
易被看穿,而且他们,包含我都会反感包装后的candidate, 当然强调自己的优点,淡化
自己的缺点是必要的。

高年薪阶层人士的经历,都有一个共同点就是跳槽经历非常少,一般5到8年才跳一次,甚
至要10年以上才跳一次,而关键在于其最初择业的时候非常清楚自己要选择什么行业,而
不是像现在的大学生频繁的跳槽。跳槽如果跳得有系统,能够反映出毕业生有系统的规划
,有明确的目标,还是会获得企业的青睐。如果毕业生一直在追求自己的理想,虽然不停
地跳槽,但如果是执著地向自己的目标在前进的话,公司还是会考虑这样的人才。但如果
毕业生工作一年半以内就频繁换
工作,一般公司也坚决不会考虑。

所以我们猎头挑人的时候,第一眼看简历就是看跳的频率,这也在猎头行业达成的共识。
虽然我们的存在,也在促使跳槽的发生,那只是人才的合理条配, 我承认好多猎头公司
不负责的挖人,好的猎头公司不仅为公司找到合适的人,也要为我们的candidate 定下长
远的职业规划,不是为了挖人而挖人,相反有时候我和他们面试的时候,我会建议他不要
跳,嘻嘻,虽然我也是新人,不过在他们面前还是要装的professional 一点。

小常识:

高级蓝领”是蓝领人才的较高层次,其最重要的特点在于它不以理论和管理方面的综合素
质为判定标准,而以实际动手能力为判定标准,一个合格的“高级蓝领”应当具备相当丰
富的实际动手经验,应该属于操作型人才。

“低级白领”主要指办公室行政人员,他们从事的是单纯的脑力劳动,不同的是他们的脑
力劳动技术、知识含量比较低,可替代性比较大。所以小白领可替代性强,但每天川行于
商业大楼,过着小资一样的生活让他们很自我,这也是我为什么想离开现在的猎头去另一
家做项目主管。。。。

收集渠道:

我们猎头收集资料的渠道主要看报刊和网站, 报刊主要是计算机世界/中国计算机报;网
站去的最多的是ICXO.COM,他们对经理人收集得最快,而且按照CEO/CFO/CTO/CIO/CHO/..
.分得很细! 有一次, 一家跨国公司的大中华CEO突然打电话过来, 说在ICXO.COM 上看到
一位CIO, 要挖过去。。

关于创业:

我们清华的在校生和刚毕业的学生创业的很多, 可是几乎没有成功的! 根据清华科技圆的
统计, 以前进驻清华科技圆的187家企业, 185家已经倒闭了, 只剩下两个公司, 一个公司
叫视美乐, 已经卖给青岛一家家电企业, 还一个公司因为是联想公司高层的亲戚开的, 勉
强活着! 开公司必须具备3个条件:1) 资金, 2)独特的不容易被模仿的产品, 3) 人才. 中
关村年薪100万的个人远比年利润100万的企业多得多! 我建议在35岁前, 谨慎创业! 除非
你输得起!

2005年04月20日
Ant初始化数据库[实战]

大家知道,对于一个B/S系统,怎么初始化数据库是很重要的,下面是本人使用ant来初始化数据库中数据的一个经验,在这里分享给大家。

前提条件:安装了mysql数据库和ant,下载了mysql的jdbc驱动,mysql数据库已启动,并且path 和classpath已经配置好。

1,准备好数据库操作的文件。

—-photo_manager.sql—-

create database photo_manager;

use photo_manager;

create table news (
id int not null auto_increment,
title varchar(50) not null,
content text not null,
pub_time datetime not null,
primary key(id)
 )
;

create table phototype (
id int not null auto_increment,
name varchar(50) not null,
description varchar(100) null,
primary key(id)
 )
;

create table photo (
id int not null auto_increment,
typeid int not null,
name varchar(50) not null,
image mediumblob not null,
upload_time datetime not null,
primary key(id),
foreign key (typeid) references phototype(id)
on delete cascade on update cascade
 )
;

2,准备好ant的buildfile文件。

—-setupDB.xml—-

<?xml version="1.0"?>

<project name="setupDB" basedir="." default="build">

<property name="driver" value="org.gjt.mm.mysql.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/"/>
<property name="username" value="root"/>
<property name="password" value="root"/>

<target name="build">
<sql driver="${driver}" url="${url}" userid="${username}" password="${password}" src="photo_manager.sql">
<classpath>
 <pathelement location="d:\java\jdbc\mysql-connector-java-3.1.0-alpha-bin.jar"/>
</classpath>
</sql>

</target>

</project>
<!–powered by bruce chen–>

3,执行ant。

假设photo_manager.sql和setupDB.xml存放在d:\java目录下,打开dos窗口,切换到d:\java目录下,

执行ant -f setupDB.xml,就可以执行数据库数据的初始化了,谢谢观看,欢迎提出改进意见。