2005年07月28日

由于整合造成的重复工作和变动令人苦不堪言,这两天一直忙于将我的研究生网报管理系统整合到另外的一个框架内。为了在泰山会议前调出一个可以演示的版本,这两天折磨得我差不多快折寿了。

这几天遇到一个又一个难缠得异常奇怪的问题,注意下面提到的问题在整合之前都是不存在的。

今年为了整合需要要将我去年的根目录的链接形式变成一个虚拟目录/wb,这样我程序中所有以/开头,而前边没有附加request.getContentPath()的代码都要发生改变,几乎是痛不欲生。幸亏有学信的lf帮我整理jsp页面,否则就要自杀去了。完全就是体力活,还必须细心,细心再细心。

于是开始是找规律,把/zhaoban,/admin,/edu,/school开头的所有字符串都搜索出来,不能全局替换,因为有些前边写了request.getContentPath(),而大部分则没有写,于是一个一个的查出来,然后替换。好不容易替换完了,又出现了问题,servlet的"/xxx"的链接也要变成request.getContentPath()+"/xxx"的形式,于是再找再替换。替换完了,又出现了问题,原来struts中的action的/xxx.do,不需要写成request.getContentPath()+"/xxx.d"的形式,替换了反而不能正常工作,还好action不多,替换完成。

其实做这种工作,主要的不是技术问题,而是细心,同时要应付自己的心理障碍。一遍又一遍替换完成了,老是觉得还有没有替换干净的地方,测一测没有问题了,但是不一定什么时候又发现了一处问题,于是再去改。页面多起来,servlet多起来,头脑中已经不可能对所有的细节了熟于心,只能靠不断的测试,当然这又是额外的工作量。这里多亏有yzk帮我做测试工作,让我节省了很多精力,特别感谢一下。

经过两天三人的不屑努力,终于大部分功能显得没有问题了,只剩下一些硬骨头了,到了必须解决这些通过简单替换无法解决问题的时候了。难搞的问题有两个:

第一个

在我原来的系统中为了在页面跳转中能够传递request对象中的setAttribute的值,我没有采用方便的response.sendRedirect(),而采用了jsp:forward的形式,这个问题曾经困扰我,在以前的文章中曾经提到过。这个看似成熟的技术在去年的网报工作中是没有问题的。

但是今年改成/wb就出了问题。首先是在上传科目采集数据的时候三个表上传中间的页面跳转要经过一个jump.jsp页面跳转到wait.jsp,成功后会通过jump.jsp自动跳转到/put/success.jsp或/pub/failure.jsp页面。测试发现这里打死也过不去,报告是servlet的问题。开始怀疑在LEV1Servlet中的forward方法使用的不是相对路径链接跳转,而是绝对路径,做了一个试验发现不是的,在其他不涉及/pub/success.jsp和failure.jsp跳转的功能上使用相对context的路径都是没有问题的。那是怎么回事儿呢?回到上传的servlet中,让它跳转到/index.jsp,发现成功跳转,哈哈,原来果然不是servlet的问题。那就是jump.jsp的问题,那么问题出在哪里呢?反复检查jump.jsp文件,怎么也是发现不了问题。一个下午的时间就在这个怪异的问题中过去了,只好暂时把这个问题放一放,应付演示要紧。

晚上过来又去改别的跳转问题,发现凡是涉及到/pub/success.jsp或者/pub/failure.jsp的页面都会出现问题。于是反复检查这两个极其简单的页面,找不出问题。想了好久终于发现凡是跳转到这两个页面的页面都有一个共同的特征就是前边提到的要把requst.setAttribute中的数据带过来,显示在这两个页面上,都采用了jsp:forward,似乎是找到了规律,但是仍然不知道是怎么回事,为什么会造成这个原因呢?页面的异常提示说

org.apache.jasper.JasperException: /include/header.jsp(1,1) Page directive: illegal to have multiple occurrences of contentType with different values (old: text/html;charset=gb2312, new: text/html; charset=gb2312)
 

这个显然是不对的,因为我检查header.jsp发现关于text/html; charset=gb2312的设置跟这个页面是完全一致的。最后实在是没有办法了,只要到处乱找,一search发现只有在/pub/success.jsp和/pub/failure.jsp这两个页面中的是上面那个所谓的old设置:text/html;charset=gb2312,差在哪里呢,只差在中间的一个空格。wk,当我惊喜的发现这个天大的秘密的时候我都要崩溃了。我至今不知道为什么会因为一个空格之差造成在原来的"/"可以跳转而同样的配置到了"/wb"时却无法跳转,但改了以后的确就可以work了。

返回来我又开始琢磨jump.jsp是不是也是因为这个问题,因为jump.jsp也使用了jsp:forward,但是不幸的是这个似乎不是这个原因,因为报错都是不一样的。直到深夜我都无法解决,最后实在无奈了,我决定重写一个servlet,而不用原来的jsp进行跳转了。效果居然出人意料的好,可以正常实现我的跳转企图。

这个失败令我有一种强烈的失败感,本来以为做了两年的jsp/servlet,也该算是个熟手了吧,而且也没有用什么高深的struts,hibernate之类的东东,没想到还是会碰到一些莫名其妙的问题。

第二个

第二个是整合中的乱码问题。得益于系统原有的CodeConvert,原来在使用get方式传递parameter的时候不曾有过汉字的乱码问题。但是将我的系统整合到jboss以后却出现了这个问题。问了一下cwx和gl,原来jboss中的tomcat的server.xml中有一个URIEncoding=”utf-8“的设置,在我的环境中默认是没有这个设置的(我想默认的设置应该是ISO9805)。也就是说在默认的tomcat的环境中是没有乱码问题的,但是设置成了utf-8以后就有了问题。

为了解决这个问题我做了一个测试,结果如下:

1,默认的tomcat中的server.xml中URIEncoding项并没有设置,在接受参数时request.getParameter()后要经过一步CodeConvert.ISOToGBK,然后正常写入数据库;

2,把server.xml中的URIEncoding设置为utf-8,接受参数以后如果CodeConvert.ISOToGBK()转换一下,看到的是全角的???,不转换是半角的???;

3,把server.xml中的URIEncoding设置为GBK,接受参数以后就是汉字,无须CodeConvert.ISOToGBK(),如果转换则看到??

也就是说用URIEncoding="GBK"的设置或者不设置,都是有办法解决乱码问题的,惟独用了utf-8以后无法解决。我找gl了解情况,他说这样主要是可以兼顾繁体中文,他提了一个用模板的方法,但我这里没有用struts,所以到现在还不知道最后怎么来解决这个问题,还是等smzhou和cwx大牛过来问问他们吧。

这个显然是不对的,因为我检查header.jsp发现关于text/html; charset=gb2312的设置跟这个页面是完全一致的。最后实在是没有办法了,只要到处乱找,一search发现只有在/pub/success.jsp和/pub/failure.jsp这两个页面中的是上面那个所谓的old设置:text/html;charset=gb2312,差在哪里呢,只差在中间的一个空格。wk,当我惊喜的发现这个天大的秘密的时候我都要崩溃了。我至今不知道为什么会因为一个空格之差造成在原来的"/"可以跳转而同样的配置到了"/wb"时却无法跳转,但改了以后的确就可以work了。

返回来我又开始琢磨jump.jsp是不是也是因为这个问题,因为jump.jsp也使用了jsp:forward,但是不幸的是这个似乎不是这个原因,因为报错都是不一样的。直到深夜我都无法解决,最后实在无奈了,我决定重写一个servlet,而不用原来的jsp进行跳转了。效果居然出人意料的好,可以正常实现我的跳转企图。

这个失败令我有一种强烈的失败感,本来以为做了两年的jsp/servlet,也该算是个熟手了吧,而且也没有用什么高深的struts,hibernate之类的东东,没想到还是会碰到一些莫名其妙的问题。

第二个

第二个是整合中的乱码问题。得益于系统原有的CodeConvert,原来在使用get方式传递parameter的时候不曾有过汉字的乱码问题。但是将我的系统整合到jboss以后却出现了这个问题。问了一下cwx和gl,原来jboss中的tomcat的server.xml中有一个URIEncoding=”utf-8“的设置,在我的环境中默认是没有这个设置的(我想默认的设置应该是ISO9805)。也就是说在默认的tomcat的环境中是没有乱码问题的,但是设置成了utf-8以后就有了问题。

为了解决这个问题我做了一个测试,结果如下:

1,默认的tomcat中的server.xml中URIEncoding项并没有设置,在接受参数时request.getParameter()后要经过一步CodeConvert.ISOToGBK,然后正常写入数据库;

2,把server.xml中的URIEncoding设置为utf-8,接受参数以后如果CodeConvert.ISOToGBK()转换一下,看到的是全角的???,不转换是半角的???;

3,把server.xml中的URIEncoding设置为GBK,接受参数以后就是汉字,无须CodeConvert.ISOToGBK(),如果转换则看到??

也就是说用URIEncoding="GBK"的设置或者不设置,都是有办法解决乱码问题的,惟独用了utf-8以后无法解决。我找gl了解情况,他说这样主要是可以兼顾繁体中文,他提了一个用模板的方法,但我这里没有用struts,所以到现在还不知道最后怎么来解决这个问题,还是等smzhou和cwx大牛过来问问他们吧。

2005年07月26日
暴雪风云录
(2005.07.25)  



 

[引子]

早饭后,弗兰克.罗伯兹像往常一样一路小跑的来到自己的邮箱前去收取自己的信件。这次他依旧是满载而归,顺手拆开了其中的一封,是一位出版商写给他的。

“我知道也许我犯了错,也许是十年以来最大的错,但是,我仍然要拒绝你的作品。”

罗伯兹手里捏着这张信纸,茫然的站在院子中,他不知道还是不是应该拆开剩下的信,就像被相恋
多年的女友突然之间抛弃在舞会上一样,罗伯兹的大脑不听使唤的僵在了那里,下面,该怎么办?


是一部花费了自己七年心血的作品,为了这部作品,他甚至花费了整整六年的时间去研究自己根本不曾接触过的阿拉伯文明。为了这部作品,他甚至还放弃了自己在
大学中所学的专业。但是罗伯兹没有想到的是多年的辛劳换来的竟然是这样的结果,但是为了这部作品,他不能放弃。等待是需要耐心的,在等到第十三个出版商的
时候,罗伯兹终于等到了自己想要的结果,他的作品出版了。

这部作品就是日后风靡于欧美的科幻小说《沙丘魔堡》。但是罗伯兹更不会想到的是在四十年后,世界上的任何的一个人都能够随意的掌握他所创造出来的这个荒凉、神秘的外星世界。个中的缘由,还得让我们从八十年代说起。

[暴雪到来之前的故事]

拉斯维加斯,一个游戏金钱的地方,当150多年前美国摩门教徒们创建这
所城市的时候他们也许根本就不会想到,就是这样一个建立在沙漠中的城市,最终会成为一个让全世界瞩目的地方。赌博的巨大刺激吸引着来自世界各地的人们来此
一试手气,同样也吸引着世界各地的富商来此谋筹财路。

暴雪风云录
拉斯维加斯街景


位名叫布莱特.W.斯帕里(Brett W
Sperry)的年轻人就生活在这座城市之中,在被一家教育公司扫地出门之后,斯帕里开始了自己游荡的生活,最终,他凭借自己在计算机方面的特长,和一家
名为Epyx的公司签订了合同,为他们开发一款名为《Dragonfire》的计算机游戏。但是穷困潦倒的斯帕里在工作的最后关头遇到了麻烦,在他需要为
发行商打印最终成果的时候他发现就是拿出自己所有的积蓄也换不来一台最普通的打印机。

他找到了自己的朋友,在“23世纪计算机”商店打工
的路易斯.卡塞尔(Louis
Castle)。这是一间内华达最古老的计算机商店,只有在这里你才能买到Macintosh。当时的卡塞尔拥有着一台点阵式的打印机,这就是斯帕里最需
要的。接下来的时间里,他们开始了对未来的憧憬。他们常常花上很长的时间驾车去穿越内华达州的沙漠,来到加利福尼亚的Westwood市,这是他们最喜欢
来的一座小城,在这里他们看电影、逛商场、闲坐在街边的咖啡馆里聊着各自感兴趣的话题。

悠闲的时光很快就过去了,1985年的夏天,卡塞尔全家像每年一样,又开始动身前往Yosemite避暑,临行前,卡塞尔邀请斯帕里一同前去。

暴雪风云录
“我们自己开公司吧――五五分成。”斯帕里向毫无准备的卡塞尔说出了自己的想法,惊愕之余,卡塞尔最终还是被斯帕里说服了。斯帕里激动的冲向了街边的电话亭,他要同自己的东家Epyx公司做笔交易,他要用自己公司的名义为Epyx编制下一款游戏。

公司总得有自己的名字。在枪毙了几个糟糕的名字之后,卡塞尔提议用“Westwood”来做公司的名称,因为那里给他们留下了太多的美好回忆,斯帕里在这个名字后面又加上了Associates,因为斯帕里感觉到他们要永远做一位伙伴,而不是雇员。

暴雪风云录

间很快就过去了,转眼的时间,90年代到来了,在过去的五年中,卡塞尔和斯帕里带领着Westwood一刻不停的奋战着。除了少数的几款由自己设计的游戏
之外,他们的日常工作几乎全部被移植游戏的工作占满。就这样在整个80年代他们都在移植中度过了,他们甚至自我解嘲似的说出了“我们移植一切,包括
Bug”这样的名言。

暴雪风云录
Westwood的作品

1992
年,Westwood决定让自己成为一间专业的游戏工作室,于是他们转而加盟了维真公司,在摆脱了发行和商务等等一系列的烦恼之后,Westwood开始
了自己高速的发展历程。几款连续的成功作品让Westwood名声大振,但是也许就连Westwood也没有想到,下面的事情,将彻底的改变
Westwood的命运,甚至改变整个PC游戏产业。

暴雪风云录
《沙丘Ⅱ》的游戏画面


章的开头我们说到了罗伯兹的科幻小说《沙丘魔堡》,但是在40多年后的1992年,斯帕里和卡塞尔决定将它搬上计算机的屏幕。虽然在斯帕里的眼中,这只是
一款小型的战争游戏,但是他们一刻也没有中止过为这款游戏添加新创意的想法,因为他们相信,人们有理由在这款游戏中获得足够多的乐趣。1992年的圣诞节
马上就要到来了,卡塞尔和斯帕里决定在这个欢庆的节日中将自己的新游戏推到世人的面前,《沙丘Ⅱ》面世了。就仿佛在Pentium时代我们就见到了
DOOMⅢ一样,当时的人们是无法用语言来描述自己对于《沙丘Ⅱ》的感觉的。在游戏中,我们俨然已经成为了一个万能的上帝,主宰着这个神秘的外星世界。斯
帕里和卡塞尔对于界面的易用性和画面的完美性的追求在这款游戏中得到了充分的体现,但是也正是《沙丘Ⅱ》开创了一种全新的游戏类型——RTS(即时战
略),这种全新的游戏模式将Westwood送上了巅峰,西木,一个响亮的名字开始在玩家中叫响。

[大树底下好乘凉]

九十年代的感觉是明亮和鲜艳的。

加州大学洛杉矶分校的一间
计算机教室中,一群大学生正在专心致志的摆弄着电脑。一个名叫艾伦•阿德汗(Allen
Adham)的年轻人站了起来,他输入一串口令将自己的计算机锁住之后就离开了,但是5分钟之后,他仍旧没有回来,而此时早已过了系统规定的时间,计算机
自动解锁了。坐在这台计算机旁边的一个学生注意到了这个情况,他偷偷的向艾伦的计算机中输入了一个全新的口令,然后就开始等待着一出好戏的上演。

艾伦回来了,输入口令,登陆系统,一切无恙。这个替换了口令的学生坐不住了,他向艾伦询问是怎么进入了系统。

“输入自己的密码不就进入了系统了吗。”艾伦不在意的耸了耸肩膀。

“那你的密码是什么?”

“这个我可不能告诉你。”艾伦感觉这个人有点奇怪。

“口令是JOE,对不对?”

艾伦吃了一惊,这个人怎么会知道自己的密码?一番解释之后,原来这个名叫迈克•莫怀米(Mike Morhaime)的学生输入的竟然也是JOE,他们选择了同样的口令。

暴雪风云录

大学的时光总是过的很快,毕业的时间转瞬及至,此时的艾伦和迈克早已是形影不离的好友。在大学的这段时间里,艾伦已经开始为一些公司设计游戏,而迈克也逐渐被这些源代码的威力所吸引,大学毕业后,他们顺理成章的开始了自己的创业历程。


为任天堂的红白机设计了几款游戏之后,1991年,一个属于他们自己的公司Silicon &
Synapse(硅与神经键)公司成立了。但是就同Westwood一样,迈克和艾伦很快就决定枪毙这个名字,因为在他们看来,神经键是个什么东西?几乎
没有人知道。Chaos,也许这个名字不错,但是麻烦接踵而至,因为早已有了一家拥有同样名字的公司。直到有一天迈克在翻字典的时候,Blizzard
(暴雪)这个词跳入了他们的眼帘,几乎是瞬间的决定,从此Blizzard开始了自己辉煌的历程。

暴雪风云录


雪觉得自己应该做点什么了,在被著名的游戏发行公司Davidson &
Associates收购之后,艾伦和迈克所领导的暴雪已经完全成为了一间专业的游戏设计室。时间已经到了1994年,此时的Westwood已然如日中
天,《沙丘Ⅱ》更是牢牢占据着全世界玩家的硬盘,暴雪悄悄的将目标锁定了。所有美好的事物总是经常被人模范,就像以前的赵老焉,就像现在的iPod,但是
模仿似乎又是一件吃力不讨好的事,成功了自然流芳百世,失败了则不免遗臭万年,虽然如此,模仿者依然络绎不绝。艾伦和迈克发动了公司所有的15名员工,因
为他们已经牢牢的掌握了《沙丘Ⅱ》的所有优点,同时也洞悉了它所有的缺点,暴雪的任务,就是将RTS这一概念完美完美再完美。

外面的玩家
已经有点不耐烦了,毕竟一个玩了两年的游戏已经很难再勾引起人们足够的兴趣了,他们急切的想体验沙丘之外的乐趣。暴雪是深知这一点的,因为暴雪的每个员工
都是十足的游戏发烧友,而也正是因为这一点,他们才获得了在暴雪工作的机会。为了让玩家体验到完全新鲜的感受,暴雪决定将游戏的背景设定在一个奇幻的世界
中,刀剑和魔法代替了飞机和大炮,漫天的黄沙也换成了葱郁的森林,暴雪也许根本就不会想到,艾泽拉斯的世界会如此的迷人。

暴雪风云录
《魔兽争霸》游戏画面

1994
年的圣诞节即将到来了,Westwood的卡塞尔端坐在计算机前,屏幕上是一个他自己从来没有见过却又感到十分熟悉的游戏,这就是《魔兽争霸》。暴雪成功
了,当时正在欧美流行的奇幻文学自然而然的将这款游戏带到了每一位玩家的家中,早已熟悉了沙丘的玩家很快就学会了如何来操纵这款游戏,因为他们在骨子里都
是一样的。“当我第一次看到这款游戏的时候,它对我们产品的‘模仿’的确让我大吃一惊”,很多年之后卡塞尔是这样回忆当时的情景的。但是暴雪还是在《魔兽
争霸》的身上打下了自己的烙印,联网对战模式、随机地图生成器、大量快捷键设定,《魔兽争霸》所带给玩家的这些全新感受无一不在指引着未来游戏的发展方
向。

三年前,Westwood还是一棵参天大树,漫无边际的向四周伸展着自己的枝叶,尽情享受着每一缕阳光。三年后,从自己根部汲取了足够多养分的暴雪已经冲破了那厚密的树冠,品尝到了第一缕阳光的味道。
命令与征服]


功后的艾伦和迈克开始将关注的目光越来越多的投向自己的对手,以希望在那里获得更多的经验和诀窍。但是他们却发现了一个自己根本不能容忍的问题,那就是相
当数量的顶尖游戏设计师竟然根本不会玩游戏!“看到许多制作游戏的人根本不玩游戏真是让我大吃一惊,”日后成为暴雪重要领导人的Bill
Roper这样说,“在面试中这种情况相当明显。我们将问应聘人最喜爱的游戏,那个游戏中他们最喜爱的角色,而且要进行深入的讨论。如果被接见者认为可以
通过前一晚阅读最新的游戏杂志来增加他们头脑中的游戏知识,他们会发现这种方式绝对会适得其反。如果你告诉我们你最喜爱的游戏是街霸,我们就会想知道对于
每个角色你喜爱的格斗招数。”直到现在,仍然有人在批评暴雪的这种做法让它丧失了很多顶尖的人才,“但是如果一个美工可以真正去思考他们的工作在游戏环境
中的表现,那么这绝对是价值无法衡量的财富。”Roper这么解释。

暴雪风云录
Bill Roper


雪的成功是必然的,但是此时的Westwood却是需要倍加努力的时候。虽然斯帕里和卡塞尔在表面上说着希望暴雪成为一个伟大对手的客套话,但是在
Westwood内部,一项工作正在有条不紊的进行着。因为Westwood也早已注意到玩家已经对《沙丘Ⅱ》感到厌烦了,他们急需一款全新的游戏来继续
吸引玩家的注意力,只是《魔兽争霸》的问世让这种需要变的更加的迫切。

斯帕里带领着自己的团队已经进入到了游戏开发的最后阶段。和《沙丘
Ⅱ》那幻想中的世界不一样,这次斯帕里似乎要回到了现实的世界中,他要把当今世界的政治版图作出一个翻版来。这就是《命令与征服》(C&C),一
款让Westwood登峰造极的作品,一款让Westwood盛极而衰的游戏。

暴雪风云录

了1995年年中的时候,《命令与征服》发布了。没有了幻想中的不切实际的武器,没有了令人倍感荒凉的外星世界,所有的兵种和建筑都复刻于我们的世界,一
切都以我们身边的事物为原型,GDI和NOD双方的厮杀很快就让全世界的玩家沉浸在其中,Westwood也再一次向人们证明自己才是RTS领域的最强
者。

此时的暴雪一刻也没有停止工作,他们正在继续讲述着艾泽拉斯世界中的故事,但是他们万万没有想到,一个更加古怪的念头正在斯帕里的脑
海中逐渐成形:他想把二战中的场景搬上计算机的屏幕,恐怖、残酷、杀戮,战争中所有最糟糕的一面都将得到展现。但是他这个念头很快就得到了维真公司的否
定,原因只有一个:这款游戏最终将面向全世界发行。此时斯帕里才恍然大悟,在他的设想中,游戏已经涉及了太多的政治敏感话题,单单描写德军的情节,就别想
让这部作品在德国发行。

《命令与征服》的成功让Westwood充满了空前的自信,斯帕里决定继续之前的古怪念头,为《命令与征服》推出
资料片,背景依然是现实中的世界,但是游戏的主角,却已然换成现实中的国家。1995年的圣诞节即将到来了,此时的游戏销售排行榜上,《命令与征服》已经
遥遥领先于其他的任何游戏。如果此时的暴雪消沉下去,那么Westwood的下一款游戏也许就会让玩家永远的忘记暴雪这个名字。暴雪提前采取了行动,已经
悄悄开发了十个月之久的《魔兽争霸Ⅱ》如期上市了。

暴雪风云录


场是检验一款游戏成功与否的最佳标准。仅仅使用了十个月就完成的《魔兽争霸Ⅱ》在技术上不但支持了高分辨率图形,更是在游戏中加入了支持8玩家局域网对战
的功能。但是一款游戏如果仅仅只是拥有技术上的优势是远远不够的,首先必须是一名游戏爱好者的聘用政策让暴雪能够更多的照顾玩家的游戏感受。战争迷雾,左
键选择单位、右键点击移动,这两个看似不起眼的小改动不但确立了暴雪今后游戏的操作风格,更是成为了大多数RTS游戏所遵循的操作标准。不仅仅只是这些,
暴雪开始在这款游戏中注意游戏本身的平衡性,各个种族之间绝对的公平保证了游戏自身的可玩性,各种各样的战术开始被创造出来,以最大限度的利用种族的自身
优势。很快,《魔兽争霸Ⅱ》的销量奇迹般的突破了一百万套,而暴雪,也开始了自己的蜕变。

《魔兽争霸Ⅱ》的成功极大的刺激了
Westwood,虽然此时C&C仍然占据着游戏排行榜的第一位,但是如日中天的Westwood对于暴雪将自己的风头抢去是难以容忍的。斯帕里
的构思终于变成了现实,1996年中的时候,《红色警报》发布了。作为C&C的资料片,人们吃惊的发现《红警》竟然从头到脚几乎有90%的内容是
全新的,由此看见Westwood对于这款游戏的态度。Westwood打出了一张好牌,由于当时Windows95的发布,一场Windows的风潮席
卷全球,于是Westwood率先推出了Windows版本的《红警》,为后来的迅速蹿红埋下了伏笔。相比于奇幻中的世界,现实中的故事更能引起人们的兴
趣。以冷战为背景,玩家可以操作美苏两个超级大国中的任意一方来完成自己称霸世界的雄心,游戏不但有着强烈的真实感,完善的联网对战功能更是让玩家的兴趣
大增。其实我们已经毋需多言了,相信每一个经历过那个时代的玩家都会对那段时光深有体会。不论我们走进哪一家网吧,或是哪一家电脑游戏室,首先听到的几乎
都会是厮杀于红警中的玩家的喊叫。记得有人曾经说过,红警定将为21世纪培养出一批合格的将军。Westwood最为辉煌的时刻就这样到来了,但是斯帕里
和卡塞尔没有想到的是,对于游戏性理解的偏差和随之而来的急功近利让Westwood走入了歧途,并渐行渐远,直至再也没有找回最初的方向。

[中世纪的幻想]

这是一件只有极少数玩家知道的事情。在1994年的芝加哥消费电子产品展上,
阿德汗结识了Condor公司,共同的游戏理念很快就让两家公司走到了一起。在经过几个月的初步设计之后,Condor的三位创始人Max
Schaefer、Dave
Brevet和Erich提出了动作RPG游戏的概念。新生的事物总是很难被世人接受的,他们的概念遭了绝大多数游戏发行公司的冷遇。无奈之下,Dave
拨通了阿德汗的电话,暴雪也许是他们最后的知音了。相同的设计哲学很快就让他们达成了共识,“我们都热爱中世纪的东西、剑、怪物、财宝等等,”Dave说
道“你只会感觉到这种结果太合适了。”1995年1月份,暴雪和Condor达成了制作一款全新游戏的协定,暴雪为Condor提供30万美元的开发资
金,Condor将全权负责这款游戏的开发,这款游戏就是《暗黑破坏神》,一款日后每一个暴雪人都深深迷恋的作品。

暴雪风云录
《暗黑破坏神》的关卡设计师


年以后,Dave承认当时暴雪所提供的30万美元是难以满足《暗黑破坏神》的开发需要的,因为绝大多数的Condor员工都投入到了这款游戏的开发之中。
幸好当时另外一家游戏厂商3DO正在让Condor为他们的新主机开发一款游戏,于是Condor就暗自挪动了3DO为他们提供的开发款项,就这样,
3DO就在自己毫不知情的情况下间接支持了《暗黑破坏神》的开发。

就在Condor在预算不足的情况下为完成《暗黑破坏神》而努力工作的
时候,暴雪的开发团队已经在开发《魔兽争霸Ⅱ》之余,注意到了这款游戏所采用的回合制游戏方式。几乎是凭借作为一名游戏玩家的直觉,他们就感觉到即时制能
让玩家体会到更多的游戏乐趣。长时间的争论自然是难免的,但是暴雪的开发团队通过不断的向Condor施压,最终让他们改变了想法。三个小时后,即时制的
《暗黑破坏神》运行在了Condor开发小组的计算机上,一切都变的流畅了起来,畅快的游戏感受让Condor小组的成员不得不承认,也许这是《暗黑破坏
神》在开发过程中遇到的最棒的事情。

此时的媒体终于开始注意到暴雪和Condor正在开发的这款游戏,暴雪将要涉足RPG领域的消息开始
在玩家中不胫而走。暴雪似乎意识到了《暗黑破坏神》对自己的重要性,而Condor卓越的才能更是让暴雪钦佩不已,更何况此时的暴雪和Condor在感情
上已经如一家兄弟。虽然有更多的发行商愿意出更高的价钱来收购Condor,但是Max和Dave最终还是带领着Condor走到了暴雪的身边。于是在
1996年3月,Condor成为了Blizzard North,北美暴雪成立了。

暴雪风云录
Max和Dave沉醉于暗黑破坏神中,墙上是象征荣誉的“五年之剑”


美暴雪的成立进一步加快了《暗黑破坏神》的开发计划,圣诞节前发行已经成为可能。暴雪的开发团队开始考虑《魔兽争霸Ⅱ》之后的作品了,虽然《魔兽争霸Ⅱ》
让暴雪成为了一家足以抗衡Westwood的公司,但是此时的RTS代名词却是C&C和红警。“我们想暂时抛弃魔兽系列,并寻求一个新的突破,每
个人似乎都在想着做一款科幻游戏”,Bill roper这样回忆着当时的情景。

《暗黑破坏神》的开发已经进入了尾声。1996年的E3
大展到来了,此时的暴雪已经成为和Westwood、id
Software一样的厂商,他们是所有人都像击败的目标。暴雪动用了三套系统来展示自己的作品《暗黑破坏神》,但是这似乎远远不能应付玩家对于它的热
情,到了展会的后期,演示的系统不得一再添加,直至占用了暴雪所有六套系统中的五套。一个更加令人兴奋的消息传来了,暴雪宣布将在《暗黑破坏神》中附带一
个名为Battle.net的免费互联网游戏服务,战网问世了。

暴雪风云录
1996
年的圣诞节到来了,商店的货架上却看不到《暗黑破坏神》的踪影,暴雪的承诺没有兑现。而此时在暴雪的内部,熬红了双眼的Max和Dave正在带领着手下的
员工们为最后加入的Battle.net做出最后的努力,这一切都是为了达到玩家和自己为游戏设定的双重标准。1997年来到了,《暗黑破坏神》终于压盘
上市了,玩家们的热情是不可抵挡的,短短18天内,《暗黑破坏神》的销量突破了一百万套,暴雪对完美的追求再次得到了丰厚的回报。

[我的征途是星辰大海]

1996年的E3大展造就了《暗黑破坏神》的成功,同时也造就了另外一
个悲情英雄《星际争霸》。在暴雪使用的六套展示系统中,有三套用来展示《暗黑破坏神》,另外三套展示的,就是Bill
roper口中的“每个人都想制作的科幻游戏”《星际争霸》。但是玩家对《暗黑破坏神》的热情和对《星际争霸》的冷遇形成了鲜明的对比,最终《星际争霸》
的展示平台被剥夺到了一套,暴雪似乎将要推出自己第一款失败的作品了。

暴雪风云录

继续开发,还是重头再来,抑或是就此取消,E3归来后的《星际争霸》面临着一个艰难的选择。紧接着到来的1997年给予了暴雪足够的动力去作出最为艰难的
选择,《暗黑破坏神》的成功让暴雪明白了优秀与伟大之间的区别。最终,暴雪选择了最为漫长和痛苦的道路,《星际争霸》从头再来。

暴雪风云录
最初的《星际争霸》游戏画面


莫怀米在2000年接受一家杂志的采访时曾经承认,《星际争霸》确实是他们“最漫长和最痛苦的一个”,但是暴雪制作一款伟大游戏的决心却从来没有改变过。
之前的《星际争霸》最终只保留了种族的设定,其余的设计被全部抛弃。经过半年多的重新设计之后,全新的图形引擎和渲染影片终于被设计出来,第一个测试版的
《星际争霸》即将出炉。任何人在回忆起这段时光的时候总是能够记起《星际争霸》战役编辑器的设计师Jeff Strain和他的太太Annie
Strain。

“当时我在检查了自己的笔记本电脑之后,就飞奔似的回家了,因为我的太太已经怀孕9个多月,她已经开始产前的痉挛了。”
Jeff自己这样回忆到,那个时候全公司都在加班加点以赶在节假期前推出《星际争霸》,但是妻子的电话不得不让Jeff在最后的关头离开自己的工作岗位。
“你要知道,在妻子痉挛真正开始之前我在医院完成了5到6个小时的编程工作。”当Annie从产前的麻醉状态中苏醒过来的时候,Jeff正在将自己的手从
键盘上拿开。

“在我们女儿出生的时候你还在为这个该死的游戏工作吗?”Annie似乎有些愤怒。

“Annie,这可不是’该死的游戏’。它是Star craft!”Jeff解释到。
暴雪风云录
可爱的Jeff Strai

尽管多年后Jeff已经离开了暴雪,并同其它从暴雪离开的同事共同创立了Arena.net,但是Jeff仍然对自己妻子的评语感到“耿耿于怀”,也许正像Bill roper所说的那样“让我们回到工作室坚持下去的动力是制作伟大游戏作品的机会。”


整三个月的时间,暴雪的所有人员都放下了手中的工作,全力进行《星际争霸》的测试工作。虽然所有的设计师发疯了似的工作着,但是当1997年的圣诞节到来
的时候,暴雪还是宣布《星际争霸》需要推迟了,三年来,暴雪第一次没有在新年的时候推出自己的作品。但是暴雪却并没有这样看,“公司的每个人都知道只需几
个令人失望的游戏就会毁掉我们所有的声望。”星际争霸的总设计师Bob
Fitch后来回忆到,看起来他似乎有点心有余悸。但是此时的暴雪内部却并没有出现任何的差错,一切都在按部就班的进行,重新调整过后的游戏在种族的平衡
性上取得了长足的进步,因为暴雪不想学Westwood那样让自己的游戏变成疯狂的坦克大战。

暴雪风云录
1998
年3月,当《星际争霸》终于摆上了商店货架的时候,所有的结果都证明了暴雪在96’E3大展后作出的决定是如何的正确。仅仅在发行当月《星际争霸》就冲上
了游戏销售排行榜的首位,而在韩国,星际的销量更是很快的就突破了一百万份。但是有谁能够知道,在星际大获成功的背后,却有着另外一个游戏的凄凉结局。这
个游戏就是Roper宠爱的Warcraft Adventures,它讲述了萨尔的故事,在Warcraft
II结局中人类封闭了通向德拉诺的入口,随后萨尔被抓到了艾泽拉斯。虽然这是暴雪第一款采用3D引擎的游戏,虽然不少的玩家和媒体都非常看好这款游戏,但
是在暴雪内部却始终认为这部作品将会是他们第一款销量达不到百万级别的作品,最终暴雪还是在98年E3大展到来前宣布Warcraft
Adventures被无限期推迟。

看着自己的同事们一直拼杀在游戏设计的第一线,此时早已是公司总裁的阿德汗再也坐不住了,抑止不住的创作激情让他毅然放弃了自己的职务,转而投入到了真正的游戏开发中去。但是此时在暴雪的内部,也许没有一个人会想到《星际争霸》会产生怎样的蜕变。

暴雪风云录
Battle.net
战网,相信如果没有它的存在,星际也许只会安静的待在每一个玩家的硬盘上,但是正式因为战网的存在,才让全世界的玩家走到了一起。在星际最初发行的版本
中,不论是暴雪还是玩家,星际的概念对于他们来说还仅仅只是一款游戏。但是到了资料片《母巢之战》发行的时候,游戏,这个被无数的家长视为洪水猛兽的科技
怪物终于在星际的带领下走进了现代竞技体育的殿堂。也许家长们无法理解当每一个痴迷于星际的孩子在看到GRRR用几乎闻所未闻的克隆手法让9个小飞机在瞬
间逐个撞杀OCTOBER的2个吞噬者和5条飞龙,让己方遭到缓速魔法袭击的飞龙群反败为胜的时候,他们会是一种什么样的心情。“微操作”、“大局观”、
“战术”等等开始流行了起来。1.08版所重新调整的种族平衡让星际向竞技体育更加的靠拢,而游戏录像功能的推出则让更多的玩家能够在第一时间看到世界顶
尖高手们的风采。而此后暴雪每次推出新的游戏版本,全世界的玩家立刻就能研究出无数的战术和绚丽的打法。

2001年9月5日的韩国汉城。


雪不应忘记这一天,电子竞技不应该忘记这一天,每一个星际玩家更不应该忘记这一天,历史上第一次电子竞技大赛WCG开幕了。林耀焕,一个曾经两次参加考高
而未曾中的的高中生,一个在星际的世界中名叫Slayers_BoxeR的年轻人,是他在比赛中让《星际争霸》得到了升华,是他让人们重新认识了《星际争
霸》这款暴雪3年前推出的作品。当一整队的机甲巨人从多架运输机上从天而降的时候,人们根本不相信这是人力所能达到的,BoxeR做到了。高阶空投,机械
化打法,三基地战术等等一系列的创新被BoxeR源源不断的创造了出来,而2002年WCG决赛中的那场璀璨对决,更让BoxeR无愧于最伟大的星际选手
的称号。也正是这样的创新精神,让《星际争霸》一次又一次的升华,成为了真正意义上的一种文化,成为了一种创新的精神。

暴雪风云录
酷酷的BoxeR的海报

暴雪风云录
星际双打冠军!WCG赛场上飘扬的五星红旗

2005年,在暴雪推出《星际争霸》7年之后,最新版本的1.12版发布了,这似乎向人们预示着,《星际争霸》的魅力是永恒的,也许有关星际的话题仍将继续下去……

[走向冰峰王座]

让我们再次回到1997年。当《暗黑破坏神》在市场上获得了巨大的成功的时
候,暴雪的母公司Davidson就急切的希望北美暴雪能够在那一年的暑期推出它的续篇,但是这个计划马上就被Dave否定了,因为在他们看来,《暗黑破
坏神》是唯一的。在经过一个月的休假之后,北美暴雪的全体成员坐在了一起,下一部作品应该是什么呢?“《暗黑破坏神Ⅱ》!”,人们几乎异口同声的说出了自
己的想法。

暴雪风云录
站在暴雪总部门前的莫怀米


年后,《暗黑破坏神Ⅱ》依旧在继续进行着自己的开发。因为在开发临近尾声的时候加入的一些全新特性如关卡零载入时间、角色特色技能树等让Dave他们的开
发团队不得不重新开始大量的工作。而在距他们500英里之遥的暴雪开发团队此时也遇到了棘手的问题,阿德汗领导的开发小组决定继续讲述艾泽拉斯的故事,只
是这次他们将第一次使用全3D引擎,而且阿德汗还希望完全改变RTS的游戏风格,使之看起来更像是一款RPG游戏。

时间到了1999年,
在这一年9月份在伦敦举办的欧洲计算机贸易大会上,暴雪首次向外界披露了《魔兽争霸Ⅲ》的消息,并进一步向外界透露了《暗黑破坏神Ⅱ》的开发情况,并且明
确的表示《暗黑破坏神Ⅱ》肯定会在年内发布。但是情况却恰恰与之相反,这款从1997年就开始进行开发的游戏竟然在两年之后依然有大量的工作需要完成。新
的创意和特点被不断的添加进去,“每当早上我走进办公室对大家说我有一个更好的想法的时候,几乎每个人都忍不住的呻吟起来,因为他们知道这又意味着三个月
的没日没夜。”Dave回忆起当时的情景。1999年的圣诞很快就到来了,但是《暗黑破坏神Ⅱ》的开发进度似乎没有一点的进展,虽然他们从4月份以来就再
也没有休息过一天。像之前的游戏一样,暴雪再一次宣布新游戏将无法按时发布了。

暴雪风云录
《暗黑破坏神》开发小组全家福


着新年的到来,玩家们对暴雪的期望也空前的高涨了起来,因为他们知道暴雪肯定会在新的世纪中为大家带来两款众人期盼已久的作品。Dave和Max知道自己
的作品无论如何都要在年中的时候完成了,当然这也意味着他们将在超过一年的时间里得不到任何休息的时间。而此时在暴雪的开发小组所遇到的问题似乎有着更大
的麻烦,因为已经在开发中的《魔兽争霸Ⅲ》有可能重蹈最初版本《星际争霸》的命运,也许它要被全盘否定了。玩家对于《魔兽争霸Ⅲ》的看法分成了两派,所有
喜欢暗黑破坏神的玩家几乎都在反对将RPG的元素添加到魔兽争霸中,而所有喜欢魔兽的玩家却几乎一致的支持这项决定,暴雪陷入了两难的境地。“小组的每个
人看了这个游戏都说,你要知道,这种效果并不怎么样。我们需要做一些事情了”,作为暴雪领导人之一的Rob
Pardo日后这样回忆说。在让《魔兽争霸Ⅲ》重新回到制图板上之后,大量的RPG元素被取消了,只能跟随英雄的视角设定被否决了,自由卷屏重新回到了游
戏中来,一切都回到了RTS游戏的根源。

玩家们对于这样的结果自然是不能满足的,已经习惯了暴雪的创新风格的忠实玩家开始诉说着自己的不
满,因为这在他们看来这是暴雪在为保证自己游戏的销量而不敢承担创新的风险。“如果你开始认为你不能承担太多风险,而肯定可以创造出顶尖作品的唯一方式是
重复以前游戏的成功路线,那么我认为压力很可能真的会抑制创造性。”已经离开暴雪的Jeff Strain似乎也在承认着暴雪当初的想法。但是Bill
Roper却不这么认为,“我们希望Warcraft II到Starcraft或者Diablo到Diablo
II之间的衍变显示了我们能够创作出有趣、为玩家熟悉、并且具有全新体验的游戏。”也许Roper的话是对的,因为接下来的事情再一次证实了暴雪的成功。

暴雪风云录
2000年6月份,经过两年多开发的《暗黑破坏神Ⅱ》终于发布了,发行当天高达184000份的销量足以显示玩家对于暴雪的疯狂,而不足一个月的时间就创下了百万份的销量更是创造了一个新的记录。

第二年的6月份,北美暴雪再次为大家带来了新的惊喜,《暗黑破坏神Ⅱ》资料片《毁灭之王》发布了,但是更大的惊喜却还在后面。9月,参加欧洲计算机贸易大展的暴雪宣布了《魔兽世界》的存在,一扇通向艾泽拉斯世界的大门正在向全世界的玩家缓缓开启。

暴雪风云录

时的《魔兽争霸Ⅲ》开发小组的工作也终于接近了尾声,三年磨一剑,Roper口中的“史诗级”概念在《魔兽争霸Ⅲ》的身上得到了淋漓尽致的体现。2002
年7月,人们第一次见到了“英雄”在RTS游戏中起到了如此重要的作用,3C、TD、熊猫,暴雪驾驭游戏的能力再一次得到了完美的体现。2002年底的时
候,《魔兽争霸Ⅲ》不负众望的登上了各大游戏排行榜的首位,暴雪重现了当年Westwood的辉煌。

资料片的发行一直的暴雪的传统,就像
星际争霸的母巢之战,就像暗黑破坏神的毁灭之王,每一次暴雪资料片的发行总是能够让人重现认识一款游戏并将自己对它的热情持之以恒。《魔兽争霸Ⅲ》也不例
外,就在游戏发布后不久,暴雪决定继续讲述艾泽拉斯的故事,一款新的资料片即将发布。

但是事情并非总是那么一帆风顺,暴雪的母公司威望迪
此时的一个决定震动了暴雪上下和整个游戏界。为了偿还债务,威望迪有可能将自己名下的游戏制作部门进行出售,当然这也包括暴雪。面对这一景况,首先出现波
动的是北美暴雪,这个开发了《暗黑破坏神》系列的团队。最终,曾经引领着Condor投入暴雪怀抱的Erich Schaefer、Max
Schaefer、David Brevik重新离开了暴雪,随着他们一同出走的,还有暴雪的副总裁,一个游戏制作的全才Bill Roper。

但是此时的暴雪并没有轰然倒地,早已成长为能够同时进行两款伟大游戏开发的暴雪在外界看来似乎并没有收到什么影响。就在北美暴雪高层集体辞职之后的第二天,2003年7月1日,《冰峰王座》准时来到了玩家的面前,暴雪仍然在继续着自己的创新与辉煌。

新的开始]

暴雪风云录
我终于站在了暴风城的大门前。


多年来,人们每每俯瞰着美丽的艾泽拉斯世界,却从来不得以进入其中,今天,暴雪将这一切都变成了显示。但我睁开双眼第一次看到北郡修道院的时候,一切都使
我感觉到那么的熟悉,就是在这里,人类的大法师麦迪文度过了自己的童年和少年时光,但是就在德拉诺的兽人在萨格拉斯和他的部下基尔加丹的影响下变得狂暴而
残忍的时候,麦迪文也完全被萨格拉斯掌控了,一条连接德拉诺和艾泽拉斯的时空之门被打通,狂暴的兽人很快席卷了整个艾泽拉斯的大陆。

历尽
千辛,人类终于在兽人的残暴下存活了,而得到了神器的兽人也暂时回到了德拉诺的世界中,但是下一次的入侵似乎将很快就会来到。为了让艾泽拉斯的世界获得永
久的和平,图拉扬将军,大法师卡德加以及来自高等精灵的艾蕾莉亚•风行者、来自矮人的库德兰•蛮锤和勇猛的战士纳斯•托尔贝恩率领着人类的远征军开向了地
狱般的德拉诺兽人世界。浴血奋战,战斗在德拉诺的联盟勇士们最终明白,只有摧毁通向艾泽拉斯的传送门才是确保联盟和平的唯一办法。一声巨响过后,联盟的战
士们永远的留在了德拉诺的红色世界中,直至今日,依然没有他们的任何消息。也许他们仍然在继续着同兽人的激战,也许他们仍在在寻找着回家的道路,也许他们
已经在天国的阶梯上俯瞰着美丽的艾泽拉斯世界。

我凝望着伫立在暴风城门口的雕像,回想着在这个美丽的世界中曾经所发生的点点滴滴,遏制不住的激情开始在胸中回荡,心中禁不住想起了库德兰•蛮锤雕像座下的那段话:

“听吧,风中传来狮鹫的呼号,战锤的声音回荡在山间。勇敢的面对困境吧,我的兄弟,祖先的圣堂在向你召唤。”

也许,这就是积淀了十年的激情!

也许,这就是暴雪的力量!

2005年07月21日

Servlet 2.3过滤器编程

做了很长时间的servlet程序,也没有接触过filter,借着这个做用户验证的机会还可以研究一下。

bill 发表于2005-03-17 作者:l_walker 评价:0/0 评论数:0 点击数:914 [收藏]

摘要:



转载:转载请保留本信息,本文来自http://www.matrix.org.cn/resource/article/1266.html
摘要

Jason Hunter通过对一些自由而又实用的过滤器的研究以对新的servlet过滤器模型进行深入探讨。你将知道这些过滤器是如何工作以及你能用他们做什么。最后,Jason介绍了他自己为简化文件上传而做的多路请求过滤器。





在"Servlet 2.3: New Features Exposed,"中,我介绍了Servlet API 2.3中的变化并给出了一个简单的servlet过滤器模型。在随后的文章中,我将对servlet过滤器进行深入的挖掘,而你看到的这些servlet过滤器都是能从Web上免费下载的。对每一个过滤器,我将检视它是做什么的,如何工作的,以及你能从哪里得到它。



你可以在两种情况下使用本文:学习过滤器的功用,或者作为你写过滤器时的辅助。我将从几个简单的例子开始然后继续更多高级的过滤器。最后,我将向你介绍我为了支持多路请求而写的一个文件上传过滤器。



Servlet 过滤器

也许你还不熟悉情况,一个过滤器是一个可以传送请求或修改响应的对象。过滤器并不是servlet,他们并不实际创建一个请求。他们是请求到达一个servlet前的预处理程序,和/或响应离开servlet后的后处理程序。就像你将在后面的例子中看到的,一个过滤器能够:

·在一个servlet被调用前截获该调用

·在一个servlet被调用前检查请求

·修改在实际请求中提供了可定制请求对象的请求头和请求数据

·修改在实际响应中提供了可定制响应对象的响应头和响应数据

·在一个servlet被调用之后截获该调用



你可以一个过滤器以作用于一个或一组servlet,零个或多个过滤器能过滤一个或多个servlet。一个过滤器实现java.servlet.Filter接口并定义它的三个方法:

1. void init(FilterConfig config) throws ServletException:在过滤器执行service前被调用,以设置过滤器的配置对象。

2. void destroy();在过滤器执行service后被调用。

3. Void doFilter(ServletRequest req,ServletResponse res,FilterChain chain) throws IOException,ServletException;执行实际的过滤工作。



服务器调用一次init(FilterConfig)以为服务准备过滤器,然后在请求需要使用过滤器的任何时候调用doFilter()。FilterConfig接口检索过滤器名、初始化参数以及活动的servlet上下文。服务器调用destory()以指出过滤器已结束服务。过滤器的生命周期和servelt的生命周期非常相似 ——在Servlet API 2.3 最终发布稿2号 中最近改变的。先前得用setFilterConfig(FilterConfig)方法来设置生命周期。



在doFilter()方法中,每个过滤器都接受当前的请求和响应,而FilterChain包含的过滤器则仍然必须被处理。doFilter()方法中,过滤器可以对请求和响应做它想做的一切。(就如我将在后面讨论的那样,通过调用他们的方法收集数据,或者给对象添加新的行为。)过滤器调用

chain.doFilter()将控制权传送给下一个过滤器。当这个调用返回后,过滤器可以在它的doFilter()方法的最后对响应做些其他的工作;例如,它能记录响应的信息。如果过滤器想要终止请求的处理或或得对响应的完全控制,则他可以不调用下一个过滤器。



循序渐进

如果想要真正理解过滤器,则应该看它们在实际中的应用。我们将看到的第一个过滤器是简单而有用的,它记录了所有请求的持续时间。在Tomcat 4.0发布中被命名为ExampleFilter。代码如下:

import java.io.*;

import javax.servlet.*;

import javax.servlet.http.*;



public class TimerFilter implements Filter {



  private FilterConfig config = null;



  public void init(FilterConfig config) throws ServletException {

    this.config = config;

  }



  public void destroy() {

    config = null;

  }



  public void doFilter(ServletRequest request, ServletResponse response,

                     FilterChain chain) throws IOException, ServletException {

    long before = System.currentTimeMillis();

    chain.doFilter(request, response);

    long after = System.currentTimeMillis();



    String name = "";

    if (request instanceof HttpServletRequest) {

      name = ((HttpServletRequest)request).getRequestURI();

    }

    config.getServletContext().log(name + ": " + (after - before) + "ms");

  }

}



当服务器调用init()时,过滤器用config变量来保存配置类的引用,这将在后面的doFilter()方法中被使用以更改ServletContext。当调用doFilter()时,过滤器计算请求发生到该请求执行完毕之间的时间。该过滤器很好的演示了请求之前和之后的处理。注意doFilter()方法的参数并不是HTTP对象,因此要调用HTTP专用的getRequestURI()方法时必须将request转化为HttpServletRequest类型。



使用此过滤器,你还必须在web.xml文件中用<filter>标签部署它,见下:

 <filter>

        <filter-name>timerFilter</filter-name>

        <filter-class>TimerFilter</filter-class>

</filter>




这将通知服务器一个叫timerFiter的过滤器是从TimerFiter类实现的。你可以使用确定的URL模式或使用<filter-mapping>标签命名的servelt 来注册一个过滤器,如:

<filter-mapping>

    <filter-name>timerFilter</filter-name>

    <url-pattern>/*</url-pattern>

</filter-mapping>




这种配置使过滤器操作所有对服务器的请求(静态或动态),正是我们需要的计时过滤器。如果你连接一个简单的页面,记录输出可能如下:

2001-05-25 00:14:11 /timer/index.html: 10ms



在Tomcat 4.0 beta 5中,你可以在server_root/logs/下找到该记录文件。



此过滤器的WAR文件从此下载:

http://www.javaworld.com/jw-06-2001/Filters/timer.war



谁在你的网站上?他们在做什么?

我们下一个过滤器是由OpenSymphony成员写的clickstream过滤器。这个过滤器跟踪用户请求(比如:点击)和请求队列(比如:点击流)以向网络管理员显示谁在她的网站上以及每个用户正在访问那个页面。这是个使用LGPL的开源库。



在clickstream包中你将发现一个捕获请求信息的ClickstreamFilter类,一个像操作结构一样的Clickstream类以保存数据,以及一个保存会话和上下文事件的ClickstreamLogger类以将所有东西组合在一起。还有个BotChecker类用来确定客户端是否是一个机器人(简单的逻辑,像“他们是否是从robots.txt来的请求?”)。该包中提供了一个clickstreams.jsp摘要页面和一个viewstream.jsp详细页面来查看数据。



我们先看ClickstreamFilter类。所有的这些例子都做了些轻微的修改以格式化并修改了些可移植性问题,这我将在后面将到。


import java.io.IOException;

import javax.servlet.*;

import javax.servlet.http.*;



public class ClickstreamFilter implements Filter {

  protected FilterConfig filterConfig;

  private final static String FILTER_APPLIED = "_clickstream_filter_applied";



  public void init(FilterConfig config) throws ServletException {

    this.filterConfig = filterConfig;

  }



  public void doFilter(ServletRequest request, ServletResponse response,

                   FilterChain chain) throws IOException, ServletException {

    // 确保该过滤器在每次请求中只被使用一次

    if (request.getAttribute(FILTER_APPLIED) == null) {

      request.setAttribute(FILTER_APPLIED, Boolean.TRUE);

      HttpSession session = ((HttpServletRequest)request).getSession();

      Clickstream stream = (Clickstream)session.getAttribute("clickstream");

      stream.addRequest(((HttpServletRequest)request));

    }



    // 传递请求

    chain.doFilter(request, response);

  }



  public void destroy() { }

}




doFilter()方法取得用户的session,从中获取Clickstream,并将当前请求数据加到Clickstream中。其中使用了一个特殊的FILTER_APPLIED标记属性来标注此过滤器是否已经被当前请求使用(可能会在请求调度中发生)并且忽略所有其他的过滤器行为。你可能疑惑过滤器是怎么知道当前session中有clickstream属性。那是因为ClickstreamLogger在会话一开始时就已经设置了它。ClickstreamLogger代码:

import java.util.*;

import javax.servlet.*;

import javax.servlet.http.*;



public class ClickstreamLogger implements ServletContextListener,

                                          HttpSessionListener {

  Map clickstreams = new HashMap();



  public ClickstreamLogger() { }



  public void contextInitialized(ServletContextEvent sce) {

    sce.getServletContext().setAttribute("clickstreams", clickstreams);

  }



  public void contextDestroyed(ServletContextEvent sce) {

    sce.getServletContext().setAttribute("clickstreams", null);

  }



  public void sessionCreated(HttpSessionEvent hse) {

    HttpSession session = hse.getSession();

    Clickstream clickstream = new Clickstream();

    session.setAttribute("clickstream", clickstream);

    clickstreams.put(session.getId(), clickstream);

  }



  public void sessionDestroyed(HttpSessionEvent hse) {

    HttpSession session = hse.getSession();

    Clickstream stream = (Clickstream)session.getAttribute("clickstream");

    clickstreams.remove(session.getId());

  }

}


logger(记录器)获取应用事件并将使用他们将所有东西帮定在一起。当context创建中,logger在context中放置了一个共享的流map。这使得clickstream.jsp页面知道当前活动的是哪个流。而在context销毁中,logger则移除此map。当一个新访问者创建一个新的会话时,logger将一个新的Clickstream实例放入此会话中并将此Clickstream加入到中心流map中。在会话销毁时,由logger从中心map中移除这个流。



下面的web.xml部署描述片段将所有东西写在一块:

 <filter>

         <filter-name>clickstreamFilter</filter-name>

         <filter-class>ClickstreamFilter</filter-class>

    </filter>





 <filter-mapping>

         <filter-name>clickstreamFilter</filter-name>

         <url-pattern>*.jsp</url-pattern>

    </filter-mapping>



    <filter-mapping>

         <filter-name>clickstreamFilter</filter-name>

         <url-pattern>*.html</url-pattern>

    </filter-mapping>



    <listener>

         <listener-class>ClickstreamLogger</listener-class>

    </listener>



这注册了ClickstreamFilter并设置其处理*.jsp和*.html来的请求。这也将ClickstreamLogger注册为一个监听器以在应用事件发生时接受他们。



两个JSP页面从会话中取clickstream数据和context对象并使用HTML界面来显示当前状态。下面的clickstream.jsp文件显示了个大概:

<%@ page import="java.util.*" %>

<%@ page import="Clickstream" %>

<%

Map clickstreams = (Map)application.getAttribute("clickstreams");

String showbots = "false";



if (request.getParameter("showbots") != null) {

  if (request.getParameter("showbots").equals("true"))

    showbots = "true";

  else if (request.getParameter("showbots").equals("both"))

    showbots = "both";

}

%>



<font face="Verdana" size="-1">

<h1>All Clickstreams</h1>



<a href="clickstreams.jsp?showbots=false">No Bots</a> |

<a href="clickstreams.jsp?showbots=true">All Bots</a> |

<a href="clickstreams.jsp?showbots=both">Both</a> <p>



<% if (clickstreams.keySet().size() == 0) { %>

        No clickstreams in progress

<% } %>



<%

Iterator it = clickstreams.keySet().iterator();

int count = 0;

while (it.hasNext()) {

  String key = (String)it.next();

  Clickstream stream = (Clickstream)clickstreams.get(key);



  if (showbots.equals("false") && stream.isBot()) {

    continue;

  }

  else if (showbots.equals("true") && !stream.isBot()) {

    continue;

  }

  count++;

  try {

%>



<%= count %>.

<a href="viewstream.jsp?sid=<%= key %>"><b>

<%= (stream.getHostname() != null && !stream.getHostname().equals("") ?

     stream.getHostname() : "Stream") %>

</b></a> <font size="-1"><%= stream.getStream().size() %> reqs</font><br>



<%

  }

  catch (Exception e) {

%>

  An error occurred - <%= e %><br>

<%

  }

}

%>



这个包很容易从OpenSymphony下载并安装。将Java文件编译并放在

WEB-INF/classes下,将JSP文件放到Web应用路径下,按帮助修改web.xml文件。为防止在这些工作前的争论,你可以从

http://www.javaworld.com/jw-06-2001/Filters/clickstream.war处找到打好包的WAR文件。



为能让此过滤器能在Tomcat 4.0 beta 5下工作,我发现我不得不做一些轻微的改动。我做的改动显示了一些在servlet和过滤器的可移植性中通常容易犯的错误,所以我将他们列在下面:

·我不得不将在JSP中添加一个额外的导入语句:<%@ page import=”Clickstream” %>。在Java中你并不需要导入在同一包下的类,而在服务器上JSP被编译到默认包中,你并不需要这句导入行。但在像Tomcat这样的服务器上,JSP被编译到一个自定义的包中,你不得不明确地从默认包中导入类。

·我不得不将<listener>元素移动到web.xml文件中的<filter>和<filter-mapping>元素之后,就像部署描述DTD要求的那样。并不是所有服务器对元素都要求固定的顺序。但Tomcat必须要。

·我不得不将web.xml中的映射由/*.html和/*.jsp改成正确的*.html和*.jsp。一些服务器会忽略开头的/,但Tomcat强硬的规定开头不能有/。

·最后,我得将ClickstreamFilter类升级到最新的生命周期API,将setFilterConfig()改成新的init()和destory()方法。



可下载的WAR文件已经包含了这些修改并能通过服务器在包外运行,虽然我并没有广泛的进行测试。



压缩响应

第三个过滤器是自动压缩响应输出流,以提高带宽利用率并提供一个很好的包装响应对象的示例。这个过滤器是由来自SUN的Amy Roh编写的,他为Tomcat 4.0 的“examples”Web程序做出过贡献。你将从webapps/examples/WEB-INF/classes/compressionFilters下找到原始代码。这里的例子代码以及WAR下的都已经为了更清晰和更简单而编辑过了。



CompressionFilter类的策略是检查请求头以判定客户端是否支持压缩,如果支持,则将响应对象用自定义的响应来打包,它的getOutputStream()和getWriter()方法已经被定义为可以利用压缩过的输出流。使用过滤器允许如此简单而有效的解决问题。



我们将从init()开始看代码:

public void init(FilterConfig filterConfig) {

    config = filterConfig;

    compressionThreshold = 0;

    if (filterConfig != null) {

      String str = filterConfig.getInitParameter("compressionThreshold");

      if (str != null) {

        compressionThreshold = Integer.parseInt(str);

      }

      else {

        compressionThreshold = 0;

      }

    }

  }



注意在检索请求头前必须把request转化为HttpServletRequest,就想在第一个例子里那样。过滤器使用wrapper类CompressResponseWrapper,一个从

HttpServletResponseWrapper类继承下来的自定义类。这个wrapper的代码相对比较简单:

public class CompressionResponseWrapper extends HttpServletResponseWrapper {



  protected ServletOutputStream stream = null;

  protected PrintWriter writer = null;

  protected int threshold = 0;

  protected HttpServletResponse origResponse = null;



  public CompressionResponseWrapper(HttpServletResponse response) {

    super(response);

    origResponse = response;

  }



  public void setCompressionThreshold(int threshold) {

    this.threshold = threshold;

  }



  public ServletOutputStream createOutputStream() throws IOException {

    return (new CompressionResponseStream(origResponse));

  }



  public ServletOutputStream getOutputStream() throws IOException {

    if (writer != null) {

      throw new IllegalStateException("getWriter() has already been " +

                                      "called for this response");

    }



    if (stream == null) {

      stream = createOutputStream();

    }

    ((CompressionResponseStream) stream).setCommit(true);

    ((CompressionResponseStream) stream).setBuffer(threshold);

    return stream;

  }



  public PrintWriter getWriter() throws IOException {

    if (writer != null) {

      return writer;

    }



    if (stream != null) {

      throw new IllegalStateException("getOutputStream() has already " +

                                      "been called for this response");

    }



    stream = createOutputStream();

    ((CompressionResponseStream) stream).setCommit(true);

    ((CompressionResponseStream) stream).setBuffer(threshold);

    writer = new PrintWriter(stream);

    return writer;

  }

}




所有调用getOutputStream() 或者getWriter()都返回一个使用

CompressResponseStream类的对象。CompressionResponseStrteam类没有显示在这个例子中,因为它继承于ServletOutputStream并使用java.util.zip.GZIPOutputStream类来压缩流。



Tomcat的”examples”Web程序中已经预先配置了这个压缩过滤器并加载了一个示例servlet。示例servlet响应/CompressionTestURL(确定先前的路径是/examples)。使用我制作的有用的WAR文件,你可以用/servlet/compressionTest(再次提醒,别忘了适当的前导路径)访问此测试servlet。你可以使用如下的web.xml片段来配置这个测试:


<filter>

    <filter-name>compressionFilter</filter-name>

    <filter-class>CompressionFilter</filter-class>

    <init-param>

      <param-name>compressionThreshold</param-name>

      <param-value>10</param-value>

    </init-param>

</filter>



<filter-mapping>

    <filter-name>compressionFilter</filter-name>

    <servlet-name>compressionTest</servlet-name>

</filter-mapping>



<servlet>

  <servlet-name>

    compressionTest

  </servlet-name>

  <servlet-class>

    CompressionTestServlet

  </servlet-class>

</servlet>



CompressionTestServlet(这里没有显示)输出压缩是否可用,如果可用,则输出压缩响应成功!


转发(forward)、包含(include)及转向(redirect)的区别与联系 
[05-4-25 | 中文java技术网 ]

   


记得曾经发过,不过今天又有些忘记,再找再发一遍。 


forward,include,redirect是jsp中web页面(jsp或servelt)的三种不同的路由关系,三个貌似相同,其实还是有许多差别的,而了解这些细微的差别,在web开发中是很重要的。
说明:
一、forward、include由javax.servlet.RequestDispatcher来封装,由包容器container提供RequestDispatcher接口的实现,其中声明如下:
void forward(ServletRequest req,ServletResponse res)
void include(ServeltRequest req,ServletResponse res)
可以通过两种方式得到RequestDispatcher:
1、ServletContext.getRequestDispatcher(String path);
其中这里的path必须开始为"/",即这里的path必须相对于context的root.
2、ServeltRequest.getRequestDispatcher(String path)
这里的path可以为相对路径,如果path开始为"/",则也认为是从context的root开始的。
二、Redirect由HttpServletResponse.sendRedirect(String location)来支持
差别:
三个都可以对用户的request进行转发,但是还是有许多的不同,差别最主要集中在如下几个方面:
1、forward与include共亨Request范围内的对象,而redirect则不行,即:如果一个javabean被声明为request范围的话,则被forward到的资源也可以访问这个javabean,而redriect则不行。
2、forward与include基本上都是转发到context内部的资源,而redirect可以重定向到外部的资源,如: req.sendRedriect("http://www.mocuai.com");

2005年07月13日

Java常见面试题(含答案)

第一,谈谈final, finally, finalize的区别。
final?修饰符(关键字)如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承。因此一个类不能既被声明为 abstract的,又被声明为final的。将变量或方法声明为final,可以保证它们在使用中不被改变。被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。被声明为final的方法也同样只能使用,不能重载
finally?再异常处理时提供 finally 块来执行任何清除操作。如果抛出一个异常,那么相匹配的 catch 子句就会执行,然后控制就会进入 finally 块(如果有的话)。
finalize?方法名。Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在 Object 类中定义的,因此所有的类都继承了它。子类覆盖 finalize() 方法以整理系统资源或者执行其他清理工作。finalize() 方法是在垃圾收集器删除对象之前对这个对象调用的。

第二,Anonymous Inner Class (匿名内部类) 是否可以extends(继承)其它类,是否可以implements(实现)interface(接口)?
匿名的内部类是没有名字的内部类。不能extends(继承) 其它类,但一个内部类可以作为一个接口,由另一个内部类实现。

第三,Static Nested Class 和 Inner Class的不同,说得越多越好(面试题有的很笼统)。
Nested Class (一般是C++的说法),Inner Class (一般是JAVA的说法)。Java内部类与C++嵌套类最大的不同就在于是否有指向外部的引用上。具体可见http: //www.frontfree.net/articles/services/view.asp?id=704&page=1
注: 静态内部类(Inner Class)意味着1创建一个static内部类的对象,不需要一个外部类对象,2不能从一个static内部类的一个对象访问一个外部类对象

第四,&和&&的区别。
&是位运算符。&&是布尔逻辑运算符。

第五,HashMap和Hashtable的区别。
都属于Map接口的类,实现了将惟一键映射到特定的值上。
HashMap 类没有分类或者排序。它允许一个 null 键和多个 null 值。
Hashtable 类似于 HashMap,但是不允许 null 键和 null 值。它也比 HashMap 慢,因为它是同步的。

第六,Collection 和 Collections的区别。
Collections是个java.util下的类,它包含有各种有关集合操作的静态方法。
Collection是个java.util下的接口,它是各种集合结构的父接口。


第七,什么时候用assert。
断言是一个包含布尔表达式的语句,在执行这个语句时假定该表达式为 true。如果表达式计算为 false,那么系统会报告一个 AssertionError。它用于调试目的:
assert(a > 0); // throws an AssertionError if a <= 0
断言可以有两种形式:
assert Expression1 ;
assert Expression1 : Expression2 ;
Expression1 应该总是产生一个布尔值。
Expression2 可以是得出一个值的任意表达式。这个值用于生成显示更多调试信息的 String 消息。
断言在默认情况下是禁用的。要在编译时启用断言,需要使用 source 1.4 标记:
javac -source 1.4 Test.java
要在运行时启用断言,可使用 -enableassertions 或者 -ea 标记。
要在运行时选择禁用断言,可使用 -da 或者 -disableassertions 标记。
要系统类中启用断言,可使用 -esa 或者 -dsa 标记。还可以在包的基础上启用或者禁用断言。
可以在预计正常情况下不会到达的任何位置上放置断言。断言可以用于验证传递给私有方法的参数。不过,断言不应该用于验证传递给公有方法的参数,因为不管是否启用了断言,公有方法都必须检查其参数。不过,既可以在公有方法中,也可以在非公有方法中利用断言测试后置条件。另外,断言不应该以任何方式改变程序的状态。

第八,GC是什么? 为什么要有GC? (基础)。
GC是垃圾收集器。Java 程序员不用担心内存管理,因为垃圾收集器会自动进行管理。要请求垃圾收集,可以调用下面的方法之一:
System.gc()
Runtime.getRuntime().gc()

第九,String s = new String("xyz");创建了几个String Object?
两个对象,一个是“xyx”,一个是指向“xyx”的引用对象s。

第十,Math.round(11.5)等於多少? Math.round(-11.5)等於多少?
Math.round(11.5)返回(long)12,Math.round(-11.5)返回(long)-11;

第十一,short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1 += 1;有什么错?
short s1 = 1; s1 = s1 + 1;有错,s1是short型,s1+1是int型,不能显式转化为short型。可修改为s1 =(short)(s1 + 1) 。short s1 = 1; s1 += 1正确。

第十二,sleep() 和 wait() 有什么区别? 搞线程的最爱
sleep()方法是使线程停止一段时间的方法。在sleep 时间间隔期满后,线程不一定立即恢复执行。这是因为在那个时刻,其它线程可能正在运行而且没有被调度为放弃执行,除非(a)“醒来”的线程具有更高的优先级
(b)正在运行的线程因为其它原因而阻塞。
wait()是线程交互时,如果线程对一个同步对象x 发出一个wait()调用,该线程会暂停执行,被调对象进入等待状态,直到被唤醒或等待时间到。



第十三,Java有没有goto?
Goto?java中的保留字,现在没有在java中使用。

第十四,数组有没有length()这个方法? String有没有length()这个方法?
数组没有length()这个方法,有length的属性。
String有有length()这个方法。

第十五,Overload和Override的区别。Overloaded的方法是否可以改变返回值的类型?
方法的重写Overriding和重载Overloading是Java多态性的不同表现。重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被“屏蔽”了。如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。Overloaded的方法是可以改变返回值的类型。

第十六,Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是equals()? 它们有何区别?
Set里的元素是不能重复的,那么用iterator()方法来区分重复与否。equals()是判读两个Set是否相等。
equals()和==方法决定引用值是否指向同一对象equals()在类中被覆盖,为的是当两个分离的对象的内容和类型相配的话,返回真值。

第十七,给我一个你最常见到的runtime exception。
ArithmeticException, ArrayStoreException, BufferOverflowException, BufferUnderflowException, CannotRedoException, CannotUndoException, ClassCastException, CMMException, ConcurrentModificationException, DOMException, EmptyStackException, IllegalArgumentException, IllegalMonitorStateException, IllegalPathStateException, IllegalStateException,
ImagingOpException, IndexOutOfBoundsException, MissingResourceException, NegativeArraySizeException, NoSuchElementException, NullPointerException, ProfileDataException, ProviderException, RasterFormatException, SecurityException, SystemException, UndeclaredThrowableException, UnmodifiableSetException, UnsupportedOperationException

第十八,error和exception有什么区别?
error 表示恢复不是不可能但很困难的情况下的一种严重问题。比如说内存溢出。不可能指望程序能处理这样的情况。
exception 表示一种设计或实现问题。也就是说,它表示如果程序运行正常,从不会发生的情况。


第十九,List, Set, Map是否继承自Collection接口?
List,Set是

Map不是

第二十,abstract class和interface有什么区别?
声明方法的存在而不去实现它的类被叫做抽象类(abstract class),它用于要创建一个体现某些基本行为的类,并为该类声明方法,但不能在该类中实现该类的情况。不能创建abstract 类的实例。然而可以创建一个变量,其类型是一个抽象类,并让它指向具体子类的一个实例。不能有抽象构造函数或抽象静态方法。Abstract 类的子类为它们父类中的所有抽象方法提供实现,否则它们也是抽象类为。取而代之,在子类中实现该方法。知道其行为的其它类可以在类中实现这些方法。
接口(interface)是抽象类的变体。在接口中,所有方法都是抽象的。多继承性可通过实现这样的接口而获得。接口中的所有方法都是抽象的,没有一个有程序体。接口只可以定义static final成员变量。接口的实现与子类相似,除了该实现类不能从接口定义中继承行为。当类实现特殊接口时,它定义(即将程序体给予)所有这种接口的方法。然后,它可以在实现了该接口的类的任何对象上调用接口的方法。由于有抽象类,它允许使用接口名作为引用变量的类型。通常的动态联编将生效。引用可以转换到接口类型或从接口类型转换,instanceof 运算符可以用来决定某对象的类是否实现了接口。

第二十一,abstract的method是否可同时是static,是否可同时是native,是否可同时是synchronized?
都不能

第二十二,接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承实体类(concrete class)?
接口可以继承接口。抽象类可以实现(implements)接口,抽象类是否可继承实体类,但前提是实体类必须有明确的构造函数。

第二十三,启动一个线程是用run()还是start()?
启动一个线程是调用start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行。这并不意味着线程就会立即运行。run()方法可以产生必须退出的标志来停止一个线程。



第二十四,构造器Constructor是否可被override?
构造器Constructor不能被继承,因此不能重写Overriding,但可以被重载Overloading。

第二十五,是否可以继承String类?
String类是final类故不可以继承。
第二十六,当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?
不能,一个对象的一个synchronized方法只能由一个线程访问。

第二十七,try {}里有一个return语句,那么紧跟在这个try后的finally {}里的code会不会被执行,什么时候被执行,在return前还是后?
会执行,在return前执行。


第二十八,编程题: 用最有效率的方法算出2乘以8等於几?
有C背景的程序员特别喜欢问这种问题。

2 << 3

第二十九,两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?
不对,有相同的hash code。

第三十,当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?
是值传递。Java 编程语言只由值传递参数。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的内容可以在被调用的方法中改变,但对象的引用是永远不会改变的。


第三十一,swtich是否能作用在byte上,是否能作用在long上,是否能作用在String上?
switch(expr1)中,expr1是一个整数表达式。因此传递给 switch 和 case 语句的参数应该是 int、 short、 char 或者 byte。long,string 都不能作用于swtich。

第三十二,编程题: 写一个Singleton出来。
Singleton模式主要作用是保证在Java应用程序中,一个类Class只有一个实例存在。
一般Singleton模式通常有几种种形式:
第一种形式: 定义一个类,它的构造函数为private的,它有一个static的private的该类变量,在类初始化时实例话,通过一个public的getInstance方法获取对它的引用,继而调用其中的方法。
public class Singleton {
  private Singleton(){}
  //在自己内部定义自己一个实例,是不是很奇怪?
  //注意这是private 只供内部调用
  private static Singleton instance = new Singleton();
  //这里提供了一个供外部访问本class的静态方法,可以直接访问  
  public static Singleton getInstance() {
    return instance;   
   }
}
第二种形式:
public class Singleton {
  private static Singleton instance = null;
  public static synchronized Singleton getInstance() {
  //这个方法比上面有所改进,不用每次都进行生成对象,只是第一次     
  //使用时生成实例,提高了效率!
  if (instance==null)
    instance=new Singleton();
return instance;   }
}
其他形式:
定义一个类,它的构造函数为private的,所有方法为static的。
一般认为第一种形式要更加安全

2005年07月04日

放计数器的时间-2005-06-30



中国六大最忙和六大最懒城市

 

NO.1 香港

评语:"手停口就停"的危机感长盛不衰;每个人都有投资观念;艺人们永远是为了工作而不敢恋爱和休息;兼职和充电是工薪族的两翼;快餐型报纸总是销量最大。

电视时间:守着电视的师奶居多,亚洲电视和无线电视的650万观众是港人无开销的休闲方式,但人们更多时候忙得只好在路上听广播,于是这个城市的电台多达13个。

快餐店:中国城市的中西快餐店数香港种类最齐全,麦当劳的广告也全年无休地做。

步行速度:"动感之都"绝不是浪得虚名,交通绿灯时播送着马达的声音,每个路口总有一队人等着冲锋。

酒精度:除了兰桂坊,香港人的餐桌上几乎没有酒精度可言,只有麦芽度。

作息时间:朝九晚五,但"开OT"是香港人的口头禅,日均客流量230万人次的地铁每天从上午6时到午夜1时运行,夜店也是人潮汹涌。

上班时间逛街人气:行政效率居亚洲第二的城市很少人上班时间逛街,街上更多是内地游客。今年春节一周内,内地游客在港消费27亿。

跳槽频率:香港不跳槽的是公务员。市民随时因为行业景气、公司裁员、增薪挖角而跳槽,18至24岁的年轻人创业率超过30%。

身体运动量:运动量偏少,眼病腰病胃病多。

手机通话状态:何时何地都可谈工作。

婚恋状况:结婚人数大减,离婚高企,单亲家庭比十年前增加7成,趋势是女孩要同居、男孩想结婚;单身派对如"G0-Con"流行。



NO.2 苏州

评语:吸引外资的全国第一是对打工者致命的吸引,它的世界工厂地位正在撼动珠三角的东莞和深圳,亦是旅游目的地;工作是这个城市发展的理由,计件工资是大多数人的发展后盾。

电视时间:多;电视在这里是大多数工薪族的精神食粮之一。

快餐店:多,但多不过工厂的食堂。

步行速度:有人说苏州好像架在了车轮子上,其实有工业苏州与古典苏州的区别,前者匆匆,后者优雅。

酒精度:偏低,小酌型;十全街的酒味不敌水天堂的茶味。

作息时间:保守型的时间表,按时作息的典范,休息是为了更好地工作。

上班时间逛街人气:旺,集中在观前街和人民路,大多是游客(多过香港),本地人不愿当一六八(苏州下岗工人的代称,其救济金是每月168块,后来有增加)。

跳槽频率:一般,因为是外向型经济,企业普遍产销两旺。

身体运动量:尚可;有人花钱买健康上健身中心,大多数人的运动只是活络筋骨,把工作当运动。

手机通话状态:忙,是一种娱乐休闲方式和社交型消费。

婚恋状况:婚介业和婚纱摄影业发达,结婚忙,先成家后立业和先立业后成家的观念并存。

评语:吸引外资的全国第一是对打工者致命的吸引,它的世界工厂地位正在撼动珠三角的东莞和深圳,亦是旅游目的地;工作是这个城市发展的理由,计件工资是大多数人的发展后盾。


  NO.3 深圳

评语:处于创业的平台期和消费的高潮期;工作的忙碌来源于城市发展的惯性势能和高消费的要求;休闲成为工作的减压阀;相当多厌于按时上班的人群选择开自己的小型公司、店铺或工作室。

电视时间:不多,因为夜生活丰富;而爱看电视者,一则香港频道构成吸引,二则想学粤语,三则想忘记现实压力。

快餐店:多,外卖"侵略"了几乎所有的写字楼。发展到网上建立"深圳快餐网",并有快餐店着手进行英国权威机构BSI的ISO9002和国内卫生和环保权威机构的环保ISO14000认证,要争深圳快餐配送的第一品牌。

步行速度:一向以快为傲,但现在算慢了,因为太过拥挤了,市民在街上少不了无法忍受走路太慢的人堵在前面的"人行道之怒"。

酒精度:高;这里是北方和南方血统的混合体,北方人能喝,南方人敢喝。

作息时间:类似香港的朝九晚五,但夜生活要占用大把睡眠时间,事实上这个城市缺睡。

上班时间逛街人气:旺,因为本城工作形态太多。

跳槽频率:高,因为工作机会多。

身体运动量:私家车狂增,个人运动量偏少,亚健康状态流行。

手机通话状态:煲电话粥的现象普遍;手机成为情感寄托的通道;工作必备。

婚恋状况:女多男少,男懒娶女恨(粤语,"渴望"的意思)嫁,情人多过夫妻。

NO.4 台北


评语:省不掉的大段通勤时间属于这个城市的乡愁;经济增长的高潮期过后,就业出现不同年龄层的世代之争,工薪阶层有强烈的高压感,充电意识强;一部分人致力于把商业搞得很文化;旅游和玩出新意成为逃离工作的出口。

电视时间:多;台北人热爱电视,也许是因为节目够娱乐够八卦。以张小燕、张菲、胡瓜、吴宗宪为代表的电视综艺界"三王一后"在市民中影响很大。

快餐店:多,但慢餐店(正常的点菜)和夜市也兴盛。

步行速度:总体快节奏,捷运自动剪票机在分析步行速度、人与人的间隔之后计算出在尖峰时段平均每一分钟有60-80人经过剪票口;也分地段,有的地段边走边看风景。

酒精度:高;这个城市玩"劈酒"的,在夜场不醉无归。

作息时间:一半人朝九晚五,一半人晚九朝五。

上班时间逛街人气:旺;台北人总是热衷于"走透透"。

跳槽频率:高;每一个时期的励志和职场书籍都可能造就一拨"跳蚤"。

身体运动量:少;因为工作和玩乐常把身体弄成透支状态。

手机通话状态:24小时;手机等于社交。

婚恋状况:重恋爱轻结婚,同居成风,追求浪漫与新鲜感。


NO.5 广州


评语:小变中变大变为广州带来更多工作机会,房地产发达而且楼价让人踮起脚来够得着;你感到自由,没有人关心你的隐私,每个人的生活都很忙碌;你感到不自由,为上班和塞车而烦恼,想做老板和自由职业者。

电视时间:少;因为很多人要加班,另外广州的平面媒体实在是发达并且抵买,每天都有看报纸。

快餐店:多;基本上是为两个极端的社区配套服务:一是浩大的城中村人群;二是白领办公社区。

步行速度:慢,因为没有良好的步行空间,讲效率的人都打的或坐地铁了。

酒精度:偏低;不像北方,这里没人劝你一定要喝的。

作息时间:保守的时间表,但还算多元化,事实上睡得晚起得也晚。

上班时间逛街人气:旺;在几个购物中心和步行街,大部分闲人是学生、SOHO、没工作或找工作的人。

跳槽频率:高;因为总有新公司要招人,总有薪水更胜一筹的工作机会。

身体运动量:偏少;健身方式经常以食补代替。这是一个有时间煲汤喝汤、没时间上健身房运动锻炼身体的城市,珠江边的晨练是老年人的天下。

手机通话状态:多用于工作和预约见面。

婚恋状况:维持现状的人比结婚和离婚的人都多,贪玩,不重形式重质量。


NO.6 上海


评语:国际性都会的口号刺激着上海人的肾上腺,全国乃至全球都有工作族在上海淘金;上海人工作第一消费第二,在积极投资与奢侈品消费方面他们同样精明。但过高房价易使它们见财化水。

电视时间:少,少于上网和打电话,多于看报纸和看热闹。

快餐店:多;这与城中便利店的增长与覆盖成正比。而便利店的覆盖已达到三步一亭、五步一岗的地步了。

步行速度:快;几乎没有闲人,只有游人和路过的人。

酒精度:偏低;这个城市追求的是优雅而非沉醉。而最旺的夜场是新天地,那里的酒总是洋味多于酒味。

作息时间:保守的时间表,以工作为重。

上班时间逛街人气:旺;浦东除外,那儿基本是城市的工作间。而南京路上的人群恐怕只有一场暴雨或非典才能驱赶。

跳槽频率:高;上海人乐于充电和求发展,这山望得那山高。

身体运动量:少;事实上,上海从来不是竞技运动的大市,比东北差很远。

手机通话状态:多用于工作和预约见面,且经常以座机和伊妹儿代替。

婚恋状况:有门户观念地嫁,有条件地娶。上海人在婚姻观念上的保守和崇洋是很突出的,至今仍在乎上海户口、出身;而上海男人四点钟去菜场买菜则构成经典好男人形象。

NO.1 丽江



评语:对逃离都市的人而言,这里是田园;对迷恋夜生活的人而言,这里也是都市;丽江与工作无关,与心情有关。
电视时间:零或无穷——在这里,谁还需要电视呢?要么开着,不看。

快餐店:无。

步行速度:用走字都夸张了,应该叫遛达。

酒精度:66度以下,各种度数、各种酒都喝。

作息时间:晚九朝五。

云南丽江纳西古乐

上班时间逛街人气:一半人专门负责上班,另一半人专门负责逛街。

跳槽频率:零。本地人全是不跳槽的,外地人根本就不工作。

身体运动量:如果伸懒腰和划拳也算的话。

手机通话状态:总是在大谈特谈理想、心情、抉择和享受。

婚恋状况:艳遇多过失恋。即使你什么人都没爱上,这里的时光也是柔软的。

NO.2 拉萨  


评语:想在西藏寻找人生转机和变局的人都来到了这里;它的藏族风情与宗教气氛令人觉得工作是一种俗务;从拉萨回去后许多人辞职了。

电视时间:近似零——人们在八角街和转经轮前吸氧。

快餐店:无(路边的酥油茶排档不算)。

步行速度:随便走,多数人没有急切的动机。

酒精度:偏高;这里的酒风也不是盖的,且饮酒驱寒。

作息时间:不定,但极端个人主义。

上班时间逛街人气:没有上班时间,出门不是爬山就是逛街。

跳槽频率:当时是零,离去后是90%。

身体运动量:少;因为缺氧。

手机通话状态:可有可无;一则信号不太好,二则另有世面可见。

婚恋状况:游人中偶尔有一两对驴友成恋人。

NO.3 成都


评语:以生活魅力荣登中国第四城,号称是"一座来了就不想离开的城市";有娱乐精神,消费欲强过创业欲;边吃边喝边工作,或者吃吃喝喝就是工作。

四川成都青城山

电视时间:少;因为打麻将和泡茶馆时间多。

快餐店:少(无所不在的排档和麻辣烫不算)。

步行速度:慢;所以买车也只买小小型的车就够

酒精度:高;微醺在此是一种生活美好的状态。

作息时间:保守的时间表,但经常不准时。

上班时间逛街人气:旺;感觉是全城在轮班逛街。

跳槽频率:低;但有不少人跳槽离开这里到更忙碌的城市去。

身体运动量:少;打麻将不应该算吧?

手机通话状态:更像一种时尚。

婚恋状况:擅于调情,享受恋爱,不忘婚姻。

NO.4 厦门


评语:悠闲生活节奏并不妨碍厦门成为中国城市竞争力强劲的城市;厦门有天时地利和充分的开放度与外向度;当然,更有大海和生活。

电视时间:多;厦门人有本土观念。与此相应的是年轻人不太愿到外乡就业。

快餐店:少(海鲜排档不算)。

步行速度:慢,这里有全国数一数二的漫步环境。

酒精度:不高;厦门酒风温和,规矩是"敬酒的干杯,被敬的随意",本地把酒当药,俗语"小酒小人参"。有时也行酒令划拳,但基本不滥饮滥醉。

作息时间:按部就班。

上班时间逛街人气:偏少;因为人口本不多。节奏松弛如鼓浪屿若隐若现的钢琴声。

跳槽频率:一般;因为工作竞胜的野心较淡。

身体运动量:正常;因为有余暇。

手机通话状态:工作与闲聊。

婚恋状况:男大当婚、女大当嫁型,不过激,不眼高手低。


NO.5 哈尔滨


评语:哈尔滨人露财,爱穿爱喝爱面子爱交朋友,与工作比起来他们更会享受;总想挣大钱不屑挣小钱;此次中央"振兴东北"国策应该能造就更多的工作机会,并提升工作热情。

电视时间:多;生活在室内的时间相对较多。

快餐店:少(这不是东北菜的风格)。

步行速度:慢;女人要展示男人要养眼。

酒精度:高;不喝会被人当狗熊踢出去,喝了会被人当烂泥拖出去。据说,哈尔滨人一年光喝啤酒扔下的"易拉罐"就可以造三座松花江大桥了。

作息时间:休息比工作多;全国21个城市的居民生活节奏调查显示,与上海的生活节奏比,哈尔滨人每天慢了1.8小时。

上班时间逛街人气:一般;与气候有关。

跳槽频率:低;因为好工作机会不多。

身体运动量:尚佳;至少夏天有街头露天舞会、周末有太阳岛野游、冬天有滑雪溜冰。

手机通话状态:闲聊型。

婚恋状况:恋爱高消费,结婚高消费,"素婚"少。


NO.6 北京


评语:与北京占有的资源和天时地利人和相比,北京人的表现算是懒的;这个城市永远不缺侃爷,不缺民间思想家,不缺策划大师,不缺私家车和堵车,只缺更多的行动和更高的效率。

电视时间:多;北京人关心国家大事和小事。

快餐店:多(中国第一家麦当劳餐厅就是从北京开出来的)。

步行速度:慢;一种原因是因为北京人口的平均年龄比深圳大了近10岁。

酒精度:高;以二锅头为标志。

作息时间:保守的时间表,经常因塞车受影响。

上班时间逛街人气:多(想想北京人口数量仅次于上海)。

跳槽频率:高;北京太多好企业了,白领阶层庞大。

身体运动量:一般;挤车不算吧?

手机通话状态:忙音,忙音,都在谈事呢。

婚恋状况:结婚多,离婚也多,部分处于"《手机》电影综合症"状态。

2005年07月01日

从一个获取时间的程序看我的基本功
/**

*
此处插入类型说明:获得系统时间及JAVA运行时环境参数

*
后来加入日期测试代码

*
创建日期: (2001-8-1 11:12:20)

*
@作者: adi

*/

import
java.sql.*;

import
java.util.*;

import
java.util.Date;

public
class TestClass {

public
static void main(String[] args) {

//以下为Clendar测试代码

int
yyyy,mm,dd;

Calendar
c=Calendar.getInstance();

yyyy=c.get(Calendar.YEAR);

mm=c.get(Calendar.MONTH)
+1;

dd=c.get(Calendar.DAY_OF_MONTH);

System.out.println(yyyy);

System.out.println(mm);

System.out.println(dd);

//以下为获取系统时间的代码

Timestamp
ts=new Timestamp(System.currentTimeMillis());

String
dateStr1=ts.toString();

System.out.println(dateStr1);

//以下为获得系统时间的另外一种方法

Date
date=new Date();

String
dateStr2=new Timestamp(date.getTime()).toString();

System.out.println(dateStr2);

//得到JAVA运行环境的一些基本属性

System.getProperties().list(System.out);

}

}

有时候就象自己用java也有一年多了,也做过实际的项目了,对java应该很熟了,但是今天当我要取得系统时间的时候我却突然发现自己竟然不知道要用哪个方法。说起来其实只是知道一个大概,对技术细节的掌握还是远远不够的。

由此想到两种学习工作的方式,一种是解决问题的能力特别强,不一定知道特别多的知识,但是遇到技术问题的时候能够很快找到解决的方法;另一种则是知识极其
渊博,厚积薄发,我应该在头一种人中算是不好的,在第二种人中呢,应该是属于第二种人,但是又没有足够的时间和经历来积累这些知识,于是只能混到今天每次都要请教google这个地步啦,真是没出息!