posts - 51, comments - 17, trackbacks - 0, articles - 9
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

2007年6月27日

引子 Web 2.0,在过去的一年里也许还是一个新的名词,曾几何时它像网上核武一样爆发了,并以不可阻挡之势燃烧了整个互联网,其热度不压于当年的超女,又曾几何时它悄悄地走进了我们的生活,从陌生走向了熟悉,从概念走向了应用。今天,Web 2.0构成了我们网络生活不可缺少的一部分,今天,你Web 2.0了吗?

什么是Web2.0?
如果你是一只老网虫但还不知道什么是Web2.0,赶紧去跳海吧,不怕死的就去跳(记得带个救生圈,出事了我不负责哈!),怕死的还不赶紧补补课,免得被人看穿了让人笑话。如果你是个善于表现自己的人,多学点东西吧,学了Web 2.0你可以不分对象地大谈特谈地讲述你的Web 2.0观点, 嘿嘿,想象一下别人对你崇拜的表情吧。

Web2.0是以 Flickr、Craigslist、Linkedin、Tribes、Ryze、 Friendster、Del.icio.us、43Things.com等网站为代表,以Blog、TAG、SNS、RSS、wiki等应用为核心,依据六度分隔、xml、ajax等新理论和技术实现的互联网新一代模式。

 1. 什么是Wiki

  WIKI的来源

  WIKI概念的发明人是Ward Cunningham,该词来源于夏威夷语的“wee kee wee kee”,原本是“快点快点” (quick)的意思。

  Wiki--一种多人协作的写作工具。Wiki站点可以有多人(甚至任何访问者)维护,每个人都可以发表自己的意见,或者对共同的主题进行扩展或者探讨。

  Wiki指一种超文本系统。这种超文本系统支持面向社群的协作式写作,同时也包括一组支持这种写作的辅助工具。有人认为,Wiki系统属于一种人类知识网格系统,我们可以在Web的基础上对Wiki文本进行浏览、创建、更改,而且创建、更改、发布的代价远比HTML文本小;同时Wiki系统还支持面向社群的协作式写作,为协作式写作提供必要帮助;最后,Wiki的写作者自然构成了一个社群,Wiki系统为这个社群提供简单的交流工具。与其它超文本系统相比,Wiki有使用方便及开放的特点,所以Wiki系统可以帮助我们在一个社群内共享某领域的知识。

      WIKI可以做什么

  WIKI最适合做百科全书、知识库、整理某一个领域的知识等知识型站点,几个分在不同地区的人利用wiki协同工作共同写一本书等等。Wiki技术已经被较好的用在百科全书、手册/FAQ编写、专题知识库方面。

  Wiki的特点

  使用方便

  维护快捷:快速创建、存取、更改超文本页面(这也是为什幺叫作“wiki wiki”的原因)。

  格式简单:用简单的格式标记来取代 HTML 的复杂格式标记。(类似所见即所得的风格)

  链接方便:通过简单标记,直接以关键字名来建立链接(页面、外部连接、图像等)。

  命名平易:关键字名就是页面名称,并且被置于一个单层、平直的名空间中。

  有组织

  自组织的:同页面的内容一样,整个超文本的组织结构也是可以修改、演化的。

  可汇聚的:系统内多个内容重复的页面可以被汇聚于其中的某个,相应的链接结构也随之改变。

  可增长

  可增长:页面的链接目标可以尚未存在,通过点击链接,我们可以创建这些页面,从而使系统得到增长。

  修订历史:记录页面的修订历史,页面的各个版本都可以被获取。

  开放性

  开放的:社群的成员可以任意创建、修改、删除页面。

  可观察:系统内页面的变动可以被访问者观察到。

2.

  什么是RSS

  RSS是站点用来和其他站点之间共享内容的一种简易方式(也叫聚合内容)的技术。最初源自浏览器“新闻频道”的技术,现在通常被用于新闻和其他按顺序排列的网站,例如Blog。

  RSS可以干什么?

  1、订阅BLOG(BLOG上,你可以订阅你工作中所需的技术文章;也可以订阅与你有共同爱好的作者的日志,总之,BLOG上你对什么感兴趣你就可以订什么)

  2、订阅新闻(无论是奇闻怪事、明星消息、体坛风云,只要你想知道的,都可以订阅)

  如何使用RSS

  ·下载和安装一个RSS新闻阅读器

  ·从网站提供的聚合新闻目录列表中订阅您感兴趣的新闻栏目的内容

  ·订阅后您将会及时获得所订阅新闻频道的最新内容

  RSS的几个缩写来源

  1、Really Simple Syndication(真正简易的聚合)

  2、Rich Site Summary(丰富的站点摘要)

  3、RDF Site Summary(RDF站点摘要)

  RSS新闻特点

  对网民而言:对网站而言:

  1.没有广告或者图片来影响标题或者文章概要的阅读。

  2.RSS阅读器自动更新你定制的网站内容,保持新闻的及时性。

  3.用户可以加入多个定制的RSS提要,从多个来源搜集新闻整合到单个数据流中。 1.扩大了网站内容的传播面,也增加了网站访问量,因为访问者调阅的RSS文件和浏览的网页,都是从网站服务器上下载的。

  2.RSS文件的网址是固定不变的,网站可以随时改变其中的内容。RSS内容一旦更新,浏览者看到的内容也随即更新了


3.什么是Tag?

  Tag(标签)是一种更为灵活、有趣的分类方式,您可以为每篇日志、每个帖子或者每张图片等添加一个或多个Tag(标签),你可以看到网站上所有和您使用了相同Tag的内容,由此和他人产生更多的联系。Tag体现了群体的力量,使得内容之间的相关性和用户之间的交互性大大增强。

  比如,你在一篇日志上添加了“读书”和“Tag”两个标签,就能通过这两个tag看到和你有相同兴趣的其他日志。同样,如果你给自己的网络书签贴上不同标签,那么,在下一次去寻找时,会轻易找到自己想要的信息。

  那么,如果我贴了Tag,能产生什么效果呢?首先,信息将会条理化。其次,当你积累了一定数量的Tag之后,你会发现自己最关心的话题。GOOGLE的"我的搜索历史"功能就是采用了标签,你的每次搜索关键词都可以成为tag,之后,你会了解自己这一天在关心什么。

  当然,你也可以看到有哪些人和自己使用了一样的Tag(标签),进而找到和您志趣相投的人。

  2、Tag究竟有哪些不同?

  Tag不是关键词,因为,一个机器就没有办法提取一张照片的关键字,但人可以给它设定一个或多个Tag。而Tag真正不同的地方在于,你可以随意用任何词来标记一件事物,只要方便你找到它。因此,这一标志是活跃的、无序的、个人化、相当自我的一种标记方式。

  当我可以为我自己的言论作出自己想要的标志,而不是别人给予我的分类,那么,我将说些什么呢?我又会通过这种标志找到什么样的人什么样的文章、图片呢?Tag创造了一个新的无序但充满生机的网络联合体,通过这个联合,人们找到和自己最接近的内容。

  3、如何使用Tag?

  现在很多网站都使用了Tag模式,只要使用者自身打开了界限,随心所欲地给自己注释标签,不被旧有思维局限住,就对了。简单地说,Tag是一种随心所欲的标签,当我读一篇文章或者看一张图片的时候想什么就写什么,不受原有分类的束缚,怎么想就怎么使用。

posted @ 2007-08-07 20:46 chenweicai 阅读(275) | 评论 (0)编辑 收藏

Abraham Lincoln

Delivered on the 19th Day of November, 1863

Cemetery Hill, Gettysburg, Pennsylvania

Fourscore and seven years ago, our fathers brought forth upon this continent a new Nation, conceived in Liberty, and dedicated to the proposition that all men are created equal. Now, we are engaged in a great Civil War,testing whether that Nation, or any nation so conceived and so dedicated, can long endure. We are met on a great battlefield of that war. We have come to dedicate a portion of that field as a final resting-place for those who gave their lives that Nation might live. It is altogether fitting and proper that we should do this.

But, in a larger sense, we cannot dedicate, we cannot consecrate, we cannot hallow this ground. The brave men, living and dead, who struggled here, have consecrated it far above our power to add or detract. The world will little note nor long remember what we say here, but it can never forget what they did here. It is for us, the living, rather to be dedicated to the great task remaining before us; that from these honored dead, we take increased devotion to that cause for which they gave the last full measure of devotion; that this Nation, under GOD, shall have a new birth of freedom; and that government of the People by the People and for the People shall not perish from the earth.

葛底斯堡演说

亚伯拉罕·林肯,1963年11月19日

87年前,我们的先辈们在这个大陆上创立了一个新国家,它孕育于自由之中,奉行一切人生来平等的原则。现在我们正从事一场伟大的内战,以考验这个国家,或者任何一个孕育于自由和奉行上述原则的国家是否能够长久存在下去。我们在这场战争中的一个伟大战场上集会。烈士们为使这个国家能够生存下去而献出了自己的生命,我们来到这里,是要把这个战场的一部分奉献给他们作为最后安息之所。我们这样做是完全应该而且是非常恰当的。

但是,从更广泛的意义上来说,这块土地我们不能够奉献,不能够圣化,不能够神化。那些曾在这里战斗过的勇士们,活着的和去世的,已经把这块土地圣化了,这远不是我们微薄的力量所能增减的。我们今天在这里所说的话,全世界不大会注意,也不会长久地记住,但勇士们在这里所做过的事,全世界却永远不会忘记。毋宁说,倒是我们这些还活着的人,应该在这里把自己奉献于勇士们已经如此崇高地向前推进但尚未完成的事业。倒是我们应该在这里把自己奉献于仍然留在我们面前的伟大任务??我们要从这些光荣的死者身上汲取更多的献身精神,来完成他们已经完全彻底为之献身的事业;我们要在这里下定最大的决心,不让这些死者白白牺牲;我们要使国家在上帝福佑下得到自由的新生,要使这个民有、民治、民享的政府永世长存。

posted @ 2007-06-30 11:23 chenweicai 阅读(295) | 评论 (0)编辑 收藏

Inaugural Address of John F. Kennedy  肯尼迪总统就职演说
January 20, 1961

Vice President Johnson, Mr. Speaker, Mr. Chief Justice, President Eisenhower, Vice President Nixon, President Truman, Reverend Clergy, fellow citizens:

We observe today not a victory of party but a celebration of freedom, symbolizing an end as well as a beginning, signifying renewal as well as change. For I have sworn before you and Almighty God the same solemn oath our forebears prescribed nearly a century and three-quarters ago.

我们今天所看到的,并非是某一党派的胜利,而是自由的庆典。它象征着结束,亦象征着开始;意味着更新,亦意味着变化。因为我已在你们及万能的上帝面前,依着我们先辈175年前写下的誓言宣誓。

The world is very different now. For man holds in his mortal hands the power to abolish all forms of human poverty and all forms of human life. And yet the same revolutionary beliefs for which our forebears fought are still at issue around the globe -- the belief that the rights of man come not from the generosity of the state but from the hand of God.

世界已然今非昔比,因为人类手中已经掌握了巨大的力量,既可以用来消除各种形式的贫困,亦可用以毁灭人类社会。然而,我们先辈曾为之战斗的那些革命性的信念还依然在世界上受人争议——那就是,每个人享有的各项权利决非来自国家政权的慷慨赐予,而是出自上帝之手。

We dare not forget today that we are the heirs of that first revolution. Let the word go forth from this time and place, to friend and foe alike, that the torch has been passed to a new generation of Americans -- born in this century, tempered by war, disciplined by a hard and bitter peace, proud of our ancient heritage -- and unwilling to witness or permit the slow undoing of those human rights to which this nation has always been committed, and to which we are committed today at home and around the world.

今天,我们不敢有忘,我们乃是那第一次革命的后裔。此时,让这个声音从这里同时向我们的朋友和敌人传达:火炬现已传递到新一代美国人手中——他们生于本世纪,既经受过战火的锤炼,又经历过艰难严峻的和平岁月的考验。他们深为我们古老的遗产所自豪——决不愿目睹或听任诸项人权受到无形的侵蚀,这些权利不仅为这个国家始终信守不渝,亦是我们正在国内和世界上誓死捍卫的东西。

Let every nation know, whether it wishes us well or ill, that we shall pay any price, bear any burden, meet any hardship, support any friend, oppose any foe to assure the survival and the success of liberty.

让每一个国家都知道,无论它们对我们抱有善意还是恶意,我们都准备付出任何代价、承受任何重任、迎战任何艰险、支持任何朋友、反对任何敌人,以使自由得以维系和胜利。

This much we pledge -- and more.

这是我们矢志不移的承诺,且远不止此!

To those old allies whose cultural and spiritual origins we share, we pledge the loyalty of faithful friends. United there is little we cannot do in a host of cooperative ventures. Divided there is little we can do, for we dare not meet a powerful challenge at odds and split asunder.

对于那些与我们共享同一文化和精神源头的老朋友,我们许以朋友的忠诚。在许许多多的合作事业中,我们会尽己所能以促进我们的团结,而决不故意制造分裂,因为我们不敢轻易面对由分歧或体系崩溃而导致的巨大挑战。

To those new states whom we welcome to the ranks of the free, we pledge our word that one form of colonial control shall not have passed away merely to be replaced by a far more iron tyranny. We shall not always expect to find them supporting our view. But we shall always hope to find them strongly supporting their own freedom -- and to remember that, in the past, those who foolishly sought power by riding the back of the tiger ended up inside.

对于那些新成立的国家,我们欢迎它们加入自由阵营,并在此许以忠告:某种形式的殖民控制决不会仅仅因为被另一种更为残酷的霸权所取代就消声匿迹。我们不会期待他们始终支持我们的观点,但我们希望他们能始终坚定地维护他们自己的自由——并且牢记,在过去,那些愚蠢地骑上独~裁的虎背以谋求权力的人最终都以葬身虎腹而告终。

To those people in the huts and villages of half the globe struggling to break the bonds of mass misery, we pledge our best efforts to help them help themselves, for whatever period is required -- not because the communists may be doing it, not because we seek their votes, but because it is right.

对于那些寄居于大半个地球上的草舍村落、为着挣脱无尽苦难的枷锁而奋斗的人民,我们承诺将尽我们最大的努力,以使他们获得自助的能力。因为这是时代对我们提出的要求——不是因为共~产~党人可能如此行事、不是因为我们需要他们的选票,仅仅是因为这样做是正当的。

If a free society cannot help the many who are poor, it cannot save the few who are rich.

如果一个自由的社会不能帮助贫穷的多数,它就不能拯救那富裕的少数。

To our sister republics south of our border, we offer a special pledge: to convert our good words into good deeds, in a new alliance for progress, to assist free men and free governments in casting off the chains of poverty. But this peaceful revolution of hope cannot become the prey of hostile powers. Let all our neighbors know that we shall join with them to oppose aggression or subversion anywhere in the Americas.

对于我们的南部邻邦共和国,我们许以特殊的承诺:将我们的良言转为善行,在为了进步而结成的新盟邦里,帮助自由的人民和自由的政府摆脱贫困。但这一希翼中的和平革命不能成为敌对势力的牺牲品,让我们所有的邻邦都知道,我们将与他们一道,反对发生在美洲任何地区的侵略和颠覆。

And let every other power know that this hemisphere intends to remain the master of its own house.

让所有其他势力都知道,这一半球的人民致力于维护他们作为自己家园主人的地位。

To that world assembly of sovereign states, the United Nations, our last best hope in an age where the instruments of war have far outpaced the instruments of peace, we renew our pledge of support -- to prevent it from becoming merely a forum for invective, to strengthen its shield of the new and the weak, and to enlarge the area in which its writ may run.

对于那个主权国家的世界性会议组织——联合国,我们最后一次良好祝愿是发生在战争机器远远超过和平机器的时代。为了防止它沦为仅仅用来谩骂攻讦的论坛,为了加强它对新成立国家及弱小国家的保障功能、为了扩展其权力涵盖的领域,我们现在重申对它的支持承诺。

Finally, to those nations who would make themselves our adversary, we offer not a pledge but a request: that both sides begin anew the quest for peace -- before the dark powers of destruction unleashed by science engulf all humanity in planned or accidental self-destruction.

最后,对于那些主动站到我们敌对面的国家,我们提出的不是许诺,而是恳求:在被科学释放出的、黑暗的破坏力量以有计划的或偶然性的自我毁灭方式吞噬全人类之前,恳求双方再一次地开始谋求和平的努力。

We dare not tempt them with weakness. For only when our arms are sufficient beyond doubt can we be certain beyond doubt that they will never be employed. But neither can two great and powerful groups of nations take comfort from our present course -- both sides overburdened by the cost of modern weapons, both rightly alarmed by the steady spread of the deadly atom, yet both racing to alter that uncertain balance of terror that stays the hand of mankind's final war. So let us begin anew -- remembering on both sides that civility is not a sign of weakness, and sincerity is always subject to proof.

我们不敢以软弱诱惑它们,因为只有当我们的军备充足到确切无疑的程度时,我们才能确切无疑地肯定它们永远不会被投入使用。但这两个强大的国家集团都无法从彼此当前的做法中得到安慰——双方都背负了过高的现代武器系统的成本、双方都理所当然地对致死性原子武器的持续扩散感到惊恐不安,但双方都竞相改变不确定的恐怖均衡,这种均衡恰恰抑制了人类最后摊牌的冲动。

Let us never negotiate out of fear. But let us never fear to negotiate.

让我们永远不要因为惧怕而谈判,让我们永远不要惧怕谈判。

Let both sides explore what problems unite us instead of belaboring those problems which divide us.

让双方探寻那些能将我们团结在一起的因素,而不是那些刻意挑出那些分裂我们的因素。

Let both sides, for the first time, formulate serious and precise proposals for the inspection and control of arms, and bring the absolute power to destroy other nations under the absolute control of all nations.

让双方首先提出认真细致的方案来核查及控制军备,并将毁灭其他国家的绝对力量置于所有国家的绝对控制之下。

Let both sides seek to invoke the wonders of science instead of its terrors. Together let us explore the stars, conquer the deserts, eradicate disease, tap the ocean depths, and encourage the arts and commerce.

让双方努力去激发科学的奇迹,而非科学的恐怖。让我们一同探索星空、征服沙漠、消除疾病、开发海洋深处,鼓励艺术和商业。

Let both sides unite to heed, in all corners of the earth, the command of Isaiah -- to "undo the heavy burdens... [and] let the oppressed go free."

让双方在世界每一个角落,都共同信守《圣经.以赛亚书》中的教诲——“卸下重负……让被压迫者自由。”

And if a beachhead of cooperation may push back the jungle of suspicion, let both sides join in creating a new endeavor -- not a new balance of power, but a new world of law -- where the strong are just, and the weak secure, and the peace preserved.

如果合作的滩头堡能够遏制重重猜疑,让双方携手进行新的努力——不是为了建立新的势力均衡,而是为了建立新的规则体系——以使强者正义,弱者安全,和平维系。

All this will not be finished in the first one hundred days. Nor will it be finished in the first one thousand days; nor in the life of this Administration; nor even perhaps in our lifetime on this planet. But let us begin.

所有这些工作将不会在从现在起的一百天、一千天内完成,也不会在本届行政分支任期内完成,甚至可能不会在我们的有生之年完成,但是,请让我们现在开始工作。

In your hands, my fellow citizens, more than mine, will rest the final success or failure of our course. Since this country was founded, each generation of Americans has been summoned to give testimony to its national loyalty. The graves of young Americans who answered the call to service surround the globe.

我的同胞们,我们事业的最终成败将掌握在你们的手中而不仅仅是我的手中。从这个国家被创建那天起,每一代美国人都被召唤去证实自己对国家的忠诚。那些响应号召献身国家的年轻美国人的安息之所遍布全球。

Now the trumpet summons us again -- not as a call to bear arms, though arms we need -- not as a call to battle, though embattled we are -- but a call to bear the burden of a long twilight struggle, year in and year out, rejoicing in hope, patient in tribulation, a struggle against the common enemies of man: tyranny, poverty, disease, and war itself.

现在,召唤的号角又一次吹响——不是号召我们扛起武器,虽然武器是我们所需要的——也不是号召我们去参加战斗,虽然我们准备战斗——而是号召我们年复一年地去进行一场漫长而未分胜负的搏斗,在希望中欢乐,而患难中忍耐,以反对人类共同的敌人:暴政、贫困、疾病以及战争本身。

Can we forge against these enemies a grand and global alliance, North and South, East and West, that can assure a more fruitful life for all mankind? Will you join in that historic effort?

为了反对这些敌人,我们能够将南方与北方、东方与西方团结起来,熔铸成一个伟大的和全球性的联盟,以确保全人类得享更为成果累累的生活吗?你们愿意参与这项历史性的努力吗?

In the long history of the world, only a few generations have been granted the role of defending freedom in its hour of maximum danger. I do not shrink from this responsibility -- I welcome it. I do not believe that any of us would exchange places with any other people or any other generation. The energy, the faith, the devotion which we bring to this endeavor will light our country and all who serve it. And the glow from that fire can truly light the world.

在世界历史的长河里,只有少数几代人被赋予了在自由面临最大危机时捍卫自由的使命,我不会畏缩于这一责任——我欢迎它!我也不相信我们中的任何人会愿意与其他国家的人民或其他世代的人民易地而处。我们在这场努力中所倾注的精力、信念和奉献将照耀我们的国家以及所有为之献身的人,火焰所放射出的光芒必将普照全世界。

And so, my fellow Americans, ask not what your country can do for you; ask what you can do for your country.

所以,我的美国同胞们,不要问你的国家为你做了什么,而应问你能为你的国家做些什么。

My fellow citizens of the world, ask not what America will do for you, but what together we can do for the freedom of man.

我的世界同胞们,不要问美国将为你做些什么,而应问我们应该一起为了全人类的自由做些什么。

Finally, whether you are citizens of America or citizens of the world, ask of us here the same high standards of strength and sacrifice which we ask of you. With a good conscience our only sure reward, with history the final judge of our deeds, let us go forth to lead the land we love, asking His blessing and His help, but knowing that here on earth God's work must truly be our own.

最后,无论是美国公民还是世界其他国家的公民,请用我们要求于你们的关于力量和牺牲的高标准来要求我们,本着我们唯一可以指望有所回报的善意良知,依着能最终裁决我们功业的历史,让我们着手领导我们所热爱的国家,在祈求神的赐福和神的帮助的同时,也能深切体认,在这片土地上,神的工作必定也是我们自己所应承担的使命。

posted @ 2007-06-30 11:18 chenweicai 阅读(600) | 评论 (0)编辑 收藏

摘要:

虽然session机制在web应用程序中被采用已经很长时间了,但是仍然有很多人不清楚session机制的本质,以至不能正确的应用这一技术。本文将详细讨论session的工作机制并且对在Java web application中应用session机制时常见的问题作出解答。
 
一、术语session
在我的经验里,session这个词被滥用的程度大概仅次于transaction,更加有趣的是transaction与session在某些语境下的含义是相同的。

session,中文经常翻译为会话,其本来的含义是指有始有终的一系列动作/消息,比如打电话时从拿起电话拨号到挂断电话这中间的一系列过程可以称之为一个 session。有时候我们可以看到这样的话“在一个浏览器会话期间,...”,这里的会话一词用的就是其本义,是指从一个浏览器窗口打开到关闭这个期间 ①。最混乱的是“用户(客户端)在一次会话期间”这样一句话,它可能指用户的一系列动作(一般情况下是同某个具体目的相关的一系列动作,比如从登录到选购商品到结账登出这样一个网上购物的过程,有时候也被称为一个transaction),然而有时候也可能仅仅是指一次连接,也有可能是指含义①,其中的差别只能靠上下文来推断②。

然而当session一词与网络协议相关联时,它又往往隐含了“面向连接”和/或“保持状态”这样两个含义, “面向连接”指的是在通信双方在通信之前要先建立一个通信的渠道,比如打电话,直到对方接了电话通信才能开始,与此相对的是写信,在你把信发出去的时候你并不能确认对方的地址是否正确,通信渠道不一定能建立,但对发信人来说,通信已经开始了。“保持状态”则是指通信的一方能够把一系列的消息关联起来,使得消息之间可以互相依赖,比如一个服务员能够认出再次光临的老顾客并且记得上次这个顾客还欠店里一块钱。这一类的例子有“一个TCP session”或者 “一个POP3 session”③。

而到了web服务器蓬勃发展的时代,session在web开发语境下的语义又有了新的扩展,它的含义是指一类用来在客户端与服务器之间保持状态的解决方案④。有时候session也用来指这种解决方案的存储结构,如“把xxx保存在session 里”⑤。由于各种用于web开发的语言在一定程度上都提供了对这种解决方案的支持,所以在某种特定语言的语境下,session也被用来指代该语言的解决方案,比如经常把Java里提供的javax.servlet.http.HttpSession简称为session⑥。

鉴于这种混乱已不可改变,本文中session一词的运用也会根据上下文有不同的含义,请大家注意分辨。
在本文中,使用中文“浏览器会话期间”来表达含义①,使用“session机制”来表达含义④,使用“session”表达含义⑤,使用具体的“HttpSession”来表达含义⑥

二、HTTP协议与状态保持
HTTP 协议本身是无状态的,这与HTTP协议本来的目的是相符的,客户端只需要简单的向服务器请求下载某些文件,无论是客户端还是服务器都没有必要纪录彼此过去的行为,每一次请求之间都是独立的,好比一个顾客和一个自动售货机或者一个普通的(非会员制)大卖场之间的关系一样。

然而聪明(或者贪心?)的人们很快发现如果能够提供一些按需生成的动态信息会使web变得更加有用,就像给有线电视加上点播功能一样。这种需求一方面迫使HTML逐步添加了表单、脚本、DOM等客户端行为,另一方面在服务器端则出现了CGI规范以响应客户端的动态请求,作为传输载体的HTTP协议也添加了文件上载、 cookie这些特性。其中cookie的作用就是为了解决HTTP协议无状态的缺陷所作出的努力。至于后来出现的session机制则是又一种在客户端与服务器之间保持状态的解决方案。

让我们用几个例子来描述一下cookie和session机制之间的区别与联系。笔者曾经常去的一家咖啡店有喝5杯咖啡免费赠一杯咖啡的优惠,然而一次性消费5杯咖啡的机会微乎其微,这时就需要某种方式来纪录某位顾客的消费数量。想象一下其实也无外乎下面的几种方案:
1、该店的店员很厉害,能记住每位顾客的消费数量,只要顾客一走进咖啡店,店员就知道该怎么对待了。这种做法就是协议本身支持状态。
2、发给顾客一张卡片,上面记录着消费的数量,一般还有个有效期限。每次消费时,如果顾客出示这张卡片,则此次消费就会与以前或以后的消费相联系起来。这种做法就是在客户端保持状态。
3、发给顾客一张会员卡,除了卡号之外什么信息也不纪录,每次消费时,如果顾客出示该卡片,则店员在店里的纪录本上找到这个卡号对应的纪录添加一些消费信息。这种做法就是在服务器端保持状态。

由于HTTP协议是无状态的,而出于种种考虑也不希望使之成为有状态的,因此,后面两种方案就成为现实的选择。具体来说cookie机制采用的是在客户端保持状态的方案,而session机制采用的是在服务器端保持状态的方案。同时我们也看到,由于采用服务器端保持状态的方案在客户端也需要保存一个标识,所以session机制可能需要借助于cookie机制来达到保存标识的目的,但实际上它还有其他选择。

三、理解cookie机制
cookie机制的基本原理就如上面的例子一样简单,但是还有几个问题需要解决:“会员卡”如何分发;“会员卡”的内容;以及客户如何使用“会员卡”。

正统的cookie分发是通过扩展HTTP协议来实现的,服务器通过在HTTP的响应头中加上一行特殊的指示以提示浏览器按照指示生成相应的cookie。然而纯粹的客户端脚本如JavaScript或者VBScript也可以生成cookie。

而cookie 的使用是由浏览器按照一定的原则在后台自动发送给服务器的。浏览器检查所有存储的cookie,如果某个cookie所声明的作用范围大于等于将要请求的资源所在的位置,则把该cookie附在请求资源的HTTP请求头上发送给服务器。意思是麦当劳的会员卡只能在麦当劳的店里出示,如果某家分店还发行了自己的会员卡,那么进这家店的时候除了要出示麦当劳的会员卡,还要出示这家店的会员卡。

cookie的内容主要包括:名字,值,过期时间,路径和域。
其中域可以指定某一个域比如.google.com,相当于总店招牌,比如宝洁公司,也可以指定一个域下的具体某台机器比如www.google.com或者froogle.google.com,可以用飘柔来做比。
路径就是跟在域名后面的URL路径,比如/或者/foo等等,可以用某飘柔专柜做比。
路径与域合在一起就构成了cookie的作用范围。
如果不设置过期时间,则表示这个cookie的生命期为浏览器会话期间,只要关闭浏览器窗口,cookie就消失了。这种生命期为浏览器会话期的 cookie被称为会话cookie。会话cookie一般不存储在硬盘上而是保存在内存里,当然这种行为并不是规范规定的。如果设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie仍然有效直到超过设定的过期时间。

存储在硬盘上的cookie 可以在不同的浏览器进程间共享,比如两个IE窗口。而对于保存在内存里的cookie,不同的浏览器有不同的处理方式。对于IE,在一个打开的窗口上按 Ctrl-N(或者从文件菜单)打开的窗口可以与原窗口共享,而使用其他方式新开的IE进程则不能共享已经打开的窗口的内存cookie;对于 Mozilla Firefox0.8,所有的进程和标签页都可以共享同样的cookie。一般来说是用javascript的window.open打开的窗口会与原窗口共享内存cookie。浏览器对于会话cookie的这种只认cookie不认人的处理方式经常给采用session机制的web应用程序开发者造成很大的困扰。

下面就是一个goolge设置cookie的响应头的例子
HTTP/1.1 302 Found
Location: http://www.google.com/intl/zh-CN/
Set-Cookie: PREF=ID=0565f77e132de138:NW=1:TM=1098082649:LM=1098082649:S=KaeaCFPo49RiA_d8; expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; domain=.google.com
Content-Type: text/html


image
这是使用HTTPLook这个HTTP Sniffer软件来俘获的HTTP通讯纪录的一部分

image
浏览器在再次访问goolge的资源时自动向外发送cookie

image
用Firefox可以很容易的观察现有的cookie的值
使用HTTPLook配合Firefox可以很容易的理解cookie的工作原理。

image
IE也可以设置在接受cookie前询问

四、理解session机制

session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。

当程序需要为某个客户端的请求创建一个session的时候,服务器首先检查这个客户端的请求里是否已包含了一个session标识 - 称为 session id,如果已包含一个session id则说明以前已经为此客户端创建过session,服务器就按照session id把这个 session检索出来使用(如果检索不到,可能会新建一个),如果客户端请求不包含session id,则为此客户端创建一个session并且生成一个与此session相关联的session id,session id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个 session id将被在本次响应中返回给客户端保存。

保存这个session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发挥给服务器。一般这个cookie的名字都是类似于SEEESIONID,而。比如weblogic对于web应用程序生成的cookie,JSESSIONID= ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764,它的名字就是 JSESSIONID。

由于cookie可以被人为的禁止,必须有其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。经常被使用的一种技术叫做URL重写,就是把session id直接附加在URL路径的后面,附加方式也有两种,一种是作为URL路径的附加信息,表现形式为http://...../xxx;jsessionid= ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764
另一种是作为查询字符串附加在URL后面,表现形式为http://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764
这两种方式对于用户来说是没有区别的,只是服务器在解析的时候处理的方式不同,采用第一种方式也有利于把session id的信息和正常程序参数区分开来。
为了在整个交互过程中始终保持状态,就必须在每个客户端可能请求的路径后面都包含这个session id。

另一种技术叫做表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。比如下面的表单
<form name="testform" action="/xxx">
<input type="text">
</form>


在被传递给客户端之前将被改写成
<form name="testform" action="/xxx">
<input type="hidden" name="jsessionid" value="ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764">
<input type="text">
</form>


这种技术现在已较少应用,笔者接触过的很古老的iPlanet6(SunONE应用服务器的前身)就使用了这种技术。
实际上这种技术可以简单的用对action应用URL重写来代替。

在谈论session机制的时候,常常听到这样一种误解“只要关闭浏览器,session就消失了”。其实可以想象一下会员卡的例子,除非顾客主动对店家提出销卡,否则店家绝对不会轻易删除顾客的资料。对session来说也是一样的,除非程序通知服务器删除一个session,否则服务器会一直保留,程序一般都是在用户做log off的时候发个指令去删除session。然而浏览器从来不会主动在关闭之前通知服务器它将要关闭,因此服务器根本不会有机会知道浏览器已经关闭,之所以会有这种错觉,是大部分session机制都使用会话cookie来保存session id,而关闭浏览器后这个 session id就消失了,再次连接服务器时也就无法找到原来的session。如果服务器设置的cookie被保存到硬盘上,或者使用某种手段改写浏览器发出的HTTP请求头,把原来的session id发送给服务器,则再次打开浏览器仍然能够找到原来的session。

恰恰是由于关闭浏览器不会导致session被删除,迫使服务器为seesion设置了一个失效时间,当距离客户端上一次使用session的时间超过这个失效时间时,服务器就可以认为客户端已经停止了活动,才会把session删除以节省存储空间。

五、理解javax.servlet.http.HttpSession
HttpSession是Java平台对session机制的实现规范,因为它仅仅是个接口,具体到每个web应用服务器的提供商,除了对规范支持之外,仍然会有一些规范里没有规定的细微差异。这里我们以BEA的Weblogic Server8.1作为例子来演示。

首先,Weblogic Server提供了一系列的参数来控制它的HttpSession的实现,包括使用cookie的开关选项,使用URL重写的开关选项,session持久化的设置,session失效时间的设置,以及针对cookie的各种设置,比如设置cookie的名字、路径、域, cookie的生存时间等。

一般情况下,session都是存储在内存里,当服务器进程被停止或者重启的时候,内存里的session也会被清空,如果设置了session的持久化特性,服务器就会把session保存到硬盘上,当服务器进程重新启动或这些信息将能够被再次使用, Weblogic Server支持的持久性方式包括文件、数据库、客户端cookie保存和复制。

复制严格说来不算持久化保存,因为session实际上还是保存在内存里,不过同样的信息被复制到各个cluster内的服务器进程中,这样即使某个服务器进程停止工作也仍然可以从其他进程中取得session。

cookie生存时间的设置则会影响浏览器生成的cookie是否是一个会话cookie。默认是使用会话cookie。有兴趣的可以用它来试验我们在第四节里提到的那个误解。

cookie的路径对于web应用程序来说是一个非常重要的选项,Weblogic Server对这个选项的默认处理方式使得它与其他服务器有明显的区别。后面我们会专题讨论。

关于session的设置参考[5] http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869

六、HttpSession常见问题
(在本小节中session的含义为⑤和⑥的混合)

1、session在何时被创建
一个常见的误解是以为session在有客户端访问时就被创建,然而事实是直到某server端程序调用 HttpServletRequest.getSession(true)这样的语句时才被创建,注意如果JSP没有显示的使用 <% @page session="false"%> 关闭session,则JSP文件在编译成Servlet时将会自动加上这样一条语句 HttpSession session = HttpServletRequest.getSession(true);这也是JSP中隐含的 session对象的来历。

由于session会消耗内存资源,因此,如果不打算使用session,应该在所有的JSP中关闭它。

2、session何时被删除
综合前面的讨论,session在下列情况下被删除a.程序调用HttpSession.invalidate();或b.距离上一次收到客户端发送的session id时间间隔超过了session的超时设置;或c.服务器进程被停止(非持久session)

3、如何做到在浏览器关闭时删除session
严格的讲,做不到这一点。可以做一点努力的办法是在所有的客户端页面里使用javascript代码window.oncolose来监视浏览器的关闭动作,然后向服务器发送一个请求来删除session。但是对于浏览器崩溃或者强行杀死进程这些非常规手段仍然无能为力。

4、有个HttpSessionListener是怎么回事
你可以创建这样的listener去监控session的创建和销毁事件,使得在发生这样的事件时你可以做一些相应的工作。注意是session的创建和销毁动作触发listener,而不是相反。类似的与HttpSession有关的listener还有 HttpSessionBindingListener,HttpSessionActivationListener和 HttpSessionAttributeListener。

5、存放在session中的对象必须是可序列化的吗
不是必需的。要求对象可序列化只是为了session能够在集群中被复制或者能够持久保存或者在必要时server能够暂时把session交换出内存。在 Weblogic Server的session中放置一个不可序列化的对象在控制台上会收到一个警告。我所用过的某个iPlanet版本如果 session中有不可序列化的对象,在session销毁时会有一个Exception,很奇怪。

6、如何才能正确的应付客户端禁止cookie的可能性
对所有的URL使用URL重写,包括超链接,form的action,和重定向的URL,具体做法参见[6]
http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770

7、开两个浏览器窗口访问应用程序会使用同一个session还是不同的session
参见第三小节对cookie的讨论,对session来说是只认id不认人,因此不同的浏览器,不同的窗口打开方式以及不同的cookie存储方式都会对这个问题的答案有影响。

8、如何防止用户打开两个浏览器窗口操作导致的session混乱
这个问题与防止表单多次提交是类似的,可以通过设置客户端的令牌来解决。就是在服务器每次生成一个不同的id返回给客户端,同时保存在session里,客户端提交表单时必须把这个id也返回服务器,程序首先比较返回的id与保存在session里的值是否一致,如果不一致则说明本次操作已经被提交过了。可以参看《J2EE核心模式》关于表示层模式的部分。需要注意的是对于使用javascript window.open打开的窗口,一般不设置这个id,或者使用单独的id,以防主窗口无法操作,建议不要再window.open打开的窗口里做修改操作,这样就可以不用设置。

9、为什么在Weblogic Server中改变session的值后要重新调用一次session.setValue
做这个动作主要是为了在集群环境中提示Weblogic Server session中的值发生了改变,需要向其他服务器进程复制新的session值。

10、为什么session不见了
排除session正常失效的因素之外,服务器本身的可能性应该是微乎其微的,虽然笔者在iPlanet6SP1加若干补丁的Solaris版本上倒也遇到过;浏览器插件的可能性次之,笔者也遇到过3721插件造成的问题;理论上防火墙或者代理服务器在cookie处理上也有可能会出现问题。
出现这一问题的大部分原因都是程序的错误,最常见的就是在一个应用程序中去访问另外一个应用程序。我们在下一节讨论这个问题。

七、跨应用程序的session共享

常常有这样的情况,一个大项目被分割成若干小项目开发,为了能够互不干扰,要求每个小项目作为一个单独的web应用程序开发,可是到了最后突然发现某几个小项目之间需要共享一些信息,或者想使用session来实现SSO(single sign on),在session中保存login的用户信息,最自然的要求是应用程序间能够访问彼此的session。

然而按照Servlet规范,session的作用范围应该仅仅限于当前应用程序下,不同的应用程序之间是不能够互相访问对方的session的。各个应用服务器从实际效果上都遵守了这一规范,但是实现的细节却可能各有不同,因此解决跨应用程序session共享的方法也各不相同。

首先来看一下Tomcat是如何实现web应用程序之间session的隔离的,从 Tomcat设置的cookie路径来看,它对不同的应用程序设置的cookie路径是不同的,这样不同的应用程序所用的session id是不同的,因此即使在同一个浏览器窗口里访问不同的应用程序,发送给服务器的session id也可以是不同的。

image
image

根据这个特性,我们可以推测Tomcat中session的内存结构大致如下。
image

笔者以前用过的iPlanet也采用的是同样的方式,估计SunONE与iPlanet之间不会有太大的差别。对于这种方式的服务器,解决的思路很简单,实际实行起来也不难。要么让所有的应用程序共享一个session id,要么让应用程序能够获得其他应用程序的session id。

iPlanet中有一种很简单的方法来实现共享一个session id,那就是把各个应用程序的cookie路径都设为/(实际上应该是/NASApp,对于应用程序来讲它的作用相当于根)。
<session-info>
<path>/NASApp</path>
</session-info>


需要注意的是,操作共享的session应该遵循一些编程约定,比如在session attribute名字的前面加上应用程序的前缀,使得 setAttribute("name", "neo")变成setAttribute("app1.name", "neo"),以防止命名空间冲突,导致互相覆盖。

在Tomcat中则没有这么方便的选择。在Tomcat版本3上,我们还可以有一些手段来共享session。对于版本4以上的Tomcat,目前笔者尚未发现简单的办法。只能借助于第三方的力量,比如使用文件、数据库、JMS或者客户端cookie,URL参数或者隐藏字段等手段。

我们再看一下Weblogic Server是如何处理session的。
image
image

从截屏画面上可以看到Weblogic Server对所有的应用程序设置的cookie的路径都是/,这是不是意味着在Weblogic Server中默认的就可以共享session了呢?然而一个小实验即可证明即使不同的应用程序使用的是同一个session,各个应用程序仍然只能访问自己所设置的那些属性。这说明Weblogic Server中的session的内存结构可能如下
image

对于这样一种结构,在 session机制本身上来解决session共享的问题应该是不可能的了。除了借助于第三方的力量,比如使用文件、数据库、JMS或者客户端 cookie,URL参数或者隐藏字段等手段,还有一种较为方便的做法,就是把一个应用程序的session放到ServletContext中,这样另外一个应用程序就可以从ServletContext中取得前一个应用程序的引用。示例代码如下,

应用程序A
context.setAttribute("appA", session);


应用程序B
contextA = context.getContext("/appA");
HttpSession sessionA = (HttpSession)contextA.getAttribute("appA");


值得注意的是这种用法不可移植,因为根据ServletContext的JavaDoc,应用服务器可以处于安全的原因对于context.getContext("/appA");返回空值,以上做法在Weblogic Server 8.1中通过。

那么Weblogic Server为什么要把所有的应用程序的cookie路径都设为/呢?原来是为了SSO,凡是共享这个session的应用程序都可以共享认证的信息。一个简单的实验就可以证明这一点,修改首先登录的那个应用程序的描述符weblogic.xml,把cookie路径修改为/appA 访问另外一个应用程序会重新要求登录,即使是反过来,先访问cookie路径为/的应用程序,再访问修改过路径的这个,虽然不再提示登录,但是登录的用户信息也会丢失。注意做这个实验时认证方式应该使用FORM,因为浏览器和web服务器对basic认证方式有其他的处理方式,第二次请求的认证不是通过 session来实现的。具体请参看[7] secion 14.8 Authorization,你可以修改所附的示例程序来做这些试验。

八、总结
session机制本身并不复杂,然而其实现和配置上的灵活性却使得具体情况复杂多变。这也要求我们不能把仅仅某一次的经验或者某一个浏览器,服务器的经验当作普遍适用的经验,而是始终需要具体情况具体分析。
摘要:虽然session机制在web应用程序中被采用已经很长时间了,但是仍然有很多人不清楚session机制的本质,以至不能正确的应用这一技术。本文将详细讨论session的工作机制并且对在Java web application中应用session机制时常见的问题作出解答。

posted @ 2007-06-27 22:36 chenweicai 阅读(255) | 评论 (0)编辑 收藏

counting 1 bits C implementations

(idea) by bis (1.5 mon) (print)   ?   Thu Oct 18 2001 at 4:34:42

Here are C implementations of all the methods for counting 1 bits mentioned in that node. (Go read that first, if you haven't already.) All of the statistical information is purely anecdotal, but for what it's worth, it's based on my testing the code on a Pentium 3 and a Celeron 2, using the cl compiler of Microsoft Visual C++, and on a Sun Ultra 5, using gcc and Sun's own cc. For testing 64-bit code, I used __int64 on the Intel machines, and long long on the Sparc. It's worth noting that while Sun's compiler outputs faster executables than gcc, it doesn't change the relative performance of the different methods.

Table Lookup

Use a pre-built lookup table of all the 1-bit counts for every possibly byte, then index into that for each byte that comprises the word. This is the fastest method (slightly) for 32 bits on both Intel and Sparc, and (even more slightly) the fastest for 64 bits on Sparc, falling to second fastest on 64 bits on Intel. Changing the lookup table from anything but unsigned or int makes it a little slower (what with the extra casting and byte-loading the compiler is forced to add.)
unsigned numbits_lookup_table[256] = {
                0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2,
                3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3,
                3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3,
                4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4,
                3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5,
                6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4,
                4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5,
                6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5,
                3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3,
                4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6,
                6, 7, 6, 7, 7, 8
                };
                unsigned numbits_lookup(unsigned i)
                {
                unsigned n;
                n = numbits_lookup_table[i & 0xff];
                n += numbits_lookup_table[i>>8  & 0xff];
                n += numbits_lookup_table[i>>16 & 0xff];
                n += numbits_lookup_table[i>>24 & 0xff];
                return n;
                }
                

 

Counters

If you want a full explanation of how this works, read my writeup at counting 1 bits, but suffice it to say that you are essentially partitioning the word into groups, and combining the groups by adding them together in pairs until you are left with only one group, which is the answer. (performance notes in the next section.)
unsigned numbits(unsigned int i)
                {
                unsigned int const MASK1  = 0x55555555;
                unsigned int const MASK2  = 0x33333333;
                unsigned int const MASK4  = 0x0f0f0f0f;
                unsigned int const MASK8  = 0x00ff00ff;
                unsigned int const MASK16 = 0x0000ffff;
                i = (i&MASK1 ) + (i>>1 &MASK1 );
                i = (i&MASK2 ) + (i>>2 &MASK2 );
                i = (i&MASK4 ) + (i>>4 &MASK4 );
                i = (i&MASK8 ) + (i>>8 &MASK8 );
                i = (i&MASK16) + (i>>16&MASK16);
                return i;
                }
                

 

Optimized Counters

call pointed out in counting 1 bits that you could optimize the Counters method further if you pay attention to which bits you care about and which you don't, which allows you to skip applying some of the masks.

 

Some symbols that I'll use to represent what's going on:

  • 0: bits we know are zero from the previous step
  • o: bits we know are zero due to masking
  • -: bits we know are zero due to shifting
  • X: bits that might be 1 and we care about their values
  • x: bits that might be 1 but we don't care about their values

 

So a 0 plus a 0 is still a 0, obviously; the tricky ones are the others, but they're not even so bad. 0 plus X is X, since if the X is a 0 or a 1, added to 0 it will pass through unchanged. However, X plus X is XX, because the sum can range from 0 (0+0), to 10 (1+1). The same holds true with xs, once those show up.

Step 1:

        oXoXoXoXoXoXoXoXoXoXoXoXoXoXoXoX
                +       -XoXoXoXoXoXoXoXoXoXoXoXoXoXoXoX
                XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
                
Step 2:
        ooXXooXXooXXooXXooXXooXXooXXooXX
                +       --XXooXXooXXooXXooXXooXXooXXooXX
                0XXX0XXX0XXX0XXX0XXX0XXX0XXX0XXX
                
Step 3:
        oooo0XXXoooo0XXXoooo0XXXoooo0XXX
                +       ----0XXXoooo0XXXoooo0XXXoooo0XXX
                0000XXXX0000XXXX0000XXXX0000XXXX
                
Step 4:
        oooooooo0000XXXXoooooooo0000XXXX
                +       --------0000XXXXoooooooo0000XXXX
                00000000000XXXXX00000000000XXXXX
                
Step 5:
        oooooooooooooooo00000000000XXXXX
                +       ----------------00000000000XXXXX
                00000000000000000000000000XXXXXX
                
You'll notice that the higher the step, the more known zeros (0) there are. call's suggestion was to change step 5 to this:

Step 5:
        ooooooooooooxxxx00000000000XXXXX
                +       ----------------00000000000XXXXX
                000000000000xxxx0000000000XXXXXX
                (mask)  ooooooooooooooooooooooooooXXXXXX
                
(where "(mask)" means "after adding, apply a mask".)

 

However, you can go back even further and apply the same technique - all the way to step 3, in fact. The best I can think to optimize this changes the last three steps into the following: Step 3:

        0xxx0XXX0xxx0XXX0xxx0XXX0xxx0XXX
                +       ----0XXX0xxx0XXX0xxx0XXX0xxx0XXX
                0xxxXXXX0xxxXXXX0xxxXXXX0xxxXXXX
                (mask)  0000XXXX0000XXXX0000XXXX0000XXXX
                
Step 4:
        0000xxxx0000XXXX0000xxxx0000XXXX
                +       --------0000XXXX0000xxxx0000XXXX
                0000xxxx000XXXXX000xxxxx000XXXXX
                
Step 5:
        0000xxxx000xxxxx000xxxxx000XXXXX
                +       ----------------000xxxxx000XXXXX
                0000xxxx000xxxxx00xxxxxx00XXXXXX
                (mask)  ooooooooooooooooooooooooooXXXXXX
                
Anyway, that's all very lovely, but here's the C to do it:
unsigned numbits(unsigned int i)
                {
                unsigned int const MASK1  = 0x55555555;
                unsigned int const MASK2  = 0x33333333;
                unsigned int const MASK4  = 0x0f0f0f0f;
                unsigned int const MASK6 = 0x0000003f;
                unsigned int const w = (v & MASK1) + ((v >> 1) & MASK1);
                unsigned int const x = (w & MASK2) + ((w >> 2) & MASK2);
                unsigned int const y = (x + (x >> 4) & MASK4);
                unsigned int const z = (y + (y >> 8));
                unsigned int const c = (z + (z >> 16)) & MASK6;
                return c;
                }
                
The performance on this method is marginally worse than the lookup method in the 32 bit cases, slightly better than lookup on 64 bit Intel, and right about the same on 64 bit Sparc. Of note is the fact that loading one of these bitmasks into a register actually takes two instructions on RISC machines, and a longer-than-32-bit instruction on the Intel, because it's impossible to pack an instruction and 32 bits worth of data into a single 32 bit instruction. See the bottom of jamesc's writeup at MIPS for more details on that...

 

Mind-bending "best" method (even more optimized counters)

A slightly-modified version of the code on this page: http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel, which in turn stole the code from the "Software Optimization Guide for AMD AthlonTM 64 and OpteronTM Processors":
unsigned numbits(unsigned int i)
                {
                unsigned int const MASK1 = 0x55555555;
                unsigned int const MASK2 = 0x33333333;
                unsigned int const MASK4 = 0x0f0f0f0f;
                unsigned int const w = v - ((v >> 1) & MASK1);
                unsigned int const x = (w & MASK2) + ((w >> 2) & MASK2);
                unsigned int const y = (x + (x >> 4) & MASK4);
                unsigned int const c = (y * 0x01010101) >> 24;
                return c;
                }
                
This method is identical to the "Optimized Counters" method, with two tricks applied:
  1. To get rid of an AND in the first line: instead of adding adjacent bits, it subtracts the high bit by itself of a pair of the bits from the pair together, because the results are the same. 00 - 0 = 0, 01 - 0 = 01, 10 - 1 = 01, 11 - 1 = 10
  2. To merge the last two lines into one, it uses a multiply and a shift, which adds the four remaining byte-sized "counters" together in one step.

 

Subtract 1 and AND

See counting 1 bits SPOILER for a fuller explanation of this one, but basically the lowest 1-bit gets zeroed out every iteration, so when you run out of 1s to zero, you've iterated to the number of bits in the word. Clever. Unfortunately, not that terribly fast; it's roughly two to three times slower than the lookup and counters methods on both architectures.
unsigned numbits_subtraction(unsigned i)
                {
                unsigned n;
                for(n=0; i; n++)
                i &= i-1;
                return n;
                }
                

 

Straightforwardly Examine Each Bit

The most easily understandable and slowest method: iterate over all the bits in the word; if the current bit is a 1, then increment the counter, otherwise, do nothing. That's actually done here by looking at the least-significant bit on each iteration, then shifting to the right one, and iterating until there are no more 1 bits in the word. There's a little optimization in the #ifndef here: instead of doing if (i & 1) n++;, which uses a branch instruction, just add the actual value of the least-significant bit to the counter ( n += (i & 1); ), as it will be a 1 when you want to add 1, and 0 when you don't. (We're just twiddling bits anyway, so why not?) This actually makes the processor do more adds, but adding is fast, and branching is slow, on modern processors, so it turns out to be about twice as fast. However, even "twice as fast" is still four to five times slower than the lookup method, again, on all architectures.
unsigned numbits(unsigned int i)
                {
                unsigned n;
                for(n=0; i; i >>= 1)
                #ifndef MORE_OPTIMAL
                if (i & 1) n++;
                #else
                n += (i & 1);
                #endif
                return n;
                }
                
Now, why does this all matter? It doesn't, really, but it was sure a good way to waste some time, and maybe someone learned some optimizing tricks from it... (Well, I did, actually - so I hope someone else did as well.)

posted @ 2007-06-27 18:30 chenweicai 阅读(342) | 评论 (0)编辑 收藏