一輩子的程序員?

          愛你一生不變-芳芳!
          posts - 27, comments - 15, trackbacks - 0, articles - 0
            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

              何去何從?有點(diǎn)迷茫,是沉寂在溫柔之鄉(xiāng)還是去迎接狂風(fēng)暴雨!
          看來自己還是沒有自信!bs..........myself

          posted @ 2007-08-15 14:32 boddi 閱讀(590) | 評(píng)論 (3)編輯 收藏

          2007最新騙局:

          ?????建行一同志轉(zhuǎn)送:?今天經(jīng)過一棟大樓門口,門口有一提款機(jī)。有一個(gè)老伯,一直?

          看著我走過他身邊,突然叫住我。他說他不識(shí)字,拿一張?zhí)峥羁ㄒ規(guī)退诖髽情T口?

          的自動(dòng)提款機(jī)取錢。我回答我無法幫你取,叫警衛(wèi)幫你。結(jié)果,他就回答我說不用了?

          ,繼續(xù)找其他路人幫他取錢。朋友們要記住---取款機(jī)可是有攝影機(jī)耶。萬一他說我搶?

          劫或是偷他的提款卡,甚至他的卡片是偷來的,幫他領(lǐng)錢會(huì)在提款機(jī)留下影像,絕對(duì)?

          會(huì)讓你百口莫辯!我會(huì)警惕?!?是因?yàn)橐延型律袭?dāng),目前仍官司纏身。顯然這是詐騙?

          集團(tuán)在找替身了!?請(qǐng)用力傳出去~~~?騙案真是層出不窮,一不小心就會(huì)踏入陷阱,真是?

          令人防不勝防!

          提醒各位朋友在外多小心!?

          (2)一業(yè)主,家中突然斷電,看到窗戶外別人家里都有電,就出門查看自家電?

          表箱,打開門就被刀子頂著了——持刀入室搶劫傷人家里突然斷電,不要貿(mào)然就開門?

          查看,有貓眼的多觀察一會(huì)門外動(dòng)靜,沒貓眼的也隔著門靜聽一段時(shí)間,沒有異常響?

          動(dòng)再開門。?

          (3)各位女同胞們注意了!這是最新騙局?

          女同胞請(qǐng)注意?男同胞請(qǐng)叫自己的朋友注意?新出的情況,女性朋友要特別注意啦:?

          一位上班的小姐在下班回家的路上看到一個(gè)小孩子一直哭,很可憐?,然后就過去問那?

          小朋友怎么了.小朋友就跟那個(gè)小姐說:?我迷路了,可以請(qǐng)你帶我回家嗎?然后拿一張?

          紙條給她看,?說那是他家地址.然后她就笨笨的帶小孩子去了.一般人都有同情心,然后?

          帶到那個(gè)所謂小孩子的家里以后,她一按鈴,門鈴像是有高壓電,就失去知覺了.隔天醒?

          來就被脫光光在一間空屋里,身邊什么都沒有了,她甚至連犯人長啥樣子都沒看見.所以?

          ,現(xiàn)在人犯案都是利用同情心啊,如果遇到類似這種的,千萬別帶他去,要帶就帶他到派?

          出所去好了,走丟的小孩放到派出所一定沒錯(cuò)啦,請(qǐng)通知身邊所有女性,為了廣大女士?

          的安全,看完后麻煩給轉(zhuǎn)發(fā)給所有人。?

          (4)今天遇到討飯新招,大家注意提防了~~?

          今天在家休息,有人按門鈴,開門一看,是個(gè)50來歲的老婦女,手里拿了2包喜糖,我?

          還以為是鄰居來分喜糖的,結(jié)果一開口,聽得出不是本地人,她說什么這2包糖給我們?

          的,圖個(gè)喜氣,要換一點(diǎn)錢給她,后面還說了一大堆不知道什么,我也沒聽清楚,感?

          覺就是不對(duì),嚇的馬上關(guān)門,暈!這年頭,還有這么討錢的一剛。?

          (5)轉(zhuǎn)發(fā):大家注意了!到自動(dòng)取款機(jī)取錢時(shí)一定要倍加小心!!!!!?

          昨晚在工行自動(dòng)取款機(jī)取錢時(shí),后面來了個(gè)老婦女,問我能不能取錢,還說?

          什么取款機(jī)有個(gè)鍵可能壞了,旁邊不知什么時(shí)候來了個(gè)小女孩,一直想我身邊躋,我?

          也沒在意,小孩子淘氣嘛,可是過分的是她竟然把手朝出鈔口放,準(zhǔn)備拿我的錢了,?

          我感覺不對(duì)勁了,立即把她推到一邊,等著把錢取出來。之后我想了一下,她們倆給?

          我設(shè)了個(gè)套:老婦女負(fù)責(zé)和我瞎聊,吸引我的注意力,小女孩趁我不注意時(shí)搶走我的?

          錢!如果我不防備的話,錢說不定就被搶走了,這樣的話,我就進(jìn)套了:(一則我立?

          即去追小女孩,去追回我的錢,可是誰又會(huì)相信一個(gè)小女孩能搶我一個(gè)大人的錢呢??

          更可怕的是站在我后面的老婦女將會(huì)取光我卡中所有的錢,因?yàn)槲业目ㄟ€在取款機(jī)里?

          面;二則我不立即去追小女孩,等拿到卡再追,到那時(shí)小女孩就無影無蹤了,錢也就?

          沒了啊:(她們真的很聰明,很可恥的!!!)?

          這是我的親身經(jīng)歷,希望大家以后取錢時(shí)一定要警惕起來,注意觀察周圍的所有人,?

          并轉(zhuǎn)告周圍的家人、同事、朋友,讓壞蛋分子沒有可乘之機(jī)!!!!又出現(xiàn)騙局新招?

          !!?

          (6)我父母都退休在家。昨天上午,來一陌生中年人,說自己摩托車油開沒了,加油?

          站太遠(yuǎn),摩托車又太重推不動(dòng),所以想問我父母要一個(gè)可樂瓶去買汽油,剛開口就說?

          實(shí)在不行就出2、3元買一個(gè)空瓶好了。我母親就拿了個(gè)空瓶給他,別說他還真從口袋?

          里掏出錢來,不過是幾張百元大鈔,還讓我父母找錢。我母親頓生警覺,說算了,不?

          過是一個(gè)空瓶而已。他10元錢買下來,只不過還是那張百元大鈔。好在我母?

          親尚未龍鐘,也不是那種愛貪小便宜的人?

          女性朋友一定要認(rèn)真看完,注意自我安全啊,現(xiàn)在萬惡的社會(huì)。。。。朋友發(fā)給我一?

          篇報(bào)道,現(xiàn)轉(zhuǎn)給各位看看?,出門在外,千萬小心,小心千萬。。。?

          (7)一對(duì)新婚夫婦到巴黎度蜜月。在巴黎,妻子在一間時(shí)尚服裝店試衣服?,身為丈夫?

          就在試衣間外等候。但等候多時(shí)卻不見妻子走出來?,緊張的丈夫要求店員幫忙到里頭?

          查看?,卻意外發(fā)現(xiàn)試衣間空無一人。丈夫以為妻子開玩笑作弄人?,要他緊張.于是回到?

          酒店等她回來。幾小時(shí)后卻不見妻子的蹤影,才知事態(tài)嚴(yán)重。丈夫趕忙報(bào)警?,并到巴黎?

          所有服裝店和醫(yī)院詢問妻子下落。三星期過去了,妻子猶如從人間蒸發(fā),音訊全無,傷?

          心的丈夫只能收拾包袱回到?**?。由于無法從絕望中振作,丈夫無心工作,甚至獨(dú)自生?

          活?,決定把自己放逐,流浪到各地方。幾年后?,他心血來潮到巴厘島,在一破舊的屋子?

          參觀一畸形秀?(?freak?show?)?。他見到一臟生銹的鐵籠里,有一女人四肢全無,身軀,?

          包括臉部,猶如破布般殘破?,充滿疤痕。她在地上扭曲著?,并發(fā)出有如野獸般的呻吟聲?

          。突然間男人驚恐地發(fā)出尖叫聲。他從那毫無人樣的女人臉上見到,他再熟悉不過,屬?

          于他新婚不久就告失蹤的妻子臉上的紅色胎記。?

          (8)另一版本則發(fā)生在上海。幾年前一女通知公安她的表妹在上海市集購物時(shí)無故失?

          蹤,可是遍尋不著?,直到五年后一友人撞見這表妹在泰國曼谷街道上行乞。恐怖的是她?

          不知何故沒了雙手雙腳,身子被鐵鏈綁在燈柱旁。?

          (9)這是在某一對(duì)夫婦去香港游玩時(shí)發(fā)生的故事。一對(duì)夫妻不知不覺走入了全香港治?

          安最壞的地區(qū)的一家精品店里???妻子對(duì)店里的衣服樣式十分喜歡?,隨后就進(jìn)入試衣間?

          試衣。可是,先生在外頭等了又等,卻不見妻子出來。由于實(shí)在是等太久了?,所以先生?

          開門?進(jìn)去找她,可是試衣間里早已空空如也。他吃驚地向店員詢問妻子到哪里去了,可?

          是店員們卻好象是串通好了一樣,都說沒有看見,并堅(jiān)持根本沒有象他妻子這樣的人來?

          過店里。因此他只好請(qǐng)當(dāng)?shù)氐木靺f(xié)助搜索這家精品店?,可是卻一無所獲。后來他又?

          一個(gè)人找了一段時(shí)間?,直到他的簽證到期。最后不得已他就在找不到妻子的情況下回?

          國了。之后經(jīng)過了一年?…?他向公司請(qǐng)了一段長假,再一次回到香港去找他的妻子。他?

          帶著妻子的相片走遍香港的大街小巷,但這次仍是一點(diǎn)線索也沒有。終于假期就要結(jié)束?

          了,他身心疲憊地開始考慮要回國的時(shí)候?,有一天無意間經(jīng)過了一間珍奇小屋。小屋的?

          看板上寫著?:?達(dá)磨(不倒翁)?雖然他對(duì)珍奇事物并不感興趣?,但由于連日疲勞他想?

          讓自己改變一下心情?,?加上看板上寫著?達(dá)磨的文字也引起他的興趣。最后他決定?

          進(jìn)去瞧瞧。但是他不該進(jìn)去的!因?yàn)檎淦嫘∥堇锩嬲钩鲆患钏麘K不忍睹的東?…?小屋?

          里的舞臺(tái)上有一位手腳都被切斷的全裸女性被當(dāng)成花瓶一樣擺在那里?!?這位女性的舌?

          頭已經(jīng)被拔掉了,不斷發(fā)出奇怪的呻吟聲。看到這么惡心的東西真令他恨不得馬上拔腿?

          就跑?,但不知為什么他心里感受到一股奇怪的氣氛?,于是他又重新仔細(xì)看那女人的面?

          孔?…?沒錯(cuò)!這女人正是他一年前失蹤的妻子。后來,他向當(dāng)?shù)氐暮诘乐Ц洱嫶蟮内H金?

          換取妻子的剩下的軀干。但一切都太遲了,他可憐的妻子早就已經(jīng)瘋了。現(xiàn)在她還住在?

          國內(nèi)某家醫(yī)院,繼續(xù)不斷地發(fā)出奇怪的呻吟聲?…?

          (10)最近有人告訴我,他的朋友在晚上聽到門口有嬰兒在哭,不過當(dāng)時(shí)已很晚了,?

          而且她認(rèn)為這件事很奇怪,于是她打電話給警察。警察告訴她∶?「無論如何,絕對(duì)不?

          要開?門。」這位女士表示那聲音聽起來象是嬰兒爬到窗戶附近哭,她擔(dān)心嬰兒會(huì)爬到?

          街上,被車子碾過。警察告訴她∶我們已派人前往,無論如何不能開門。警方認(rèn)為這?

          是一個(gè)連續(xù)殺人犯,利用嬰兒哭聲的錄音帶,誘使女性以為有人在外面遺棄嬰兒,她?

          們出門察看。雖然尚未證實(shí)此事,但是警方已接到許多女性打電話來說,他們晚上獨(dú)?

          自在家時(shí),聽到門外有嬰兒的哭聲,請(qǐng)將這個(gè)消息傳給其他人,不要因?yàn)槁牭綃雰旱?

          哭聲而開門。?

          請(qǐng)嚴(yán)肅看待這貼子!有這么離譜!小心為妙!!!?

          以前聽說大活人現(xiàn)場失蹤,后來被賣到馬戲團(tuán)、被賣器官什么的,只當(dāng)是天方夜譚。?

          結(jié)果真的看到發(fā)生在真實(shí)中的恐怖故事。?

          (11)事情是同事群發(fā)郵件告知的。她的朋友,簡稱小a吧,上周和兩個(gè)女孩子,簡稱?

          小b和小c,去逛羅湖商業(yè)城。羅湖商業(yè)城是深圳假貨集散地,龍蛇混雜,緊靠深圳火?

          車站和香港的羅湖口岸,人流量非常大。話說小c內(nèi)急,就去上衛(wèi)生間,小a和小b在洗?

          手間外面等。等了很久很久,還是不見小c出來,兩個(gè)人有點(diǎn)奇怪了。于是兩個(gè)人進(jìn)去?

          催她。誰知道進(jìn)去一看,人影全無。兩個(gè)人倒豎一口冷氣,打手機(jī)也沒人接。一個(gè)大?

          活人,難道就這么活不見人死不見尸的失蹤了?于是趕緊報(bào)警。?警察來了,問情事情?

          經(jīng)過以后,說了一句令人無比毛骨悚然的話,“你們有沒有看見其他可疑的人進(jìn)去??

          ”,兩個(gè)人再三回憶,沒有。因?yàn)椴豢赡軒е粋€(gè)活生生的100多斤的人出來,而她們?

          不注意。這時(shí)候小a突然想起來,其間有個(gè)清潔工打扮的人推著一輛清潔小車進(jìn)去、接?

          著又出來……?警察告訴他們,這種事情已經(jīng)不是第一次發(fā)生,現(xiàn)在深圳警方初步懷疑?

          一個(gè)犯罪團(tuán)伙,有組織地在管理疏松的低檔商業(yè)城,利用人們,尤其是女性對(duì)清潔工?

          沒有防范意識(shí)的心理,進(jìn)行有組織地綁架、販賣人體器官犯罪。別忘了,羅湖商業(yè)城?

          離香港和深圳火車站有多么地近。現(xiàn)在已經(jīng)幾天過去了,那個(gè)可憐的小c姑娘,仍是活?

          不見人死不見尸。我的同事說,小a,也就是我同事的朋友,仍在等小c的消息。但是?

          很可能,也許如果幸運(yùn)的話,活著的小c會(huì)被扔在哪個(gè)角落,只是失去了她的腎,但是?

          ,更有可能的是,也許再過幾天或者幾個(gè)月、幾年,小c的頭顱和軀體、四肢會(huì)在深圳?

          的城鄉(xiāng)結(jié)合部的垃圾堆被人發(fā)現(xiàn)。如果看到這個(gè)發(fā)生在身邊的活生生的恐怖事件,請(qǐng)?

          轉(zhuǎn)告身邊的女性親友,一定小心防范清潔工打扮的人,因?yàn)樗?她很可能會(huì)趁你不注意?

          把你敲暈,放進(jìn)清潔車?yán)撸酉聛淼戎愕氖菬o比恐怖的活人分尸。?

          posted @ 2007-03-05 17:11 boddi 閱讀(421) | 評(píng)論 (2)編輯 收藏

          遠(yuǎn)程方法調(diào)用入門指南(Java RMI Tutorial)


          Java R MI Tutorial

          遠(yuǎn)程方法調(diào)用入門指南

          Copyright ? 2005 Stephen Suen. All rights reserved.

          Java 遠(yuǎn)程方法調(diào)用(Remote Method Invocation, RMI)使得運(yùn)行在一個(gè) Java 虛擬機(jī)(Java Virtual Machine, JVM)的對(duì)象可以調(diào)用運(yùn)行另一個(gè) JVM 之上的其他對(duì)象的方法,從而提供了程序間進(jìn)行遠(yuǎn)程通訊的途徑。RMI 是 J2EE 的很多分布式技術(shù)的基礎(chǔ),比如 RMI-IIOP 乃至 EJB。本文是 RMI 的一個(gè)入門指南,目的在于幫助讀者快速建立對(duì) Java RMI 的一個(gè)感性認(rèn)識(shí),以便進(jìn)行更深層次的學(xué)習(xí)。事實(shí)上,如果你了解 RMI 的目的在于更好的理解和學(xué)習(xí) EJB,那么本文就再合適不過了。通過本文所了解的 RMI 的知識(shí)和技巧,應(yīng)該足夠服務(wù)于這個(gè)目的了。

          本文的最新版本將發(fā)布在程序員咖啡館網(wǎng)站上(建設(shè)中)。歡迎訂閱我們的郵件組,以獲得關(guān)于本文的正式發(fā)布及更新信息。

          全文在保證完整性,且保留全部版權(quán)聲明(包括上述鏈接)的前提下可以在任意媒體轉(zhuǎn)載——須保留此標(biāo)注。


          1. 簡介

          我們知道遠(yuǎn)程過程調(diào)用(Remote Procedure Call, RPC)可以用于一個(gè)進(jìn)程調(diào)用另一個(gè)進(jìn)程(很可能在另一個(gè)遠(yuǎn)程主機(jī)上)中的過程,從而提供了過程的分布能力。Java 的 RMI 則在 RPC 的基礎(chǔ)上向前又邁進(jìn)了一步,即提供分布式 對(duì)象間的通訊,允許我們獲得在遠(yuǎn)程進(jìn)程中的對(duì)象(稱為遠(yuǎn)程對(duì)象)的引用(稱為遠(yuǎn)程引用),進(jìn)而通過引用調(diào)用遠(yuǎn)程對(duì)象的方法,就好像該對(duì)象是與你的客戶端代碼同樣運(yùn)行在本地進(jìn)程中一樣。RMI 使用了術(shù)語"方法"(Method)強(qiáng)調(diào)了這種進(jìn)步,即在分布式基礎(chǔ)上,充分支持面向?qū)ο蟮奶匦浴?/p>

          RMI 并不是 Java 中支持遠(yuǎn)程方法調(diào)用的唯一選擇。在 RMI 基礎(chǔ)上發(fā)展而來的 RMI-IIOP(Java Remote Method Invocation over the Internet Inter-ORB Protocol),不但繼承了 RMI 的大部分優(yōu)點(diǎn),并且可以兼容于 CORBA。J2EE 和 EJB 都要求使用 RMI-IIOP 而不是 RMI。盡管如此,理解 RMI 將大大有助于 RMI-IIOP 的理解。所以,即便你的興趣在 RMI-IIOP 或者 EJB,相信本文也會(huì)對(duì)你很有幫助。另外,如果你現(xiàn)在就對(duì) API 感興趣,那么可以告訴你,RMI 使用 java.rmi 包,而 RMI-IIOP 則既使用 java.rmi 也使用擴(kuò)展的 javax.rmi 包。

          本文的隨后內(nèi)容將僅針對(duì) Java RMI。

          2. 分布式對(duì)象

          在學(xué)習(xí) RMI 之前,我們需要了解一些基礎(chǔ)知識(shí)。首先需要了解所謂的分布式對(duì)象(Distributed Object)。分布式對(duì)象是指一個(gè)對(duì)象可以被遠(yuǎn)程系統(tǒng)所調(diào)用。對(duì)于 Java 而言,即對(duì)象不僅可以被同一虛擬機(jī)中的其他客戶程序(Client)調(diào)用,也可以被運(yùn)行于其他虛擬機(jī)中的客戶程序調(diào)用,甚至可以通過網(wǎng)絡(luò)被其他遠(yuǎn)程主機(jī)之上的客戶程序調(diào)用。

          下面的圖示說明了客戶程序是如何調(diào)用分布式對(duì)象的:

          從圖上我們可以看到,分布式對(duì)象被調(diào)用的過程是這樣的:

          1. 客戶程序調(diào)用一個(gè)被稱為 Stub (有時(shí)譯作存根,為了不產(chǎn)生歧義,本文將使用其英文形式)的客戶端代理對(duì)象。該代理對(duì)象負(fù)責(zé)對(duì)客戶端隱藏網(wǎng)絡(luò)通訊的細(xì)節(jié)。Stub 知道如何通過網(wǎng)絡(luò)套接字(Socket)發(fā)送調(diào)用,包括如何將調(diào)用參數(shù)轉(zhuǎn)換為適當(dāng)?shù)男问揭员銈鬏數(shù)取?/p>

          2. Stub 通過網(wǎng)絡(luò)將調(diào)用傳遞到服務(wù)器端,也就是分布對(duì)象一端的一個(gè)被稱為 Skeleton 的代理對(duì)象。同樣,該代理對(duì)象負(fù)責(zé)對(duì)分布式對(duì)象隱藏網(wǎng)絡(luò)通訊的細(xì)節(jié)。Skeleton 知道如何從網(wǎng)絡(luò)套接字(Socket)中接受調(diào)用,包括如何將調(diào)用參數(shù)從網(wǎng)絡(luò)傳輸形式轉(zhuǎn)換為 Java 形式等。

          3. Skeleton 將調(diào)用傳遞給分布式對(duì)象。分布式對(duì)象執(zhí)行相應(yīng)的調(diào)用,之后將返回值傳遞給 Skeleton,進(jìn)而傳遞到 Stub,最終返回給客戶程序。

          這個(gè)場景基于一個(gè)基本的法則,即行為的定義和行為的具體實(shí)現(xiàn)相分離。如圖所示,客戶端代理對(duì)象 Stub 和分布式對(duì)象都實(shí)現(xiàn)了相同的接口,該接口稱為遠(yuǎn)程接口(Remote Interface)。正是該接口定義了行為,而分布式對(duì)象本身則提供具體的實(shí)現(xiàn)。對(duì)于 Java RMI 而言,我們用接口(interface)定義行為,用類(class)定義實(shí)現(xiàn)。

          3. RMI 架構(gòu)

          RMI 的底層架構(gòu)由三層構(gòu)成:

          • 首先是 Stub/Skeleton 層。該層提供了客戶程序和服務(wù)程序彼此交互的接口。

          • 然后是遠(yuǎn)程引用(Remote Reference)層。這一層相當(dāng)于在其之上的 Stub/Skeleton 層和在其之下的傳輸協(xié)議層之前的中間件,負(fù)責(zé)處理遠(yuǎn)程對(duì)象引用的創(chuàng)建和管理。

          • 最后是傳輸協(xié)議(Transport Protocol) 層。該層提供了數(shù)據(jù)協(xié)議,用以通過線路傳輸客戶程序和遠(yuǎn)程對(duì)象間的請(qǐng)求和應(yīng)答。

          這些層之間的交互可以參照下面的示意圖:

          和其它分布式對(duì)象機(jī)制一樣,Java RMI 的客戶程序使用客戶端的 Stub 向遠(yuǎn)程對(duì)象請(qǐng)求方法調(diào)用;服務(wù)器對(duì)象則通過服務(wù)器端的 Skeleton 接受請(qǐng)求。我們深入進(jìn)去,來看看其中的一些細(xì)節(jié)。

          注意: 事實(shí)上,在 Java 1.2 之后,RMI 不再需要 Skeleton 對(duì)象,而是通過 Java 的反射機(jī)制(Reflection)來完成對(duì)服務(wù)器端的遠(yuǎn)程對(duì)象的調(diào)用。為了便于說明問題,本文以下內(nèi)容仍然基于 Skeleton 來講解。

          當(dāng)客戶程序調(diào)用 Stub 時(shí),Stub 負(fù)責(zé)將方法的參數(shù)轉(zhuǎn)換為序列化(Serialized)形式,我們使用一個(gè)特殊的術(shù)語,即編列(Marshal)來指代這個(gè)過程。編列的目的是將這些參數(shù)轉(zhuǎn)換為可移植的形式,從而可以通過網(wǎng)絡(luò)傳輸?shù)竭h(yuǎn)程的服務(wù)對(duì)象一端。不幸的是,這個(gè)過程沒有想象中那么簡單。這里我們首先要理解一個(gè)經(jīng)典的問題,即方法調(diào)用時(shí),參數(shù)究竟是傳值還是傳引用呢?對(duì)于 Java RMI 來說,存在四種情況,我們將分別加以說明。

          • 對(duì)于基本的原始類型(整型,字符型等等),將被自動(dòng)的序列化,以傳值的方式編列。

          • 對(duì)于 Java 的對(duì)象,如果該對(duì)象是可序列化的(實(shí)現(xiàn)了 java.io.Serializable 接口),則通過 Java 序列化機(jī)制自動(dòng)地加以序列化,以傳值的方式編列。對(duì)象之中包含的原始類型以及所有被該對(duì)象引用,且沒有聲明為 transient 的對(duì)象也將自動(dòng)的序列化。當(dāng)然,這些被引用的對(duì)象也必須是可序列化的。

          • 絕大多數(shù)內(nèi)建的 Java 對(duì)象都是可序列化的。 對(duì)于不可序列化的 Java 對(duì)象(java.io.File 最典型),或者對(duì)象中包含對(duì)不可序列化,且沒有聲明為 transient 的其它對(duì)象的引用。則編列過程將向客戶程序拋出異常,而宣告失敗。

          • 客戶程序可以調(diào)用遠(yuǎn)程對(duì)象,沒有理由禁止調(diào)用參數(shù)本身也是遠(yuǎn)程對(duì)象(實(shí)現(xiàn)了 java.rmi.Remote 接口的類的實(shí)例)。此時(shí),RMI 采用一種模擬的傳引用方式(當(dāng)然不是傳統(tǒng)意義的傳引用,因?yàn)楸镜貙?duì)內(nèi)存的引用到了遠(yuǎn)程變得毫無意義),而不是將參數(shù)直接編列復(fù)制到遠(yuǎn)程。這種情況下,交互的雙方發(fā)生的戲劇性變化值得我們注意。參數(shù)是遠(yuǎn)程對(duì)象,意味著該參數(shù)對(duì)象可以遠(yuǎn)程調(diào)用。當(dāng)客戶程序指定遠(yuǎn)程對(duì)象作為參數(shù)調(diào)用服務(wù)器端遠(yuǎn)程對(duì)象的方法時(shí),RMI 的運(yùn)行時(shí)機(jī)制將向服務(wù)器端的遠(yuǎn)程對(duì)象發(fā)送作為參數(shù)的遠(yuǎn)程對(duì)象的一個(gè) Stub 對(duì)象。這樣服務(wù)器端的遠(yuǎn)程對(duì)象就可以回調(diào)(Callback)這個(gè) Stub 對(duì)象的方法,進(jìn)而調(diào)用在客戶端的遠(yuǎn)程對(duì)象的對(duì)應(yīng)方法。通過這種方法,服務(wù)器端的遠(yuǎn)程對(duì)象就可以修改作為參數(shù)的客戶端遠(yuǎn)程對(duì)象的內(nèi)部狀態(tài),這正是傳統(tǒng)意義的傳引用所具備的特性。是不是有點(diǎn)暈?這里的關(guān)鍵是要明白,在分布式環(huán)境中,所謂服務(wù)器和客戶端都是相對(duì)的。被請(qǐng)求的一方就是服務(wù)器,而發(fā)出請(qǐng)求的一方就是客戶端。

          在調(diào)用參數(shù)的編列過程成功后,客戶端的遠(yuǎn)程引用層從 Stub 那里獲得了編列后的參數(shù)以及對(duì)服務(wù)器端遠(yuǎn)程對(duì)象的遠(yuǎn)程引用(參見 java.rmi.server.RemoteRef API)。該層負(fù)責(zé)將客戶程序的請(qǐng)求依據(jù)底層的 RMI 數(shù)據(jù)傳輸協(xié)議轉(zhuǎn)換為傳輸層請(qǐng)求。在 RMI 中,有多種的可能的傳輸機(jī)制,比如點(diǎn)對(duì)點(diǎn)(Point-to-Point)以及廣播(Multicast)等。不過,在當(dāng)前的 JMI 版本中只支持點(diǎn)對(duì)點(diǎn)協(xié)議,即遠(yuǎn)程引用層將生成唯一的傳輸層請(qǐng)求,發(fā)往指定的唯一遠(yuǎn)程對(duì)象(參見 java.rmi.server.UnicastRemoteObject API)。

          在服務(wù)器端,服務(wù)器端的遠(yuǎn)程引用層接收傳輸層請(qǐng)求,并將其轉(zhuǎn)換為對(duì)遠(yuǎn)程對(duì)象的服務(wù)器端代理對(duì)象 Skeleton 的調(diào)用。Skeleton 對(duì)象負(fù)責(zé)將請(qǐng)求轉(zhuǎn)換為對(duì)實(shí)際的遠(yuǎn)程對(duì)象的方法調(diào)用。這是通過與編列過程相對(duì)的反編列(Unmarshal)過程實(shí)現(xiàn)的。所有序列化的參數(shù)被轉(zhuǎn)換為 Java 形式,其中作為參數(shù)的遠(yuǎn)程對(duì)象(實(shí)際上發(fā)送的是遠(yuǎn)程引用)被轉(zhuǎn)換為服務(wù)器端本地的 Stub 對(duì)象。

          如果方法調(diào)用有返回值或者拋出異常,則 Skeleton 負(fù)責(zé)編列返回值或者異常,通過服務(wù)器端的遠(yuǎn)程引用層,經(jīng)傳輸層傳遞給客戶端;相應(yīng)地,客戶端的遠(yuǎn)程引用層和 Stub 負(fù)責(zé)反編列并最終將結(jié)果返回給客戶程序。

          整個(gè)過程中,可能最讓人迷惑的是遠(yuǎn)程引用層。這里只要明白,本地的 Stub 對(duì)象是如何產(chǎn)生的,就不難理解遠(yuǎn)程引用的意義所在了。遠(yuǎn)程引用中包含了其所指向的遠(yuǎn)程對(duì)象的信息,該遠(yuǎn)程引用將用于構(gòu)造作為本地代理對(duì)象的 Stub 對(duì)象。構(gòu)造后,Stub 對(duì)象內(nèi)部將維護(hù)該遠(yuǎn)程引用。真正在網(wǎng)絡(luò)上傳輸?shù)膶?shí)際上就是這個(gè)遠(yuǎn)程引用,而不是 Stub 對(duì)象。

          4. RMI 對(duì)象服務(wù)

          在 RMI 的基本架構(gòu)之上,RMI 提供服務(wù)與分布式應(yīng)用程序的一些對(duì)象服務(wù),包括對(duì)象的命名/注冊(cè)(Naming/Registry)服務(wù),遠(yuǎn)程對(duì)象激活(Activation)服務(wù)以及分布式垃圾收集(Distributed Garbage Collection, DGC)。作為入門指南,本文將指介紹其中的命名/注冊(cè)服務(wù),因?yàn)樗菍?shí)戰(zhàn) RMI 所必備的。其它內(nèi)容請(qǐng)讀者自行參考其它更加深入的資料。

          在前一節(jié)中,如果你喜歡刨根問底,可能已經(jīng)注意到,客戶端要調(diào)用遠(yuǎn)程對(duì)象,是通過其代理對(duì)象 Stub 完成的,那么 Stub 最早是從哪里得來的呢?RMI 的命名/注冊(cè)服務(wù)正是解決這一問題的。當(dāng)服務(wù)器端想向客戶端提供基于 RMI 的服務(wù)時(shí),它需要將一個(gè)或多個(gè)遠(yuǎn)程對(duì)象注冊(cè)到本地的 RMI 注冊(cè)表中(參見java.rmi.registry.Registry API)。每個(gè)對(duì)象在注冊(cè)時(shí)都被指定一個(gè)將來用于客戶程序引用該對(duì)象的名稱。客戶程序通過命名服務(wù)(參見 java.rmi.Naming API),指定類似 URL 的對(duì)象名稱就可以獲得指向遠(yuǎn)程對(duì)象的遠(yuǎn)程引用。在 Naming 中的 lookup() 方法找到遠(yuǎn)程對(duì)象所在的主機(jī)后,它將檢索該主機(jī)上的 RMI 注冊(cè)表,并請(qǐng)求所需的遠(yuǎn)程對(duì)象。如果注冊(cè)表發(fā)現(xiàn)被請(qǐng)求的遠(yuǎn)程對(duì)象,它將生成一個(gè)對(duì)該遠(yuǎn)程對(duì)象的遠(yuǎn)程引用,并將其返回給客戶端,客戶端則基于遠(yuǎn)程引用生成相應(yīng)的 Stub 對(duì)象,并將引用傳遞給調(diào)用者。之后,雙方就可以按照我們前面講過的方式進(jìn)行交互了。

          注意: RMI 命名服務(wù)提供的 Naming 類并不是你的唯一選擇。RMI 的注冊(cè)表可以與其他命名服務(wù)綁定,比如 JNDI,這樣你就可以通過 JNDI 來訪問 RMI 的注冊(cè)表了。

          5. 實(shí)戰(zhàn) RMI

          理論離不開實(shí)踐,理解 RMI 的最好辦法就是通過例子。開發(fā) RMI 的分布式對(duì)象的大體過程包括如下幾步:

          1. 定義遠(yuǎn)程接口。這一步是通過擴(kuò)展 java.rmi.Remote 接口,并定義所需的業(yè)務(wù)方法實(shí)現(xiàn)的。

          2. 定義遠(yuǎn)程接口的實(shí)現(xiàn)類。即實(shí)現(xiàn)上一步所定義的接口,給出業(yè)務(wù)方法的具體實(shí)現(xiàn)邏輯。

          3. 編譯遠(yuǎn)程接口和實(shí)現(xiàn)類,并通過 RMI 編譯器 rmic 基于實(shí)現(xiàn)類生成所需的 Stub 和 Skeleton 類。

          RMI 中各個(gè)組件之間的關(guān)系如下面這個(gè)示意圖所示:

          回憶我們上一節(jié)所講的,Stub 和 Skeleton 負(fù)責(zé)代理客戶和服務(wù)器之間的通訊。但我們并不需要自己生成它們,相反,RMI 的編譯器 rmic 可以幫我們基于遠(yuǎn)程接口和實(shí)現(xiàn)類生成這些類。當(dāng)客戶端對(duì)象通過命名服務(wù)向服務(wù)器端的 RMI 注冊(cè)表請(qǐng)求遠(yuǎn)程對(duì)象時(shí),RMI 將自動(dòng)構(gòu)造對(duì)應(yīng)遠(yuǎn)程對(duì)象的 Skeleton 實(shí)例對(duì)象,并通過 Skeleton 對(duì)象將遠(yuǎn)程引用返回給客戶端。在客戶端,該遠(yuǎn)程引用將用于構(gòu)造 Stub 類的實(shí)例對(duì)象。之后,Stub 對(duì)象和 Skeleton 對(duì)象就可以代理客戶對(duì)象和遠(yuǎn)程對(duì)象之間的交互了。

          我們的例子展現(xiàn)了一個(gè)簡單的應(yīng)用場景。服務(wù)器端部署了一個(gè)計(jì)算引擎,負(fù)責(zé)接受來自客戶端的計(jì)算任務(wù),在服務(wù)器端執(zhí)行計(jì)算任務(wù),并將結(jié)果返回給客戶端。客戶端將發(fā)送并調(diào)用計(jì)算引擎的計(jì)算任務(wù)實(shí)際上是計(jì)算指定精度的 π 值。

          重要: 本文的例子改編自 The Java? Tutorial Trail:RMI。所有權(quán)利屬于相應(yīng)的所有人。

          6. 定義遠(yuǎn)程接口

          定義遠(yuǎn)程接口與非分布式應(yīng)用中定義接口的方法沒有太多的區(qū)別。只要遵守下面兩個(gè)要求:

          • 遠(yuǎn)程接口必須直接或者間接的擴(kuò)展自 java.rmi.Remote 接口。遠(yuǎn)程接口還可以在擴(kuò)展該接口的基礎(chǔ)上,同時(shí)擴(kuò)展其它接口,只要被擴(kuò)展的接口的所有方法與遠(yuǎn)程接口的所有方法一樣滿足下一個(gè)要求。

          • 在遠(yuǎn)程接口或者其超接口(Super-interface)中聲明的方法必須滿足下列對(duì)遠(yuǎn)程方法的要求:

            • 遠(yuǎn)程方法必須聲明拋出 java.rmi.RemoteException 異常,或者該異常的超類(Superclass),比如 java.io.IOException 或者 java.lang.Exception 異常。在此基礎(chǔ)上,遠(yuǎn)程方法可以聲明拋出應(yīng)用特定的其它異常。

            • 在遠(yuǎn)程方法聲明中,作為參數(shù)或者返回值的遠(yuǎn)程對(duì)象,或者包含在其它非遠(yuǎn)程對(duì)象中的遠(yuǎn)程對(duì)象,必須聲明為其對(duì)應(yīng)的遠(yuǎn)程接口,而不是實(shí)際的實(shí)現(xiàn)類。

          注意: 在 Java 1.2 之前,上面關(guān)于拋出異常的要求更嚴(yán)格,即必須拋出 java.rmi.RemoteExcption,不允許類似 java.io.IOException 這樣的超類。現(xiàn)在之所以放寬了這一要求,是希望可以使定義既可以用于遠(yuǎn)程對(duì)象,也可以用于本地對(duì)象的接口變得容易一些(想想 EJB 中的本地接口和遠(yuǎn)程接口)。當(dāng)然,這并沒有使問題好多少,你還是必須聲明異常。不過,一種觀點(diǎn)認(rèn)為這不是問題,強(qiáng)制聲明異常可以使開發(fā)人員保持清醒的頭腦,因?yàn)檫h(yuǎn)程對(duì)象和本地對(duì)象在調(diào)用時(shí)傳參的語意是不同的。本地對(duì)象是傳引用,而遠(yuǎn)程對(duì)象主要是傳值,這意味對(duì)參數(shù)內(nèi)部狀態(tài)的修改產(chǎn)生的結(jié)果是不同的。

          對(duì)于第一個(gè)要求,java.rmi.Remote 接口實(shí)際上沒有任何方法,而只是用作標(biāo)記接口。RMI 的運(yùn)行環(huán)境依賴該接口判斷對(duì)象是否是遠(yuǎn)程對(duì)象。第二個(gè)要求則是因?yàn)榉植际綉?yīng)用可能發(fā)生任何問題,比如網(wǎng)絡(luò)問題等等。

          例 1 列出了我們的遠(yuǎn)程接口定義。該接口只有一個(gè)方法:executeTask() 用以執(zhí)行指定的計(jì)算任務(wù),并返回相應(yīng)的結(jié)果。注意,我們用后綴 Remote 表明接口是遠(yuǎn)程接口。

          例 1. ComputeEngineRemote 遠(yuǎn)程接口

          package rmitutorial;
          
          import java.rmi.Remote;
          import java.rmi.RemoteException;
          
          public interface ComputeEngineRemote extends Remote {
              public Object executeTask(Task task) throws RemoteException;    
          }

          例 2 列出了計(jì)算任務(wù)接口的定義。該接口也只有一個(gè)方法:execute() 用以執(zhí)行實(shí)際的計(jì)算邏輯,并返回結(jié)果。注意,該接口不是遠(yuǎn)程接口,所以沒有擴(kuò)展 java.rmi.Remote 接口;其方法也不必拋出 java.rmi.RemoteException 異常。但是,因?yàn)樗鼘⒂米鬟h(yuǎn)程方法的參數(shù),所以擴(kuò)展了 java.io.Serializable 接口。

          例 2. Task 接口

          package rmitutorial;
          
          import java.io.Serializable;
          
          public interface Task extends Serializable {
              Object execute();
          }

          7. 實(shí)現(xiàn)遠(yuǎn)程接口

          接下來,我們將實(shí)現(xiàn)前面定義的遠(yuǎn)程接口。例 3給出了實(shí)現(xiàn)的源代碼。

          例 3. ComputeEngine 實(shí)現(xiàn)

          package rmitutorial;
          
          import java.rmi.RemoteException;
          import java.rmi.server.UnicastRemoteObject;
          
          public class ComputeEngine extends UnicastRemoteObject 
                  implements ComputeEngineRemote {
              
              public ComputeEngine() throws RemoteException {
                  super();
              }
              
              public Object executeTask(Task task) throws RemoteException {
                  return task.execute();
              }
          }

          ComputeEngine 實(shí)現(xiàn)了之前定義的遠(yuǎn)程接口,同時(shí)繼承自 java.rmi.server.UnicastRemoteObject 超類。UnicastRemoteObject 類是一個(gè)便捷類,它實(shí)現(xiàn)了我們前面所講的基于 TCP/IP 的點(diǎn)對(duì)點(diǎn)通訊機(jī)制。遠(yuǎn)程對(duì)象都必須從該類擴(kuò)展(除非你想自己實(shí)現(xiàn)幾乎所有 UnicastRemoteObject 的方法)。在我們的實(shí)現(xiàn)類的構(gòu)造函數(shù)中,調(diào)用了超類的構(gòu)造函數(shù)(當(dāng)然,即使你不顯式的調(diào)用這個(gè)構(gòu)建函數(shù),它也一樣會(huì)被調(diào)用。這里這樣做,只是為了突出強(qiáng)調(diào)這種調(diào)用而已)。該構(gòu)造函數(shù)的最重要的意義就是調(diào)用 UnicastRemoteObject 類的 exportObject() 方法。導(dǎo)出(Export)對(duì)象是指使遠(yuǎn)程對(duì)象準(zhǔn)備就緒,可以接受進(jìn)來的調(diào)用的過程。而這個(gè)過程的最重要內(nèi)容就是建立服務(wù)器套接字,監(jiān)聽特定的端口,等待客戶端的調(diào)用請(qǐng)求。

          8. 引導(dǎo)程序

          為了讓客戶程序可以找到我們的遠(yuǎn)程對(duì)象,就需要將我們的遠(yuǎn)程對(duì)象注冊(cè)到 RMI 的注冊(cè)表。這個(gè)過程有時(shí)被稱為"引導(dǎo)"過程(Bootstrap)。我們將為此編寫一個(gè)獨(dú)立的引導(dǎo)程序負(fù)責(zé)創(chuàng)建和注冊(cè)遠(yuǎn)程對(duì)象。例 4 給出了引導(dǎo)程序的源代碼。

          例 4. 引導(dǎo)程序

          package rmitutorial;
          
          import java.rmi.Naming;
          import java.rmi.RMISecurityManager;
          
          public class Bootstrap {
              
              public static void main(String[] args) throws Exception {
                  String name = "ComputeEngine";
                  
                  ComputeEngine engine = new ComputeEngine();
                  System.out.println("ComputerEngine exported");
                  
                  Naming.rebind(name, engine);
                  System.out.println("ComputeEngine bound");
              }
          }
          

          可以看到,我們首先創(chuàng)建了一個(gè)遠(yuǎn)程對(duì)象(同時(shí)導(dǎo)出了該對(duì)象),之后將該對(duì)象綁定到 RMI 注冊(cè)表中。Namingrebind() 方法接受一個(gè) URL 形式的名字作綁定之用。其完整格式如下:

          protocol://host:port/object

          其中,協(xié)議(Protocol)默認(rèn)為 rmi;主機(jī)名默認(rèn)為 localhost;端口默認(rèn)為 1099。注意,JDK 中提供的默認(rèn) Naming 實(shí)現(xiàn)只支持 rmi 協(xié)議。在我們的引導(dǎo)程序里面只給出了對(duì)象綁定的名字,而其它部分均使用缺省值。

          9. 客戶端程序

          例 5 給出了我們的客戶端程序。該程序接受兩個(gè)參數(shù),分別是遠(yuǎn)程對(duì)象所在的主機(jī)地址和希望獲得的 π 值的精度。

          例 5. Client.java

          package rmitutorial;
          
          import java.math.BigDecimal;
          import java.rmi.Naming;
          
          public class Client {
              public static void main(String args[]) throws Exception {
                      String name = "rmi://" + args[0] + "/ComputeEngine";
                      ComputeEngineRemote engineRemote = 
                              (ComputeEngineRemote)Naming.lookup(name);
                      Pi task = new Pi(Integer.parseInt(args[1]));
                      BigDecimal pi = (BigDecimal)(engineRemote.executeTask(task));
                      System.out.println(pi);
              }
          }

          例 6. Pi.java

          package rmitutorial;
          
          import java.math.*;
          
          public class Pi implements Task {
              
              private static final BigDecimal ZERO =
                      BigDecimal.valueOf(0);
              private static final BigDecimal  ONE =
                      BigDecimal.valueOf(1);
              private static final BigDecimal FOUR =
                      BigDecimal.valueOf(4);
              
              private static final int roundingMode =
                      BigDecimal.ROUND_HALF_EVEN;
              
              private int digits;
              
              public Pi(int digits) {
                  this.digits = digits;
              }
              
              public Object execute() {
                  return computePi(digits);
              }
              
              public static BigDecimal computePi(int digits) {
                  int scale = digits + 5;
                  BigDecimal arctan1_5 = arctan(5, scale);
                  BigDecimal arctan1_239 = arctan(239, scale);
                  BigDecimal pi = arctan1_5.multiply(FOUR).subtract(
                          arctan1_239).multiply(FOUR);
                  return pi.setScale(digits,
                          BigDecimal.ROUND_HALF_UP);
              }
          
              public static BigDecimal arctan(int inverseX,
                      int scale) {
                  BigDecimal result, numer, term;
                  BigDecimal invX = BigDecimal.valueOf(inverseX);
                  BigDecimal invX2 =
                          BigDecimal.valueOf(inverseX * inverseX);
                  
                  numer = ONE.divide(invX, scale, roundingMode);
                  
                  result = numer;
                  int i = 1;
                  do {
                      numer =
                              numer.divide(invX2, scale, roundingMode);
                      int denom = 2 * i + 1;
                      term =
                              numer.divide(BigDecimal.valueOf(denom),
                              scale, roundingMode);
                      if ((i % 2) != 0) {
                          result = result.subtract(term);
                      } else {
                          result = result.add(term);
                      }
                      i++;
                  } while (term.compareTo(ZERO) != 0);
                  return result;
              }
          }

          10. 編譯示例程序

          編譯我們的示例程序和編譯其它非分布式的應(yīng)用沒什么區(qū)別。只是編譯之后,需要使用 RMI 編譯器,即 rmic 生成所需 Stub 和 Skeleton 實(shí)現(xiàn)。使用 rmic 的方式是將我們的遠(yuǎn)程對(duì)象的實(shí)現(xiàn)類(不是遠(yuǎn)程接口)的全類名作為參數(shù)來運(yùn)行 rmic 命令。參考下面的示例:

          E:\classes\rmic rmitutorial.ComputeEngine

          編譯之后將生成 rmitutorial.ComputeEngine_Skelrmitutorial.ComputeEngine_Stub 兩個(gè)類。

          11. 運(yùn)行示例程序

          遠(yuǎn)程對(duì)象的引用通常是通過 RMI 的注冊(cè)表服務(wù)以及 java.rmi.Naming 接口獲得的。遠(yuǎn)程對(duì)象需要導(dǎo)出(注冊(cè))相應(yīng)的遠(yuǎn)程引用到注冊(cè)表服務(wù),之后注冊(cè)表服務(wù)就可以監(jiān)聽并服務(wù)于客戶端對(duì)遠(yuǎn)程對(duì)象引用的請(qǐng)求。標(biāo)準(zhǔn)的 Sun Java SDK 提供了一個(gè)簡單的 RMI 注冊(cè)表服務(wù)程序,即 rmiregistry 用于監(jiān)聽特定的端口,等待遠(yuǎn)程對(duì)象的注冊(cè),以及客戶端對(duì)這些遠(yuǎn)程對(duì)象引用的檢索請(qǐng)求。

          在運(yùn)行我們的示例程序之前,首先要啟動(dòng) RMI 的注冊(cè)表服務(wù)。這個(gè)過程很簡單,只要直接運(yùn)行 rmiregistry 命令即可。缺省的情況下,該服務(wù)將監(jiān)聽 1099 端口。如果需要指定其它的監(jiān)聽端口,可以在命令行指定希望監(jiān)聽的端口(如果你指定了其它端口,需要修改示例程序以適應(yīng)環(huán)境)。如果希望該程序在后臺(tái)運(yùn)行,在 Unix 上可以以如下方式運(yùn)行(當(dāng)然,可以缺省端口參數(shù)):

          $ rmiregistry 1099 &

          在 Windows 操作系統(tǒng)中可以這樣運(yùn)行:

          C:\> start rmiregistry 1099

          我們的 rmitutorial.Bootstrap 類將用于啟動(dòng)遠(yuǎn)程對(duì)象,并將其綁定在 RMI 注冊(cè)表中。運(yùn)行該類后,遠(yuǎn)程對(duì)象也將進(jìn)入監(jiān)聽狀態(tài),等待來自客戶端的方法調(diào)用請(qǐng)求。

          $ java rmitutorial.Bootstrap
          ComputeEngine exported
          ComputeEngine bound

          啟動(dòng)遠(yuǎn)程對(duì)象后,打開另一個(gè)命令行窗口,運(yùn)行客戶端。命令行的第一個(gè)參數(shù)為 RMI 注冊(cè)表的地址,第二個(gè)參數(shù)為期望的 π 值精度。參考下面的示例:

          $ java rmitutorial.Client localhost 50
          3.14159265358979323846264338327950288419716939937511

          12. 其它信息

          在演示示例程序時(shí),我們實(shí)際上是在同一主機(jī)上運(yùn)行的服務(wù)器和客戶端,并且無論是服務(wù)器和客戶端所需的類都在相同的類路徑上,可以同時(shí)被服務(wù)器和客戶端所訪問。這忽略了 Java RMI 的一個(gè)重要細(xì)節(jié),即動(dòng)態(tài)類裝載。因?yàn)?RMI 的特性(包括其它幾個(gè)特性)并不適用于 J2EE 的 RMI-IIOP 和 EJB 技術(shù),所以,本文將不作詳細(xì)介紹,請(qǐng)讀者自行參考本文給出的參考資料。不過,為了讓好奇的讀者不至于過分失望,這里簡單介紹一下動(dòng)態(tài)類裝載的基本思想。

          RMI 運(yùn)行時(shí)系統(tǒng)采用動(dòng)態(tài)類裝載機(jī)制來裝載分布式應(yīng)用所需的類。如果你可以直接訪問應(yīng)用所涉及的所有包括服務(wù)器端客戶端在內(nèi)的主機(jī),并且可以把分布式應(yīng)用所需的所有類都安裝在每個(gè)主機(jī)的 CLASSPATH 中(上面的示例就是極端情況,所有的東西都在本地主機(jī)),那么你完全不必關(guān)心 RMI 類裝載的細(xì)節(jié)。顯然,既然是分布式應(yīng)用,情況往往正相反。對(duì)于 RMI 應(yīng)用,客戶端需要裝載客戶端自身所需的類,將要調(diào)用的遠(yuǎn)程對(duì)象的遠(yuǎn)程接口類以及對(duì)應(yīng)的 Stub 類;服務(wù)器端則要裝載遠(yuǎn)程對(duì)象的實(shí)現(xiàn)類以及對(duì)應(yīng)的 Skeleton 類(Java 1.2 之后不需要 Skeleton 類)。RMI 在處理遠(yuǎn)程調(diào)用涉及的遠(yuǎn)程引用,參數(shù)以及返回值時(shí),可以將一個(gè)指定的 URL 編碼到流中。交互的另一端可以通過 該 URL 獲得處理這些對(duì)象所需的類文件。這一點(diǎn)類似于 Applet 中的 CODEBASE 的概念,交互的兩端通過 HTTP 服務(wù)器發(fā)布各自控制的類,允許交互的另一端動(dòng)態(tài)下載這些類。以我們的示例為例,客戶端不必部署 ComputeEngine_Stub 的類文件,而可以通過服務(wù)器端的 HTTP 服務(wù)器獲得類文件。同樣,服務(wù)器端也不需要客戶端實(shí)現(xiàn)的定制任務(wù) Pi 的類文件。

          注意,這種動(dòng)態(tài)類裝載將需要交互的兩端加載定制的安全管理器(參見 java.rmi.RMISecurityManager API),以及對(duì)應(yīng)的策略文件。

          13. 參考資料

          • The Java? Tutorial Trail:RMI

          • David Flanagan, Jim Farley, William Crawford and Kris Magnusson, 1999, ISBN 1-56592-483-5E, O'Reilly, Java? Enterprise in a Nutshell

          • Ed Roman, Scott Ambler and Tyler Jewell 2002, ISBN 0-471-41711-4, John Wiley &Sons, Inc., Matering Enterprise JavaBeans? , Second Edition

          posted @ 2006-10-11 09:51 boddi 閱讀(2123) | 評(píng)論 (1)編輯 收藏

          正則表達(dá)式(轉(zhuǎn)載)

          關(guān)鍵詞正則表達(dá)式 ?? 模式匹配 ?? Javascript ?? ??????????????????????????????????????

          關(guān)鍵字:正則表達(dá)式 ?模式匹配 Javascript

          摘要:收集一些常用的正則表達(dá)式。

          正則表達(dá)式用于字符串處理,表單驗(yàn)證等場合,實(shí)用高效,但用到時(shí)總是不太把握,以致往往要上網(wǎng)查一番。我將一些常用的表達(dá)式收藏在這里,作備忘之用。本貼隨時(shí)會(huì)更新。

          匹配中文字符的正則表達(dá)式: [\u4e00-\u9fa5]

          匹配雙字節(jié)字符(包括漢字在內(nèi)):[^\x00-\xff]

          應(yīng)用:計(jì)算字符串的長度(一個(gè)雙字節(jié)字符長度計(jì)2,ASCII字符計(jì)1)

          String.prototype.len=function(){return this.replace([^\x00-\xff]/g,"aa").length;}

          匹配空行的正則表達(dá)式:\n[\s| ]*\r

          匹配HTML標(biāo)記的正則表達(dá)式:/<(.*)>.*<\/\1>|<(.*) \/>/

          匹配首尾空格的正則表達(dá)式:(^\s*)|(\s*$)

          String.prototype.trim = function()
          {
          ??? return this.replace(/(^\s*)|(\s*$)/g, "");
          }

          利用正則表達(dá)式分解和轉(zhuǎn)換IP地址:

          下面是利用正則表達(dá)式匹配IP地址,并將IP地址轉(zhuǎn)換成對(duì)應(yīng)數(shù)值的Javascript程序:

          function IP2V(ip)
          {
          ?re=/(\d+)\.(\d+)\.(\d+)\.(\d+)/g? //匹配IP地址的正則表達(dá)式
          if(re.test(ip))
          {
          return RegExp.$1*Math.pow(255,3))+RegExp.$2*Math.pow(255,2))+RegExp.$3*255+RegExp.$4*1
          }
          else
          {
          ?throw new Error("Not a valid IP address!")
          }
          }

          不過上面的程序如果不用正則表達(dá)式,而直接用split函數(shù)來分解可能更簡單,程序如下:

          var ip="10.100.20.168"
          ip=ip.split(".")
          alert("IP值是:"+(ip[0]*255*255*255+ip[1]*255*255+ip[2]*255+ip[3]*1))

          匹配Email地址的正則表達(dá)式:\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*

          匹配網(wǎng)址URL的正則表達(dá)式:http://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?

          利用正則表達(dá)式去除字串中重復(fù)的字符的算法程序:[注:此程序不正確,原因見本貼回復(fù)]

          var s="abacabefgeeii"
          var s1=s.replace(/(.).*\1/g,"$1")
          var re=new RegExp("["+s1+"]","g")
          var s2=s.replace(re,"")
          alert(s1+s2)? //結(jié)果為:abcefgi

          我原來在CSDN上發(fā)貼尋求一個(gè)表達(dá)式來實(shí)現(xiàn)去除重復(fù)字符的方法,最終沒有找到,這是我能想到的最簡單的實(shí)現(xiàn)方法。思路是使用后向引用取出包括重復(fù)的字符,再以重復(fù)的字符建立第二個(gè)表達(dá)式,取到不重復(fù)的字符,兩者串連。這個(gè)方法對(duì)于字符順序有要求的字符串可能不適用。

          得用正則表達(dá)式從URL地址中提取文件名的javascript程序,如下結(jié)果為page1

          s="http://www.9499.net/page1.htm"
          s=s.replace(/(.*\/){0,}([^\.]+).*/ig,"$2")
          alert(s)

          利用正則表達(dá)式限制網(wǎng)頁表單里的文本框輸入內(nèi)容:

          用正則表達(dá)式限制只能輸入中文:onkeyup="value=value.replace(/[^\u4E00-\u9FA5]/g,'')" onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\u4E00-\u9FA5]/g,''))"

          用正則表達(dá)式限制只能輸入全角字符:?onkeyup="value=value.replace(/[^\uFF00-\uFFFF]/g,'')" onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\uFF00-\uFFFF]/g,''))"

          用正則表達(dá)式限制只能輸入數(shù)字:onkeyup="value=value.replace(/[^\d]/g,'') "onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\d]/g,''))"

          用正則表達(dá)式限制只能輸入數(shù)字和英文:onkeyup="value=value.replace(/[\W]/g,'') "onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\d]/g,''))"

          posted @ 2006-09-13 16:28 boddi 閱讀(365) | 評(píng)論 (0)編輯 收藏

               摘要: javascript小技巧 事件源對(duì)象 event.srcElement.tagName event.srcElement.type 捕獲釋放 event.srcElement.setCapture();? event...  閱讀全文

          posted @ 2006-09-11 13:35 boddi 閱讀(792) | 評(píng)論 (0)編輯 收藏



          Java Excel是一開放源碼項(xiàng)目,通過它Java開發(fā)人員可以讀取Excel文件的內(nèi)容、創(chuàng)建新的Excel文件、更新已經(jīng)存在的Excel文件。使用該API非Windows操作系統(tǒng)也可以通過純Java應(yīng)用來處理Excel數(shù)據(jù)表。因?yàn)槭鞘褂肑ava編寫的,所以我們?cè)赪eb應(yīng)用中可以通過JSP、Servlet來調(diào)用API實(shí)現(xiàn)對(duì)Excel數(shù)據(jù)表的訪問。

          現(xiàn)在發(fā)布的穩(wěn)定版本是V2.0,提供以下功能:

          從Excel 95、97、2000等格式的文件中讀取數(shù)據(jù);
          讀取Excel公式(可以讀取Excel 97以后的公式);
          生成Excel數(shù)據(jù)表(格式為Excel 97);
          支持字體、數(shù)字、日期的格式化;
          支持單元格的陰影操作,以及顏色操作;
          修改已經(jīng)存在的數(shù)據(jù)表;
          現(xiàn)在還不支持以下功能,但不久就會(huì)提供了:

          不能夠讀取圖表信息;
          可以讀,但是不能生成公式,任何類型公式最后的計(jì)算值都可以讀出;
          應(yīng)用示例

          1 從Excel文件讀取數(shù)據(jù)表

          Java Excel API既可以從本地文件系統(tǒng)的一個(gè)文件(.xls),也可以從輸入流中讀取Excel數(shù)據(jù)表。讀取Excel數(shù)據(jù)表的第一步是創(chuàng)建Workbook(術(shù)語:工作薄),下面的代碼片段舉例說明了應(yīng)該如何操作:(完整代碼見ExcelReading.java)


          import java.io.*;
          import jxl.*;
          … … … …
          try
          {
          //構(gòu)建Workbook對(duì)象, 只讀Workbook對(duì)象
          //直接從本地文件創(chuàng)建Workbook
          //從輸入流創(chuàng)建Workbook
          ?? InputStream is = new FileInputStream(sourcefile);
          ?? jxl.Workbook rwb = Workbook.getWorkbook(is);
          }
          catch (Exception e)
          {
          e.printStackTrace();
          }



          一旦創(chuàng)建了Workbook,我們就可以通過它來訪問Excel Sheet(術(shù)語:工作表)。參考下面的代碼片段:


          //獲取第一張Sheet表
          Sheet rs = rwb.getSheet(0);



          我們既可能通過Sheet的名稱來訪問它,也可以通過下標(biāo)來訪問它。如果通過下標(biāo)來訪問的話,要注意的一點(diǎn)是下標(biāo)從0開始,就像數(shù)組一樣。

          一旦得到了Sheet,我們就可以通過它來訪問Excel Cell(術(shù)語:單元格)。參考下面的代碼片段:


          //獲取第一行,第一列的值
          Cell c00 = rs.getCell(0, 0);
          String strc00 = c00.getContents();

          //獲取第一行,第二列的值
          Cell c10 = rs.getCell(1, 0);
          String strc10 = c10.getContents();

          //獲取第二行,第二列的值
          Cell c11 = rs.getCell(1, 1);
          String strc11 = c11.getContents();

          System.out.println("Cell(0, 0)" + " value : " + strc00 + "; type : " + c00.getType());
          System.out.println("Cell(1, 0)" + " value : " + strc10 + "; type : " + c10.getType());
          System.out.println("Cell(1, 1)" + " value : " + strc11 + "; type : " + c11.getType());



          如果僅僅是取得Cell的值,我們可以方便地通過getContents()方法,它可以將任何類型的Cell值都作為一個(gè)字符串返回。示例代碼中Cell(0, 0)是文本型,Cell(1, 0)是數(shù)字型,Cell(1,1)是日期型,通過getContents(),三種類型的返回值都是字符型。

          如果有需要知道Cell內(nèi)容的確切類型,API也提供了一系列的方法。參考下面的代碼片段:


          String strc00 = null;
          double strc10 = 0.00;
          Date strc11 = null;

          Cell c00 = rs.getCell(0, 0);
          Cell c10 = rs.getCell(1, 0);
          Cell c11 = rs.getCell(1, 1);

          if(c00.getType() == CellType.LABEL)
          {
          LabelCell labelc00 = (LabelCell)c00;
          strc00 = labelc00.getString();
          }
          if(c10.getType() == CellType.NUMBER)
          {
          NmberCell numc10 = (NumberCell)c10;
          strc10 = numc10.getValue();
          }
          if(c11.getType() == CellType.DATE)
          {
          DateCell datec11 = (DateCell)c11;
          strc11 = datec11.getDate();
          }

          System.out.println("Cell(0, 0)" + " value : " + strc00 + "; type : " + c00.getType());
          System.out.println("Cell(1, 0)" + " value : " + strc10 + "; type : " + c10.getType());
          System.out.println("Cell(1, 1)" + " value : " + strc11 + "; type : " + c11.getType());



          在得到Cell對(duì)象后,通過getType()方法可以獲得該單元格的類型,然后與API提供的基本類型相匹配,強(qiáng)制轉(zhuǎn)換成相應(yīng)的類型,最后調(diào)用相應(yīng)的取值方法getXXX(),就可以得到確定類型的值。API提供了以下基本類型,與Excel的數(shù)據(jù)格式相對(duì)應(yīng),如下圖所示:





          每種類型的具體意義,請(qǐng)參見Java Excel API Document。

          當(dāng)你完成對(duì)Excel電子表格數(shù)據(jù)的處理后,一定要使用close()方法來關(guān)閉先前創(chuàng)建的對(duì)象,以釋放讀取數(shù)據(jù)表的過程中所占用的內(nèi)存空間,在讀取大量數(shù)據(jù)時(shí)顯得尤為重要。參考如下代碼片段:


          //操作完成時(shí),關(guān)閉對(duì)象,釋放占用的內(nèi)存空間
          rwb.close();



          Java Excel API提供了許多訪問Excel數(shù)據(jù)表的方法,在這里我只簡要地介紹幾個(gè)常用的方法,其它的方法請(qǐng)參考附錄中的Java Excel API Document。

          Workbook類提供的方法

          1. int getNumberOfSheets()
          獲得工作薄(Workbook)中工作表(Sheet)的個(gè)數(shù),示例:


          jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
          int sheets = rwb.getNumberOfSheets();



          2. Sheet[] getSheets()
          返回工作薄(Workbook)中工作表(Sheet)對(duì)象數(shù)組,示例:


          jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
          Sheet[] sheets = rwb.getSheets();



          3. String getVersion()
          返回正在使用的API的版本號(hào),好像是沒什么太大的作用。


          jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
          String apiVersion = rwb.getVersion();



          Sheet接口提供的方法

          1) String getName()
          獲取Sheet的名稱,示例:


          jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
          jxl.Sheet rs = rwb.getSheet(0);
          String sheetName = rs.getName();



          2) int getColumns()
          獲取Sheet表中所包含的總列數(shù),示例:


          jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
          jxl.Sheet rs = rwb.getSheet(0);
          int rsColumns = rs.getColumns();



          3) Cell[] getColumn(int column)
          獲取某一列的所有單元格,返回的是單元格對(duì)象數(shù)組,示例:


          jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
          jxl.Sheet rs = rwb.getSheet(0);
          Cell[] cell = rs.getColumn(0);



          4) int getRows()
          獲取Sheet表中所包含的總行數(shù),示例:


          jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
          jxl.Sheet rs = rwb.getSheet(0);
          int rsRows = rs.getRows();



          5) Cell[] getRow(int row)
          獲取某一行的所有單元格,返回的是單元格對(duì)象數(shù)組,示例子:


          jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
          jxl.Sheet rs = rwb.getSheet(0);
          Cell[] cell = rs.getRow(0);



          6) Cell getCell(int column, int row)
          獲取指定單元格的對(duì)象引用,需要注意的是它的兩個(gè)參數(shù),第一個(gè)是列數(shù),第二個(gè)是行數(shù),這與通常的行、列組合有些不同。


          jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
          jxl.Sheet rs = rwb.getSheet(0);
          Cell cell = rs.getCell(0, 0);



          2 生成新的Excel工作薄

          下面的代碼主要是向大家介紹如何生成簡單的Excel工作表,在這里單元格的內(nèi)容是不帶任何修飾的(如:字體,顏色等等),所有的內(nèi)容都作為字符串寫入。(完整代碼見ExcelWriting.java)

          與讀取Excel工作表相似,首先要使用Workbook類的工廠方法創(chuàng)建一個(gè)可寫入的工作薄(Workbook)對(duì)象,這里要注意的是,只能通過API提供的工廠方法來創(chuàng)建Workbook,而不能使用WritableWorkbook的構(gòu)造函數(shù),因?yàn)轭怶ritableWorkbook的構(gòu)造函數(shù)為protected類型。示例代碼片段如下:


          import java.io.*;
          import jxl.*;
          import jxl.write.*;
          … … … …
          try
          {
          //構(gòu)建Workbook對(duì)象, 只讀Workbook對(duì)象
          //Method 1:創(chuàng)建可寫入的Excel工作薄
          ?? jxl.write.WritableWorkbook wwb = Workbook.createWorkbook(new File(targetfile));

          //Method 2:將WritableWorkbook直接寫入到輸出流
          /*
          ?? OutputStream os = new FileOutputStream(targetfile);
          ?? jxl.write.WritableWorkbook wwb = Workbook.createWorkbook(os);
          */
          }
          catch (Exception e)
          {
          e.printStackTrace();
          }



          API提供了兩種方式來處理可寫入的輸出流,一種是直接生成本地文件,如果文件名不帶全路徑的話,缺省的文件會(huì)定位在當(dāng)前目錄,如果文件名帶有全路徑的話,則生成的Excel文件則會(huì)定位在相應(yīng)的目錄;另外一種是將Excel對(duì)象直接寫入到輸出流,例如:用戶通過瀏覽器來訪問Web服務(wù)器,如果HTTP頭設(shè)置正確的話,瀏覽器自動(dòng)調(diào)用客戶端的Excel應(yīng)用程序,來顯示動(dòng)態(tài)生成的Excel電子表格。

          接下來就是要?jiǎng)?chuàng)建工作表,創(chuàng)建工作表的方法與創(chuàng)建工作薄的方法幾乎一樣,同樣是通過工廠模式方法獲得相應(yīng)的對(duì)象,該方法需要兩個(gè)參數(shù),一個(gè)是工作表的名稱,另一個(gè)是工作表在工作薄中的位置,參考下面的代碼片段:


          //創(chuàng)建Excel工作表
          jxl.write.WritableSheet ws = wwb.createSheet("Test Sheet 1", 0);



          "這鍋也支好了,材料也準(zhǔn)備齊全了,可以開始下鍋了!",現(xiàn)在要做的只是實(shí)例化API所提供的Excel基本數(shù)據(jù)類型,并將它們添加到工作表中就可以了,參考下面的代碼片段:


          //1.添加Label對(duì)象
          jxl.write.Label labelC = new jxl.write.Label(0, 0, "This is a Label cell");
          ws.addCell(labelC);

          //添加帶有字型Formatting的對(duì)象
          jxl.write.WritableFont wf = new jxl.write.WritableFont(WritableFont.TIMES, 18, WritableFont.BOLD, true);
          jxl.write.WritableCellFormat wcfF = new jxl.write.WritableCellFormat(wf);
          jxl.write.Label labelCF = new jxl.write.Label(1, 0, "This is a Label Cell", wcfF);
          ws.addCell(labelCF);

          //添加帶有字體顏色Formatting的對(duì)象
          jxl.write.WritableFont wfc = new jxl.write.WritableFont(WritableFont.ARIAL, 10, WritableFont.NO_BOLD, false,
          Underlinestyle.NO_UNDERLINE, jxl.format.Colour.RED);
          jxl.write.WritableCellFormat wcfFC = new jxl.write.WritableCellFormat(wfc);
          jxl.write.Label labelCFC = new jxl.write.Label(1, 0, "This is a Label Cell", wcfFC);
          ws.addCell(labelCF);

          //2.添加Number對(duì)象
          jxl.write.Number labelN = new jxl.write.Number(0, 1, 3.1415926);
          ws.addCell(labelN);

          //添加帶有formatting的Number對(duì)象
          jxl.write.NumberFormat nf = new jxl.write.NumberFormat("#.##");
          jxl.write.WritableCellFormat wcfN = new jxl.write.WritableCellFormat(nf);
          jxl.write.Number labelNF = new jxl.write.Number(1, 1, 3.1415926, wcfN);
          ws.addCell(labelNF);

          //3.添加Boolean對(duì)象
          jxl.write.Boolean labelB = new jxl.write.Boolean(0, 2, false);
          ws.addCell(labelB);

          //4.添加DateTime對(duì)象
          jxl.write.DateTime labelDT = new jxl.write.DateTime(0, 3, new java.util.Date());
          ws.addCell(labelDT);

          //添加帶有formatting的DateFormat對(duì)象
          jxl.write.DateFormat df = new jxl.write.DateFormat("dd MM yyyy hh:mm:ss");
          jxl.write.WritableCellFormat wcfDF = new jxl.write.WritableCellFormat(df);
          jxl.write.DateTime labelDTF = new jxl.write.DateTime(1, 3, new java.util.Date(), wcfDF);
          ws.addCell(labelDTF);



          這里有兩點(diǎn)大家要引起大家的注意。第一點(diǎn),在構(gòu)造單元格時(shí),單元格在工作表中的位置就已經(jīng)確定了。一旦創(chuàng)建后,單元格的位置是不能夠變更的,盡管單元格的內(nèi)容是可以改變的。第二點(diǎn),單元格的定位是按照下面這樣的規(guī)律(column, row),而且下標(biāo)都是從0開始,例如,A1被存儲(chǔ)在(0, 0),B1被存儲(chǔ)在(1, 0)。

          最后,不要忘記關(guān)閉打開的Excel工作薄對(duì)象,以釋放占用的內(nèi)存,參見下面的代碼片段:


          //寫入Exel工作表
          wwb.write();

          //關(guān)閉Excel工作薄對(duì)象
          wwb.close();



          這可能與讀取Excel文件的操作有少少不同,在關(guān)閉Excel對(duì)象之前,你必須要先調(diào)用write()方法,因?yàn)橄惹暗牟僮鞫际谴鎯?chǔ)在緩存中的,所以要通過該方法將操作的內(nèi)容保存在文件中。如果你先關(guān)閉了Excel對(duì)象,那么只能得到一張空的工作薄了。

          3 拷貝、更新Excel工作薄

          接下來簡要介紹一下如何更新一個(gè)已經(jīng)存在的工作薄,主要是下面二步操作,第一步是構(gòu)造只讀的Excel工作薄,第二步是利用已經(jīng)創(chuàng)建的Excel工作薄創(chuàng)建新的可寫入的Excel工作薄,參考下面的代碼片段:(完整代碼見ExcelModifying.java)


          //創(chuàng)建只讀的Excel工作薄的對(duì)象
          jxl.Workbook rw = jxl.Workbook.getWorkbook(new File(sourcefile));

          //創(chuàng)建可寫入的Excel工作薄對(duì)象
          jxl.write.WritableWorkbook??wwb = Workbook.createWorkbook(new File(targetfile), rw);
          ??????????
          //讀取第一張工作表
          jxl.write.WritableSheet ws = wwb.getSheet(0);

          //獲得第一個(gè)單元格對(duì)象
          jxl.write.WritableCell wc = ws.getWritableCell(0, 0);
          ??????????
          //判斷單元格的類型, 做出相應(yīng)的轉(zhuǎn)化
          if(wc.getType() == CellType.LABEL)
          {
          Label l = (Label)wc;
          ?? l.setString("The value has been modified.");
          }

          //寫入Excel對(duì)象
          wwb.write();

          //關(guān)閉可寫入的Excel對(duì)象
          wwb.close();

          //關(guān)閉只讀的Excel對(duì)象
          rw.close();



          之所以使用這種方式構(gòu)建Excel對(duì)象,完全是因?yàn)樾实脑颍驗(yàn)樯厦娴氖纠攀茿PI的主要應(yīng)用。為了提高性能,在讀取工作表時(shí),與數(shù)據(jù)相關(guān)的一些輸出信息,所有的格式信息,如:字體、顏色等等,是不被處理的,因?yàn)槲覀兊哪康氖谦@得行數(shù)據(jù)的值,既使沒有了修飾,也不會(huì)對(duì)行數(shù)據(jù)的值產(chǎn)生什么影響。唯一的不利之處就是,在內(nèi)存中會(huì)同時(shí)保存兩個(gè)同樣的工作表,這樣當(dāng)工作表體積比較大時(shí),會(huì)占用相當(dāng)大的內(nèi)存,但現(xiàn)在好像內(nèi)存的大小并不是什么關(guān)鍵因素了。

          一旦獲得了可寫入的工作表對(duì)象,我們就可以對(duì)單元格對(duì)象進(jìn)行更新的操作了,在這里我們不必調(diào)用API提供的add()方法,因?yàn)閱卧褚呀?jīng)于工作表當(dāng)中,所以我們只需要調(diào)用相應(yīng)的setXXX()方法,就可以完成更新的操作了。

          盡單元格原有的格式化修飾是不能去掉的,我們還是可以將新的單元格修飾加上去,以使單元格的內(nèi)容以不同的形式表現(xiàn)。

          新生成的工作表對(duì)象是可寫入的,我們除了更新原有的單元格外,還可以添加新的單元格到工作表中,這與示例2的操作是完全一樣的。

          最后,不要忘記調(diào)用write()方法,將更新的內(nèi)容寫入到文件中,然后關(guān)閉工作薄對(duì)象,這里有兩個(gè)工作薄對(duì)象要關(guān)閉,一個(gè)是只讀的,另外一個(gè)是可寫入的。

          以上摘自IBM網(wǎng)站

          posted @ 2006-09-09 13:14 boddi 閱讀(640) | 評(píng)論 (3)編輯 收藏

          使用JXL讀取Excel表格,拷貝、更新Excel工作薄
          xymiser 原創(chuàng)? (參與分:41669,專家分:2761)?? 發(fā)表:2006-01-18 22:11 ??版本:1.0 ??閱讀:1666

          /**
          *?<p>讀取Excel表格,拷貝、更新Excel工作薄?</p>
          *?<p>Description:?可以讀取Excel文件的內(nèi)容,更新Excel工作薄
          *?</p>
          *?<p>Copyright:?Copyright?(c)?Corparation?2005</p>
          *?<p>程序開發(fā)環(huán)境為eclipse</p>
          *?@author?Walker
          *?@version?1.0
          */
          package?cn.com.yitong.xls;

          import?java.io.File;
          import?java.io.FileInputStream;
          import?java.io.InputStream;
          import?java.util.Vector;

          import?cn.com.yitong.ChartImg;
          import?cn.com.yitong.VireObj;
          import?cn.com.yitong.platform.log.YTLogger;

          import?jxl.CellType;
          import?jxl.Workbook;
          import?jxl.format.CellFormat;
          import?jxl.format.Colour;
          import?jxl.format.UnderlineStyle;
          import?jxl.write.Formula;
          import?jxl.write.Label;
          import?jxl.write.Number;
          import?jxl.write.WritableCell;
          import?jxl.write.WritableCellFormat;
          import?jxl.write.WritableFont;
          import?jxl.write.WritableImage;
          import?jxl.write.WritableSheet;
          import?jxl.write.WritableWorkbook;
          import?jxl.write.WriteException;
          import?jxl.write.biff.RowsExceededException;

          public?class?XLSDemo
          {
          ????private?static?final?int?TITLE_LENGTH?=?7;
          ????private?static?final?int?SHEET_WIDTH?=?32;
          ????private?static?final?int?SHEET_HEIGHT?=?116;
          ????
          ????/**
          ?????*?創(chuàng)建Excel
          ?????*/
          ????private?void?makeXls()
          ????{
          ????????Workbook?workbook?=?null;
          ????????try
          ????????{
          ????????????//?構(gòu)建Workbook對(duì)象,?只讀Workbook對(duì)象
          ????????????//?直接從本地文件創(chuàng)建Workbook,?從輸入流創(chuàng)建Workbook
          ????????????InputStream?ins?=?new?FileInputStream("D:/Workspace/testproj/source.xls");
          ????????????workbook?=?Workbook.getWorkbook(ins);

          ????????????//?利用已經(jīng)創(chuàng)建的Excel工作薄創(chuàng)建新的可寫入的Excel工作薄
          ????????????File?outFile?=?new?File("D:/Workspace/testproj/test.xls");
          ????????????WritableWorkbook?wwb?=?Workbook.createWorkbook(outFile,?workbook);
          ????????????//?讀取第一張工作表
          ????????????WritableSheet?dataSheet?=?wwb.getSheet(0);
          ????????????//??設(shè)置凍結(jié)單元格
          ????????????dataSheet.getSettings().setVerticalFreeze(7);
          ????????????dataSheet.getSettings().setHorizontalFreeze(2);
          ????????????
          ????????????//?測試模擬數(shù)據(jù)
          ????????????Vector?vecData?=?new?Vector();
          ????????????for(int?i?=?0;?i?<?50;?i?++)
          ????????????{
          ????????????????VireObj?obj?=?new?VireObj();
          ????????????????obj.setOrgNo("00"?+?i?+?"0");
          ????????????????obj.setOrgName("機(jī)構(gòu)"?+?(i?+?1));
          ????????????????obj.setOpenAcc((int)(100?*?Math.random()));
          ????????????????obj.setDestoryAcc((int)(10?*?Math.random()));
          ????????????????obj.setTotalAcc((int)(500?*?Math.random()));
          ????????????????obj.setMonthInCount((int)(500?*?Math.random()));
          ????????????????obj.setMonthInMoney(500?*?Math.random());
          ????????????????obj.setMonthOutCount((int)(500?*?Math.random()));
          ????????????????obj.setMonthOutMoney(500?*?Math.random());
          ????????????????
          ????????????????vecData.add(obj);
          ????????????}????????????
          ????????????//?插入數(shù)據(jù)
          ????????????insertData(wwb,?dataSheet,?vecData);????????????
          ????????????//?插入模擬圖像數(shù)據(jù)
          ????????????Vector?vecImg?=?new?Vector();
          ????????????for(int?i?=?0;?i?<?3;?i?++)
          ????????????{
          ????????????????ChartImg?img?=?new?ChartImg();
          ????????????????img.setImgTitle("圖像"?+?(i?+?1));
          ????????????????img.setImgName("D:/Workspace/testproj/images/barchart.png");
          ????????????????vecImg.add(img);
          ????????????}
          ????????????//?插入圖表
          ????????????insertImgsheet(wwb,?vecImg);
          ????????????//寫入Excel對(duì)象
          ????????????wwb.write();
          ????????????wwb.close();
          ????????}?catch?(Exception?e)
          ????????{
          ????????????YTLogger.logDebug(e);
          ????????}?finally
          ????????{
          ????????????//?操作完成時(shí),關(guān)閉對(duì)象,釋放占用的內(nèi)存空間
          ????????????workbook.close();
          ????????}
          ????}
          ????
          ????/**
          ?????*?插入數(shù)據(jù)
          ?????*?@param?wwb?WritableWorkbook?:?工作簿
          ?????*?@param?dataSheet?WritableSheet?:?工作表
          ?????*?@throws?RowsExceededException
          ?????*?@throws?WriteException
          ?????*/
          ????private?void?insertData(WritableWorkbook?wwb,?WritableSheet?dataSheet,?Vector?vecData)?throws?RowsExceededException,?WriteException
          ????{
          ????????//?獲得標(biāo)題單元格對(duì)象????????
          ????????modiStrCell(dataSheet,?2,?0,?"工商銀行江蘇省分行?個(gè)人網(wǎng)上銀行業(yè)務(wù)種類/開銷戶明細(xì)報(bào)表(2005-12)",?null);
          ????????//?修改數(shù)據(jù)單元格數(shù)據(jù)
          ????????for(int?i?=?0;?i?<?vecData.size();?i?++)
          ????????{
          ????????????VireObj?obj?=?(VireObj)vecData.get(i);
          ????????????modiStrCell(dataSheet,?0,?TITLE_LENGTH?+?i,?obj.getOrgNo(),?null);
          ????????????modiStrCell(dataSheet,?1,?TITLE_LENGTH?+?i,?obj.getOrgName(),?null);
          ????????????modiNumCell(dataSheet,?2,?TITLE_LENGTH?+?i,?obj.getOpenAcc(),?null);
          ????????????modiNumCell(dataSheet,?3,?TITLE_LENGTH?+?i,?obj.getDestoryAcc(),?null);
          ????????????modiNumCell(dataSheet,?4,?TITLE_LENGTH?+?i,?obj.getTotalAcc(),?null);
          ????????????modiNumCell(dataSheet,?5,?TITLE_LENGTH?+?i,?obj.getMonthInCount(),?null);
          ????????????modiNumCell(dataSheet,?6,?TITLE_LENGTH?+?i,?obj.getTotalInMoney(),?null);
          ????????????modiNumCell(dataSheet,?7,?TITLE_LENGTH?+?i,?obj.getMonthOutCount(),?null);
          ????????????modiNumCell(dataSheet,?8,?TITLE_LENGTH?+?i,?obj.getMonthOutMoney(),?null);
          ????????}????
          ????????//?刪除空行
          ????????for?(int?j?=?vecData.size()?+?TITLE_LENGTH;?j?<?SHEET_HEIGHT;?j++)
          ????????{
          ????????????dataSheet.removeRow(vecData.size()?+?TITLE_LENGTH);
          ????????}????????
          ????????//?插入公式
          ????????for(int?i?=?2;?i?<?SHEET_WIDTH;?i?++)
          ????????{
          ????????????modiFormulaCell(dataSheet,?i,?vecData.size()?+?TITLE_LENGTH,?8,?vecData.size()?+?TITLE_LENGTH,?null);
          ????????}????????
          ????}

          ????/**
          ?????*?修改字符單元格的值
          ?????*?@param?dataSheet?WritableSheet?:?工作表
          ?????*?@param?col?int?:?列
          ?????*?@param?row?int?:?行
          ?????*?@param?str?String?:?字符
          ?????*?@param?format?CellFormat?:?單元格的樣式
          ?????*?@throws?RowsExceededException
          ?????*?@throws?WriteException
          ?????*/
          ????private?void?modiStrCell(WritableSheet?dataSheet,?int?col,?int?row,?String?str,?CellFormat?format)?throws?RowsExceededException,?WriteException
          ????{
          ????????//?獲得單元格對(duì)象
          ????????WritableCell?cell?=?dataSheet.getWritableCell(col,?row);
          ????????//?判斷單元格的類型,?做出相應(yīng)的轉(zhuǎn)化
          ????????if?(cell.getType()?==?CellType.EMPTY)
          ????????{
          ????????????Label?lbl?=?new?Label(col,?row,?str);
          ????????????if(null?!=?format)
          ????????????{
          ????????????????lbl.setCellFormat(format);
          ????????????}?else
          ????????????{
          ????????????????lbl.setCellFormat(cell.getCellFormat());
          ????????????}
          ????????????dataSheet.addCell(lbl);
          ????????}?else?if?(cell.getType()?==?CellType.LABEL)
          ????????{
          ????????????Label?lbl?=?(Label)cell;
          ????????????lbl.setString(str);
          ????????}?else?if?(cell.getType()?==?CellType.NUMBER)
          ????????{
          ????????????//?數(shù)字單元格修改
          ????????????Number?n1?=?(Number)cell;
          ????????????n1.setValue(42.05);
          ????????}
          ????}
          ????
          ????/**
          ?????*?修改數(shù)字單元格的值
          ?????*?@param?dataSheet?WritableSheet?:?工作表
          ?????*?@param?col?int?:?列
          ?????*?@param?row?int?:?行
          ?????*?@param?num?double?:?數(shù)值
          ?????*?@param?format?CellFormat?:?單元格的樣式
          ?????*?@throws?RowsExceededException
          ?????*?@throws?WriteException
          ?????*/
          ????private?void?modiNumCell(WritableSheet?dataSheet,?int?col,?int?row,?double?num,?CellFormat?format)?throws?RowsExceededException,?WriteException
          ????{
          ????????//?獲得單元格對(duì)象
          ????????WritableCell?cell?=?dataSheet.getWritableCell(col,?row);
          ????????//?判斷單元格的類型,?做出相應(yīng)的轉(zhuǎn)化
          ????????if?(cell.getType()?==?CellType.EMPTY)
          ????????{
          ????????????Number?lbl?=?new?Number(col,?row,?num);
          ????????????if(null?!=?format)
          ????????????{
          ????????????????lbl.setCellFormat(format);
          ????????????}?else
          ????????????{
          ????????????????lbl.setCellFormat(cell.getCellFormat());
          ????????????}
          ????????????dataSheet.addCell(lbl);
          ????????}?else?if?(cell.getType()?==?CellType.NUMBER)
          ????????{
          ????????????//?數(shù)字單元格修改
          ????????????Number?lbl?=?(Number)cell;
          ????????????lbl.setValue(num);
          ????????}?else?if?(cell.getType()?==?CellType.LABEL)
          ????????{
          ????????????Label?lbl?=?(Label)cell;
          ????????????lbl.setString(String.valueOf(num));
          ????????}
          ????}
          ????
          ????/**
          ?????*?修改公式單元格的值
          ?????*?@param?dataSheet?WritableSheet?:?工作表
          ?????*?@param?col?int?:?列
          ?????*?@param?row?int?:?行
          ?????*?@param?startPos?int?:?開始位置
          ?????*?@param?endPos?int?:?結(jié)束位置
          ?????*?@param?format
          ?????*?@throws?RowsExceededException
          ?????*?@throws?WriteException
          ?????*/
          ????private?void?modiFormulaCell(WritableSheet?dataSheet,?int?col,?int?row,?int?startPos,?int?endPos,?CellFormat?format)?throws?RowsExceededException,?WriteException
          ????{
          ????????String?f?=?getFormula(col,?row,?startPos,?endPos);
          ????????//?插入公式(只支持插入,不支持修改)
          ????????WritableCell?cell?=?dataSheet.getWritableCell(col,?row);
          ????????if?(cell.getType()?==?CellType.EMPTY)
          ????????{????????????????????
          ????????????//?公式單元格
          ????????????Formula?lbl?=?new?Formula(col,?row,?f);
          ????????????if(null?!=?format)
          ????????????{
          ????????????????lbl.setCellFormat(format);
          ????????????}?else
          ????????????{
          ????????????????lbl.setCellFormat(cell.getCellFormat());
          ????????????}
          ????????????dataSheet.addCell(lbl);
          ????????}?else?if?(cell.getType()?==?CellType.STRING_FORMULA)
          ????????{
          ????????????YTLogger.logWarn("Formula?modify?not?supported!");
          ????????}
          ????}
          ????
          ????/**
          ?????*?得到公式
          ?????*?@param?col?int?:?列
          ?????*?@param?row?int?:?行
          ?????*?@param?startPos?int?:?開始位置
          ?????*?@param?endPos?int?:?結(jié)束位置
          ?????*?@return?String
          ?????*?@throws?RowsExceededException
          ?????*?@throws?WriteException
          ?????*/
          ????private?String?getFormula(int?col,?int?row,?int?startPos,?int?endPos)
          ????????????throws?RowsExceededException,?WriteException
          ????{
          ????????char?base?=?'A';
          ????????char?c1?=?base;
          ????????StringBuffer?formula?=?new?StringBuffer(128);
          ????????//?組裝公式
          ????????formula.append("SUM(");
          ????????if?(col?<=?25)
          ????????{
          ????????????c1?=?(char)?(col?%?26?+?base);
          ????????????formula.append(c1).append(startPos).append(":")
          ???????????????????.append(c1).append(endPos).append(")");
          ????????}?else?if?(col?>?25)
          ????????{
          ????????????char?c2?=?(char)?((col?-?26)?/?26?+?base);
          ????????????c1?=?(char)?((col?-?26)?%?26?+?base);
          ????????????formula.append(c2).append(c1).append(startPos).append(":")
          ???????????????????.append(c2).append(c1).append(endPos).append(")");
          ????????}

          ????????return?formula.toString();
          ????}
          ????
          ????/**
          ?????*?插入圖表工作表
          ?????*?@param?wwb?WritableWorkbook?:?工作簿
          ?????*?@param?vecImg?Vector?:?圖像鏈表
          ?????*?@throws?RowsExceededException
          ?????*?@throws?WriteException
          ?????*/
          ????private?void?insertImgsheet(WritableWorkbook?wwb,?Vector?vecImg)
          ????????????throws?RowsExceededException,?WriteException
          ????{
          ????????//?插入圖像
          ????????WritableSheet?imgSheet;
          ????????if((wwb.getSheets()).length?<?2)
          ????????{
          ????????????imgSheet?=?wwb.createSheet("圖表",?1);
          ????????}?else
          ????????{
          ????????????imgSheet?=?wwb.getSheet(1);
          ????????}
          ????????
          ????????for?(int?i?=?0;?i?<?vecImg.size();?i++)
          ????????{
          ????????????ChartImg?chart?=?(ChartImg)?vecImg.get(i);
          ????????????//?插入圖像標(biāo)題
          ????????????Label?lbl?=?new?Label(0,?2?+?20?*?i,?chart.getImgTitle());
          ????????????WritableFont?font?=?new?WritableFont(WritableFont.ARIAL,
          ????????????????????WritableFont.DEFAULT_POINT_SIZE,?WritableFont.NO_BOLD,?false,
          ????????????????????UnderlineStyle.NO_UNDERLINE,?Colour.DARK_BLUE2);
          ????????????WritableCellFormat?background?=?new?WritableCellFormat(font);
          ????????????background.setWrap(true);
          ????????????background.setBackground(Colour.GRAY_25);
          ????????????imgSheet.mergeCells(0,?2?+?20?*?i,?9,?2?+?20?*?i);
          ????????????lbl.setCellFormat(background);
          ????????????imgSheet.addCell(lbl);
          ????????????//?插入圖像單元格
          ????????????insertImgCell(imgSheet,?2,?4?+?20?*?i,?8,?15,?chart.getImgName());
          ????????}
          ????}

          ????/**
          ?????*?插入圖像到單元格(圖像格式只支持png)
          ?????*?@param?dataSheet?WritableSheet?:?工作表
          ?????*?@param?col?int?:?列
          ?????*?@param?row?int?:?行
          ?????*?@param?width?int?:?寬
          ?????*?@param?height?int?:?高
          ?????*?@param?imgName?String?:?圖像的全路徑
          ?????*?@throws?RowsExceededException
          ?????*?@throws?WriteException
          ?????*/
          ????private?void?insertImgCell(WritableSheet?dataSheet,?int?col,?int?row,?int?width,
          ????????????int?height,?String?imgName)?throws?RowsExceededException,?WriteException
          ????{
          ????????File?imgFile?=?new?File(imgName);
          ????????WritableImage?img?=?new?WritableImage(col,?row,?width,?height,?imgFile);
          ????????dataSheet.addImage(img);
          ????}
          ????
          ????/**
          ?????*?測試
          ?????*?@param?args
          ?????*/
          ????public?static?void?main(String[]?args)
          ????{
          ????????XLSDemo?demo?=?new?XLSDemo();
          ????????demo.makeXls();
          ????}
          }

          posted @ 2006-09-09 12:23 boddi 閱讀(2970) | 評(píng)論 (0)編輯 收藏

          jxl不錯(cuò),簡單易用

          import jxl.*;
          import jxl.write.*;
          import java.io.*;
          import java.io.File.*;
          import java.util.*;



          public class excel
          {
          public static void main(String[] args)
          {

          String targetfile = "c:/out.xls";//輸出的excel文件名
          String worksheet = "List";//輸出的excel文件工作表名
          String[] title = {"ID","NAME","DESCRIB"};//excel工作表的標(biāo)題


          WritableWorkbook workbook;
          try
          {
          //創(chuàng)建可寫入的Excel工作薄,運(yùn)行生成的文件在tomcat/bin下
          //workbook = Workbook.createWorkbook(new File("output.xls"));
          System.out.println("begin");

          OutputStream os=new FileOutputStream(targetfile);
          workbook=Workbook.createWorkbook(os);

          WritableSheet sheet = workbook.createSheet(worksheet, 0); //添加第一個(gè)工作表
          //WritableSheet sheet1 = workbook.createSheet("MySheet1", 1); //可添加第二個(gè)工作
          /*
          jxl.write.Label label = new jxl.write.Label(0, 2, "A label record"); //put a label in cell A3, Label(column,row)
          sheet.addCell(label);
          */

          jxl.write.Label label;
          for (int i=0; i<title.length; i++)
          {
          //Label(列號(hào),行號(hào) ,內(nèi)容 )
          label = new jxl.write.Label(i, 0, title[i]); //put the title in row1
          sheet.addCell(label);
          }




          //下列添加的對(duì)字體等的設(shè)置均調(diào)試通過,可作參考用


          //添加數(shù)字
          jxl.write.Number number = new jxl.write.Number(3, 4, 3.14159); //put the number 3.14159 in cell D5
          sheet.addCell(number);

          //添加帶有字型Formatting的對(duì)象
          jxl.write.WritableFont wf = new jxl.write.WritableFont(WritableFont.TIMES,10,WritableFont.BOLD,true);
          jxl.write.WritableCellFormat wcfF = new jxl.write.WritableCellFormat(wf);
          jxl.write.Label labelCF = new jxl.write.Label(4,4,"文本",wcfF);
          sheet.addCell(labelCF);

          //添加帶有字體顏色,帶背景顏色 Formatting的對(duì)象
          jxl.write.WritableFont wfc = new jxl.write.WritableFont(WritableFont.ARIAL,10,WritableFont.BOLD,false,jxl.format.UnderlineStyle.NO_UNDERLINE,jxl.format.Colour.RED);
          jxl.write.WritableCellFormat wcfFC = new jxl.write.WritableCellFormat(wfc);
          wcfFC.setBackground(jxl.format.Colour.BLUE);
          jxl.write.Label labelCFC = new jxl.write.Label(1,5,"帶顏色",wcfFC);
          sheet.addCell(labelCFC);

          //添加帶有formatting的Number對(duì)象
          jxl.write.NumberFormat nf = new jxl.write.NumberFormat("#.##");
          jxl.write.WritableCellFormat wcfN = new jxl.write.WritableCellFormat(nf);
          jxl.write.Number labelNF = new jxl.write.Number(1,1,3.1415926,wcfN);
          sheet.addCell(labelNF);

          //3.添加Boolean對(duì)象
          jxl.write.Boolean labelB = new jxl.write.Boolean(0,2,false);
          sheet.addCell(labelB);

          //4.添加DateTime對(duì)象
          jxl.write.DateTime labelDT = new jxl.write.DateTime(0,3,new java.util.Date());
          sheet.addCell(labelDT);

          //添加帶有formatting的DateFormat對(duì)象
          jxl.write.DateFormat df = new jxl.write.DateFormat("ddMMyyyyhh:mm:ss");
          jxl.write.WritableCellFormat wcfDF = new jxl.write.WritableCellFormat(df);
          jxl.write.DateTime labelDTF = new jxl.write.DateTime(1,3,new java.util.Date(),wcfDF);
          sheet.addCell(labelDTF);

          //和賓單元格
          //sheet.mergeCells(int col1,int row1,int col2,int row2);//左上角到右下角
          sheet.mergeCells(4,5,8,10);//左上角到右下角
          wfc = new jxl.write.WritableFont(WritableFont.ARIAL,40,WritableFont.BOLD,false,jxl.format.UnderlineStyle.NO_UNDERLINE,jxl.format.Colour.GREEN);
          jxl.write.WritableCellFormat wchB = new jxl.write.WritableCellFormat(wfc);
          wchB.setAlignment(jxl.format.Alignment.CENTRE);
          labelCFC = new jxl.write.Label(4,5,"單元合并",wchB);
          sheet.addCell(labelCFC); //


          //設(shè)置邊框
          jxl.write.WritableCellFormat wcsB = new jxl.write.WritableCellFormat();
          wcsB.setBorder(jxl.format.Border.ALL,jxl.format.BorderLineStyle.THICK);
          labelCFC = new jxl.write.Label(0,6,"邊框設(shè)置",wcsB);
          sheet.addCell(labelCFC);
          workbook.write();
          workbook.close();
          }catch(Exception e)
          {
          e.printStackTrace();
          }
          System.out.println("end");
          Runtime r=Runtime.getRuntime();
          Process p=null;
          //String cmd[]={"notepad","exec.java"};
          String cmd[]={"C:\\Program Files\\Microsoft Office\\Office\\EXCEL.EXE","out.xls"};
          try{
          p=r.exec(cmd);
          }
          catch(Exception e){
          System.out.println("error executing: "+cmd[0]);
          }


          }
          }

          posted @ 2006-09-09 11:20 boddi 閱讀(7010) | 評(píng)論 (5)編輯 收藏

          Java中合并XML文檔的設(shè)計(jì)與實(shí)現(xiàn)
          作者: 凌宗虎 李先國
          出處: 計(jì)算機(jī)與信息技術(shù)
          責(zé)任編輯: 方舟
          [ 2005-06-09 08:39 ]

            摘 要:介紹了XML應(yīng)用中合并XML文檔的方法與應(yīng)用,在基于XML的應(yīng)用中,有著廣泛的應(yīng)用前景。

            關(guān)鍵詞:XML文檔 解析器 元素

            在XML應(yīng)用中,最常用也最實(shí)用的莫過于XML文件的讀寫。由于XML語義比較嚴(yán)格,起始標(biāo)記必須配對(duì),所以合并XML文檔并不像合并普通文件那樣簡單。在JAVA中,如何合并XML文檔,下面介紹一種方法。

            設(shè)計(jì)思想

            應(yīng)用javax.xml.parsers包中的解析器解析得到兩個(gè)XML文件的根元素,再采用遞歸的方式逐一復(fù)制被合并文件的元素。


            實(shí)現(xiàn)過程

            為了讀寫XML文件,需要導(dǎo)入如下JAVA包,"http://"后為注釋說明,筆者的環(huán)境是JDK 1.3.1,在JDK 1.4.0中測試也通過。

          Import java.io. *; //Java基礎(chǔ)包,包含各種IO操作
          Import java.util. *; //Java基礎(chǔ)包,包含各種標(biāo)準(zhǔn)數(shù)據(jù)結(jié)構(gòu)操作
          Import javax.xml.parsers. *; //XML解析器接口
          Import org.w3c.dom. *; //XML的DOM實(shí)現(xiàn)
          import org.apache.crimson.tree.XmlDocument;//寫XML文件要用到
          Import javax.xml.transform. *;
          Import javax.xml.transform.dom. *;
          Import javax.xml.transform.stream. *;

            下面介紹合并XML文檔的過程。先說明一下各個(gè)方法的作用。方法is Merging()有兩個(gè)參數(shù)(分別是目標(biāo)XML文件名和被合并的XML文件名),調(diào)用JAVA的解析器,獲得兩個(gè)要合并的XML文檔的Document結(jié)構(gòu)和根元素,并調(diào)用方法duplicate()和方法write To()。當(dāng)然,在XML文檔的合并過程中,可以加入另外的一些判斷條件,比如,當(dāng)被合并XML文檔不存在時(shí),將如何處理,等等。

          Private Boolean is Merging (String mainFileName, String sub Filename) throws Exception {
           Boolean isOver = false;
           DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
           Document Builder db = null;
           Try {
            Db = dbf.newDocumentBuilder ();
           } Catch (ParserConfigurationException pce) {
            System.err.println(pce); //出現(xiàn)異常時(shí),輸出異常信息
           }
           Document doc_main = null,doc_vice = null;
           //獲取兩個(gè)XML文件的Document。
           Try {
            Doc_main = db.parse (mainFileName);
            Doc_vice = db.parse (sub Filename);
           } Catch (DOM Exception dom) {
            System.err.println (dom.getMessage ());
           } Catch (Exception ioe) {
            System.err.println (ioe);
           }
           //獲取兩個(gè)文件的根元素。
           Element root_main = doc_main.getDocumentElement ();
           Element root_vice = doc_vice.getDocumentElement ();
           //下面添加被合并文件根節(jié)點(diǎn)下的每個(gè)元素
           Novelist message Items = root_vice.getChildNodes ();
           Int item_number = messageItems.getLength ();
           //如果去掉根節(jié)點(diǎn)下的第一個(gè)元素,比如<所屬管理系統(tǒng)> ,那么i從3開始。否則i從1開始。
           For (int i=1; i < item_number; i=i+2 ) {
            //調(diào)用dupliate(),依次復(fù)制被合并XML文檔中根節(jié)點(diǎn)下的元素。
            Element messageItem = (Element) messageItems.item (i);
            IsOver = dupliate (doc_main, root_main, messageItem);
           }
           //調(diào)用 write To(),將合并得到的Document寫入目標(biāo)XML文檔。
           Boolean isWritten = write To (doc_main, mainFileName);
           Return isOver && isWritten;
          }

            方法dupliate ()有三個(gè)參數(shù)(分別是目標(biāo)XML文檔的Document,目標(biāo)XML文檔中要添加節(jié)點(diǎn)的父節(jié)點(diǎn)和被合并XML文檔的復(fù)制節(jié)點(diǎn)),采用遞歸的形式,將一個(gè)XML文檔中的元素復(fù)制到另一個(gè)XML文檔中。

          Private Boolean dupliate (Document doc_dup, Element father, Element son) throws Exception {
           Boolean is done = false;
           String son_name = son.getNodeName ();
           Element sub ITEM = doc_dup.createElement (son_name);
           //復(fù)制節(jié)點(diǎn)的屬性
           If (son.hasAttributes ()){
            NamedNodeMap attributes = son.getAttributes ();
            For (int i=0; i < attributes.getLength () ; i ++){
             String attribute_name = attributes. Item (i). GetNodeName ();
             String attribute_value = attributes. Item (i). GetNodeValue ();
             SubITEM.setAttribute (attribute_name, attribute_value);
            }
           }
           Father.appendChild (sub ITEM);
           //復(fù)制節(jié)點(diǎn)的值
           Text value son = (Text) son.getFirstChild ();
           String nodevalue_root = "";
           If (value_son! = null && value_son.getLength () > 0) nodevalue_root = (String) value_son.getNodeValue ();
           Text valuenode_root = null;
           If ((nodevalue_root! = null)&&(nodevalue_root.length () > 0)) valuenode_root = doc_dup.createTextNode (nodevalue_root);
           If (valuenode_root! = null && valuenode_root.getLength () > 0) subITEM.appendChild (valuenode_root);
           //復(fù)制子結(jié)點(diǎn)
           Novelist sub_messageItems = son.getChildNodes ();
           int sub_item_number = sub_messageItems.getLength();
           if (sub_item_number < 2){
            //如果沒有子節(jié)點(diǎn),則返回
            Is done = true;
           }
           Else {
            For (int j = 1; j < sub_item_number; j=j+2) {
             //如果有子節(jié)點(diǎn),則遞歸調(diào)用本方法
             Element sub_messageItem = (Element) sub_messageItems.item (j);
             Is done = dupliate (doc_dup, subITEM, sub_messageItem);
            }
           }
           Return is done;
          }

            方法writeTo()有兩個(gè)參數(shù)(分別是目標(biāo)XML文檔的Document和文件名),將所得目標(biāo)XML文檔寫入文件。

          Private Boolean write To (Document doc, String fileName) throws Exception {
           Boolean isOver = false;
           DOM Source doms = new DOM Source (doc);
           File f = new File (fileName);
           Stream Result sr = new Stream Result (f);
           Try
           {
            Transformer Factory tf=TransformerFactory.newInstance ();
            Transformer t=tf.newTransformer ();
            Properties properties = t.getOutputProperties ();
            Properties.setProperty (OutputKeys.ENCODING,"GB2312");
            T.setOutputProperties (properties);
            T.transform (doms, sr);
            IsOver = true;
           }
           Catch (TransformerConfigurationException tce)
           {
            Tce.printStackTrace ();
           }
           Catch (Transformer Exception te)
           {
            Te.printStackTrace ();
           }
           Return isOver;
          }

            最后使用測試函數(shù)進(jìn)行測試。對(duì)于兩個(gè)已經(jīng)存在的XML文件(比如,存在文件D:/a.xml和D:/b.xml,要將b.xml合并到a.xml中),可以測試如下:

          Public static void main (String [] args) throws Exception {
           Boolean is done = is Merging ("D:/a.xml","D:/b.xml");
           If (is Done) System.out.println ("XML files have been merged.");
           Else System.out.println ("XML files have NOT been merged.");
          }

            總結(jié)

            本文介紹了如何利用JAVA中的XML解析器,合并兩個(gè)XML文檔。當(dāng)然,在合并的過程中,還可以加入其他的約束條件,比如要求過濾掉特定的元素等。另外,復(fù)制元素的插入位置也可以加以限制。

          posted @ 2006-09-07 15:25 boddi 閱讀(1002) | 評(píng)論 (0)編輯 收藏

          在應(yīng)用中加入全文檢索功能
          ??? ——基于Java的全文索引引擎Lucene簡介

          作者: 車東 Email: chedongATbigfoot.com/chedongATchedong.com

          寫于:2002/08 最后更新: 02/22/2006 14:42:55
          Feed Back >>?(Read this before you ask question)

          版權(quán)聲明:可以任意轉(zhuǎn)載,轉(zhuǎn)載時(shí)請(qǐng)務(wù)必以超鏈接形式標(biāo)明文章原始出處和作者信息及本聲明
          http://www.chedong.com/tech/lucene.html

          關(guān)鍵詞:Lucene java full-text search engine?Chinese?word segment

          內(nèi)容摘要:

          Lucene是一個(gè)基于Java的全文索引工具包。

          1. 基于Java的全文索引引擎Lucene簡介:關(guān)于作者和Lucene的歷史
          2. 全文檢索的實(shí)現(xiàn):Luene全文索引和數(shù)據(jù)庫索引的比較
          3. 中文切分詞機(jī)制簡介:基于詞庫和自動(dòng)切分詞算法的比較
          4. 具體的安裝和使用簡介:系統(tǒng)結(jié)構(gòu)介紹和演示
          5. Hacking Lucene:簡化的查詢分析器,刪除的實(shí)現(xiàn),定制的排序,應(yīng)用接口的擴(kuò)展
          6. 從Lucene我們還可以學(xué)到什么

          基于Java的全文索引/檢索引擎——Lucene

          Lucene不是一個(gè)完整的全文索引應(yīng)用,而是是一個(gè)用Java寫的全文索引引擎工具包,它可以方便的嵌入到各種應(yīng)用中實(shí)現(xiàn)針對(duì)應(yīng)用的全文索引/檢索功能。

          Lucene的作者:Lucene的貢獻(xiàn)者Doug Cutting是一位資深全文索引/檢索專家,曾經(jīng)是V-Twin搜索引擎(Apple的Copland操作系統(tǒng)的成就之一)的主要開發(fā)者,后在Excite擔(dān)任高級(jí)系統(tǒng)架構(gòu)設(shè)計(jì)師,目前從事于一些INTERNET底層架構(gòu)的研究。他貢獻(xiàn)出的Lucene的目標(biāo)是為各種中小型應(yīng)用程序加入全文檢索功能。

          Lucene的發(fā)展歷程:早先發(fā)布在作者自己的www.lucene.com,后來發(fā)布在SourceForge,2001年年底成為APACHE基金會(huì)jakarta的一個(gè)子項(xiàng)目:http://jakarta.apache.org/lucene/

          已經(jīng)有很多Java項(xiàng)目都使用了Lucene作為其后臺(tái)的全文索引引擎,比較著名的有:

          • J ive:WEB論壇系統(tǒng);
          • Eyebrows:郵件列表HTML歸檔/瀏覽/查詢系統(tǒng),本文的主要參考文檔“TheLucene search engine: Powerful, flexible, and free”作者就是EyeBrows系統(tǒng)的主要開發(fā)者之一,而EyeBrows已經(jīng)成為目前APACHE項(xiàng)目的主要郵件列表歸檔系統(tǒng)。
          • Cocoon:基于XML的web發(fā)布框架,全文檢索部分使用了Lucene
          • Eclipse:基于Java的開放開發(fā)平臺(tái),幫助部分的全文索引使用了Lucene

          對(duì)于中文用戶來說,最關(guān)心的問題是其是否支持中文的全文檢索。但通過后面對(duì)于Lucene的結(jié)構(gòu)的介紹,你會(huì)了解到由于Lucene良好架構(gòu)設(shè)計(jì),對(duì)中文的支持只需對(duì)其語言詞法分析接口進(jìn)行擴(kuò)展就能實(shí)現(xiàn)對(duì)中文檢索的支持。

          全文檢索的實(shí)現(xiàn)機(jī)制

          Lucene的API接口設(shè)計(jì)的比較通用,輸入輸出結(jié)構(gòu)都很像數(shù)據(jù)庫的表==>記錄==>字段,所以很多傳統(tǒng)的應(yīng)用的文件、數(shù)據(jù)庫等都可以比較方便的映射到Lucene的存儲(chǔ)結(jié)構(gòu)/接口中。總體上看:可以先把Lucene當(dāng)成一個(gè)支持全文索引的數(shù)據(jù)庫系統(tǒng)

          比較一下Lucene和數(shù)據(jù)庫:

          Lucene 數(shù)據(jù)庫
          索引數(shù)據(jù)源:doc(field1,field2...) doc(field1,field2...)
          \ indexer /
          _____________
          | Lucene Index|
          --------------
          / searcher \
          結(jié)果輸出:Hits(doc(field1,field2) doc(field1...))
           索引數(shù)據(jù)源:record(field1,field2...) record(field1..)
          \ SQL: insert/
          _____________
          | DB Index |
          -------------
          / SQL: select \
          結(jié)果輸出:results(record(field1,field2..) record(field1...))
          Document:一個(gè)需要進(jìn)行索引的“單元”
          一個(gè)Document由多個(gè)字段組成
          Record:記錄,包含多個(gè)字段
          Field:字段 Field:字段
          Hits:查詢結(jié)果集,由匹配的Document組成 RecordSet:查詢結(jié)果集,由多個(gè)Record組成

          全文檢索 ≠ like "%keyword%"

          通常比較厚的書籍后面常常附關(guān)鍵詞索引表(比如:北京:12, 34頁,上海:3,77頁……),它能夠幫助讀者比較快地找到相關(guān)內(nèi)容的頁碼。而數(shù)據(jù)庫索引能夠大大提高查詢的速度原理也是一樣,想像一下通過書后面的索引查找的速度要比一頁一頁地翻內(nèi)容高多少倍……而索引之所以效率高,另外一個(gè)原因是它是排好序的。對(duì)于檢索系統(tǒng)來說核心是一個(gè)排序問題

          由于數(shù)據(jù)庫索引不是為全文索引設(shè)計(jì)的,因此,使用like "%keyword%"時(shí),數(shù)據(jù)庫索引是不起作用的,在使用like查詢時(shí),搜索過程又變成類似于一頁頁翻書的遍歷過程了,所以對(duì)于含有模糊查詢的數(shù)據(jù)庫服務(wù)來說,LIKE對(duì)性能的危害是極大的。如果是需要對(duì)多個(gè)關(guān)鍵詞進(jìn)行模糊匹配:like"%keyword1%" and like "%keyword2%" ...其效率也就可想而知了。

          所以建立一個(gè)高效檢索系統(tǒng)的關(guān)鍵是建立一個(gè)類似于科技索引一樣的反向索引機(jī)制,將數(shù)據(jù)源(比如多篇文章)排序順序存儲(chǔ)的同時(shí),有另外一個(gè)排好序的關(guān)鍵詞列表,用于存儲(chǔ)關(guān)鍵詞==>文章映射關(guān)系,利用這樣的映射關(guān)系索引:[關(guān)鍵詞==>出現(xiàn)關(guān)鍵詞的文章編號(hào),出現(xiàn)次數(shù)(甚至包括位置:起始偏移量,結(jié)束偏移量),出現(xiàn)頻率],檢索過程就是把模糊查詢變成多個(gè)可以利用索引的精確查詢的邏輯組合的過程。從而大大提高了多關(guān)鍵詞查詢的效率,所以,全文檢索問題歸結(jié)到最后是一個(gè)排序問題。

          由此可以看出模糊查詢相對(duì)數(shù)據(jù)庫的精確查詢是一個(gè)非常不確定的問題,這也是大部分?jǐn)?shù)據(jù)庫對(duì)全文檢索支持有限的原因。Lucene最核心的特征是通過特殊的索引結(jié)構(gòu)實(shí)現(xiàn)了傳統(tǒng)數(shù)據(jù)庫不擅長的全文索引機(jī)制,并提供了擴(kuò)展接口,以方便針對(duì)不同應(yīng)用的定制。

          可以通過一下表格對(duì)比一下數(shù)據(jù)庫的模糊查詢:

            Lucene全文索引引擎 數(shù)據(jù)庫
          索引 將數(shù)據(jù)源中的數(shù)據(jù)都通過全文索引一一建立反向索引 對(duì)于LIKE查詢來說,數(shù)據(jù)傳統(tǒng)的索引是根本用不上的。數(shù)據(jù)需要逐個(gè)便利記錄進(jìn)行GREP式的模糊匹配,比有索引的搜索速度要有多個(gè)數(shù)量級(jí)的下降。
          匹配效果 通過詞元(term)進(jìn)行匹配,通過語言分析接口的實(shí)現(xiàn),可以實(shí)現(xiàn)對(duì)中文等非英語的支持。 使用:like "%net%" 會(huì)把netherlands也匹配出來,
          多個(gè)關(guān)鍵詞的模糊匹配:使用like "%com%net%":就不能匹配詞序顛倒的xxx.net..xxx.com
          匹配度 有匹配度算法,將匹配程度(相似度)比較高的結(jié)果排在前面。 沒有匹配程度的控制:比如有記錄中net出現(xiàn)5詞和出現(xiàn)1次的,結(jié)果是一樣的。
          結(jié)果輸出 通過特別的算法,將最匹配度最高的頭100條結(jié)果輸出,結(jié)果集是緩沖式的小批量讀取的。 返回所有的結(jié)果集,在匹配條目非常多的時(shí)候(比如上萬條)需要大量的內(nèi)存存放這些臨時(shí)結(jié)果集。
          可定制性 通過不同的語言分析接口實(shí)現(xiàn),可以方便的定制出符合應(yīng)用需要的索引規(guī)則(包括對(duì)中文的支持) 沒有接口或接口復(fù)雜,無法定制
          結(jié)論 高負(fù)載的模糊查詢應(yīng)用,需要負(fù)責(zé)的模糊查詢的規(guī)則,索引的資料量比較大 使用率低,模糊匹配規(guī)則簡單或者需要模糊查詢的資料量少

          全文檢索和數(shù)據(jù)庫應(yīng)用最大的不同在于:讓 最相關(guān)的 頭100條結(jié)果滿足98%以上用戶的需求

          Lucene的創(chuàng)新之處:

          大部分的搜索(數(shù)據(jù)庫)引擎都是用B樹結(jié)構(gòu)來維護(hù)索引,索引的更新會(huì)導(dǎo)致大量的IO操作,Lucene在實(shí)現(xiàn)中,對(duì)此稍微有所改進(jìn):不是維護(hù)一個(gè)索引文件,而是在擴(kuò)展索引的時(shí)候不斷創(chuàng)建新的索引文件,然后定期的把這些新的小索引文件合并到原先的大索引中(針對(duì)不同的更新策略,批次的大小可以調(diào)整),這樣在不影響檢索的效率的前提下,提高了索引的效率。

          Lucene和其他一些全文檢索系統(tǒng)/應(yīng)用的比較:

            Lucene 其他開源全文檢索系統(tǒng)
          增量索引和批量索引 可以進(jìn)行增量的索引(Append),可以對(duì)于大量數(shù)據(jù)進(jìn)行批量索引,并且接口設(shè)計(jì)用于優(yōu)化批量索引和小批量的增量索引。 很多系統(tǒng)只支持批量的索引,有時(shí)數(shù)據(jù)源有一點(diǎn)增加也需要重建索引。
          數(shù)據(jù)源 Lucene沒有定義具體的數(shù)據(jù)源,而是一個(gè)文檔的結(jié)構(gòu),因此可以非常靈活的適應(yīng)各種應(yīng)用(只要前端有合適的轉(zhuǎn)換器把數(shù)據(jù)源轉(zhuǎn)換成相應(yīng)結(jié)構(gòu)), 很多系統(tǒng)只針對(duì)網(wǎng)頁,缺乏其他格式文檔的靈活性。
          索引內(nèi)容抓取 Lucene的文檔是由多個(gè)字段組成的,甚至可以控制那些字段需要進(jìn)行索引,那些字段不需要索引,近一步索引的字段也分為需要分詞和不需要分詞的類型:
          ?? 需要進(jìn)行分詞的索引,比如:標(biāo)題,文章內(nèi)容字段
          ?? 不需要進(jìn)行分詞的索引,比如:作者/日期字段
          缺乏通用性,往往將文檔整個(gè)索引了
          語言分析 通過語言分析器的不同擴(kuò)展實(shí)現(xiàn):
          可以過濾掉不需要的詞:an the of 等,
          西文語法分析:將jumps jumped jumper都?xì)w結(jié)成jump進(jìn)行索引/檢索
          非英文支持:對(duì)亞洲語言,阿拉伯語言的索引支持
          缺乏通用接口實(shí)現(xiàn)
          查詢分析 通過查詢分析接口的實(shí)現(xiàn),可以定制自己的查詢語法規(guī)則:
          比如: 多個(gè)關(guān)鍵詞之間的 + - and or關(guān)系等
           
          并發(fā)訪問 能夠支持多用戶的使用  

           

          關(guān)于亞洲語言的的切分詞問題(Word Segment)

          對(duì)于中文來說,全文索引首先還要解決一個(gè)語言分析的問題,對(duì)于英文來說,語句中單詞之間是天然通過空格分開的,但亞洲語言的中日韓文語句中的字是一個(gè)字挨一個(gè),所有,首先要把語句中按“詞”進(jìn)行索引的話,這個(gè)詞如何切分出來就是一個(gè)很大的問題。

          首先,肯定不能用單個(gè)字符作(si-gram)為索引單元,否則查“上海”時(shí),不能讓含有“海上”也匹配。

          但一句話:“北京天安門”,計(jì)算機(jī)如何按照中文的語言習(xí)慣進(jìn)行切分呢?
          “北京 天安門” 還是“北 京 天安門”?讓計(jì)算機(jī)能夠按照語言習(xí)慣進(jìn)行切分,往往需要機(jī)器有一個(gè)比較豐富的詞庫才能夠比較準(zhǔn)確的識(shí)別出語句中的單詞。

          另外一個(gè)解決的辦法是采用自動(dòng)切分算法:將單詞按照2元語法(bigram)方式切分出來,比如:
          "北京天安門" ==> "北京 京天 天安 安門"。

          這樣,在查詢的時(shí)候,無論是查詢"北京" 還是查詢"天安門",將查詢?cè)~組按同樣的規(guī)則進(jìn)行切分:"北京","天安安門",多個(gè)關(guān)鍵詞之間按與"and"的關(guān)系組合,同樣能夠正確地映射到相應(yīng)的索引中。這種方式對(duì)于其他亞洲語言:韓文,日文都是通用的。

          基于自動(dòng)切分的最大優(yōu)點(diǎn)是沒有詞表維護(hù)成本,實(shí)現(xiàn)簡單,缺點(diǎn)是索引效率低,但對(duì)于中小型應(yīng)用來說,基于2元語法的切分還是夠用的。基于2元切分后的索引一般大小和源文件差不多,而對(duì)于英文,索引文件一般只有原文件的30%-40%不同,


          自動(dòng)切分 詞表切分
          實(shí)現(xiàn) 實(shí)現(xiàn)非常簡單 實(shí)現(xiàn)復(fù)雜
          查詢 增加了查詢分析的復(fù)雜程度, 適于實(shí)現(xiàn)比較復(fù)雜的查詢語法規(guī)則
          存儲(chǔ)效率 索引冗余大,索引幾乎和原文一樣大 索引效率高,為原文大小的30%左右
          維護(hù)成本 無詞表維護(hù)成本 詞表維護(hù)成本非常高:中日韓等語言需要分別維護(hù)。
          還需要包括詞頻統(tǒng)計(jì)等內(nèi)容
          適用領(lǐng)域 嵌入式系統(tǒng):運(yùn)行環(huán)境資源有限
          分布式系統(tǒng):無詞表同步問題
          多語言環(huán)境:無詞表維護(hù)成本
          對(duì)查詢和存儲(chǔ)效率要求高的專業(yè)搜索引擎

          目前比較大的搜索引擎的語言分析算法一般是基于以上2個(gè)機(jī)制的結(jié)合。關(guān)于中文的語言分析算法,大家可以在Google查關(guān)鍵詞"wordsegment search"能找到更多相關(guān)的資料。

          安裝和使用

          下載:http://jakarta.apache.org/lucene/

          注意:Lucene中的一些比較復(fù)雜的詞法分析是用JavaCC生成的(JavaCC:JavaCompilerCompiler,純Java的詞法分析生成器),所以如果從源代碼編譯或需要修改其中的QueryParser、定制自己的詞法分析器,還需要從https://javacc.dev.java.net/下載javacc。

          lucene的組成結(jié)構(gòu):對(duì)于外部應(yīng)用來說索引模塊(index)和檢索模塊(search)是主要的外部應(yīng)用入口

          org.apache.Lucene.search/ 搜索入口
          org.apache.Lucene.index/ 索引入口
          org.apache.Lucene.analysis/ 語言分析器
          org.apache.Lucene.queryParser/ 查詢分析器
          org.apache.Lucene.document/ 存儲(chǔ)結(jié)構(gòu)
          org.apache.Lucene.store/? 底層IO/存儲(chǔ)結(jié)構(gòu)
          org.apache.Lucene.util/ 一些公用的數(shù)據(jù)結(jié)構(gòu)

          簡單的例子演示一下Lucene的使用方法:

          索引過程:從命令行讀取文件名(多個(gè)),將文件分路徑(path字段)和內(nèi)容(body字段)2個(gè)字段進(jìn)行存儲(chǔ),并對(duì)內(nèi)容進(jìn)行全文索引:索引的單位是Document對(duì)象,每個(gè)Document對(duì)象包含多個(gè)字段Field對(duì)象,針對(duì)不同的字段屬性和數(shù)據(jù)輸出的需求,對(duì)字段還可以選擇不同的索引/存儲(chǔ)字段規(guī)則,列表如下:
          方法切詞索引存儲(chǔ)用途
          Field.Text(String name, String value)YesYesYes切分詞索引并存儲(chǔ),比如:標(biāo)題,內(nèi)容字段
          Field.Text(String name, Reader value)YesYesNo切分詞索引不存儲(chǔ),比如:META信息,
          不用于返回顯示,但需要進(jìn)行檢索內(nèi)容
          Field.Keyword(String name, String value)NoYesYes不切分索引并存儲(chǔ),比如:日期字段
          Field.UnIndexed(String name, String value)NoNoYes不索引,只存儲(chǔ),比如:文件路徑
          Field.UnStored(String name, String value)YesYesNo只全文索引,不存儲(chǔ)
          public class IndexFiles { 
          //使用方法:: IndexFiles [索引輸出目錄] [索引的文件列表] ...
          public static void main(String[] args) throws Exception {
          String indexPath = args[0];
          IndexWriter writer;
          //用指定的語言分析器構(gòu)造一個(gè)新的寫索引器(第3個(gè)參數(shù)表示是否為追加索引)
          writer = new IndexWriter(indexPath, new SimpleAnalyzer(), false);

          for (int i=1; i<args.length; i++) {
          System.out.println("Indexing file " + args[i]);
          InputStream is = new FileInputStream(args[i]);

          //構(gòu)造包含2個(gè)字段Field的Document對(duì)象
          //一個(gè)是路徑path字段,不索引,只存儲(chǔ)
          //一個(gè)是內(nèi)容body字段,進(jìn)行全文索引,并存儲(chǔ)
          Document doc = new Document();
          doc.add(Field.UnIndexed("path", args[i]));
          doc.add(Field.Text("body", (Reader) new InputStreamReader(is)));
          //將文檔寫入索引
          writer.addDocument(doc);
          is.close();
          };
          //關(guān)閉寫索引器
          writer.close();
          }
          }
           

          索引過程中可以看到:

          • 語言分析器提供了抽象的接口,因此語言分析(Analyser)是可以定制的,雖然lucene缺省提供了2個(gè)比較通用的分析器SimpleAnalyser和StandardAnalyser,這2個(gè)分析器缺省都不支持中文,所以要加入對(duì)中文語言的切分規(guī)則,需要修改這2個(gè)分析器。
          • Lucene并沒有規(guī)定數(shù)據(jù)源的格式,而只提供了一個(gè)通用的結(jié)構(gòu)(Document對(duì)象)來接受索引的輸入,因此輸入的數(shù)據(jù)源可以是:數(shù)據(jù)庫,WORD文檔,PDF文檔,HTML文檔……只要能夠設(shè)計(jì)相應(yīng)的解析轉(zhuǎn)換器將數(shù)據(jù)源構(gòu)造成成Docuement對(duì)象即可進(jìn)行索引。
          • 對(duì)于大批量的數(shù)據(jù)索引,還可以通過調(diào)整IndexerWrite的文件合并頻率屬性(mergeFactor)來提高批量索引的效率。

          檢索過程和結(jié)果顯示:

          搜索結(jié)果返回的是Hits對(duì)象,可以通過它再訪問Document==>Field中的內(nèi)容。

          假設(shè)根據(jù)body字段進(jìn)行全文檢索,可以將查詢結(jié)果的path字段和相應(yīng)查詢的匹配度(score)打印出來,

          public class Search { 
          public static void main(String[] args) throws Exception {
          String indexPath = args[0], queryString = args[1];
          //指向索引目錄的搜索器
          Searcher searcher = new IndexSearcher(indexPath);
          //查詢解析器:使用和索引同樣的語言分析器
          Query query = QueryParser.parse(queryString, "body",
          new SimpleAnalyzer());
          //搜索結(jié)果使用Hits存儲(chǔ)
          Hits hits = searcher.search(query);
          //通過hits可以訪問到相應(yīng)字段的數(shù)據(jù)和查詢的匹配度
          for (int i=0; i<hits.length(); i++) {
          System.out.println(hits.doc(i).get("path") + "; Score: " +
          hits.score(i));
          };
          }
          }
          在整個(gè)檢索過程中,語言分析器,查詢分析器,甚至搜索器(Searcher)都是提供了抽象的接口,可以根據(jù)需要進(jìn)行定制。

          Hacking Lucene

          簡化的查詢分析器

          個(gè)人感覺lucene成為JAKARTA項(xiàng)目后,畫在了太多的時(shí)間用于調(diào)試日趨復(fù)雜QueryParser,而其中大部分是大多數(shù)用戶并不很熟悉的,目前LUCENE支持的語法:

          Query ::= ( Clause )*
          Clause ::= ["+", "-"] [<TERM> ":"] ( <TERM> | "(" Query ")")

          中間的邏輯包括:and or + - &&||等符號(hào),而且還有"短語查詢"和針對(duì)西文的前綴/模糊查詢等,個(gè)人感覺對(duì)于一般應(yīng)用來說,這些功能有一些華而不實(shí),其實(shí)能夠?qū)崿F(xiàn)目前類似于Google的查詢語句分析功能其實(shí)對(duì)于大多數(shù)用戶來說已經(jīng)夠了。所以,Lucene早期版本的QueryParser仍是比較好的選擇。

          添加修改刪除指定記錄(Document)

          Lucene提供了索引的擴(kuò)展機(jī)制,因此索引的動(dòng)態(tài)擴(kuò)展應(yīng)該是沒有問題的,而指定記錄的修改也似乎只能通過記錄的刪除,然后重新加入實(shí)現(xiàn)。如何刪除指定的記錄呢?刪除的方法也很簡單,只是需要在索引時(shí)根據(jù)數(shù)據(jù)源中的記錄ID專門另建索引,然后利用IndexReader.delete(Termterm)方法通過這個(gè)記錄ID刪除相應(yīng)的Document。

          根據(jù)某個(gè)字段值的排序功能

          lucene缺省是按照自己的相關(guān)度算法(score)進(jìn)行結(jié)果排序的,但能夠根據(jù)其他字段進(jìn)行結(jié)果排序是一個(gè)在LUCENE的開發(fā)郵件列表中經(jīng)常提到的問題,很多原先基于數(shù)據(jù)庫應(yīng)用都需要除了基于匹配度(score)以外的排序功能。而從全文檢索的原理我們可以了解到,任何不基于索引的搜索過程效率都會(huì)導(dǎo)致效率非常的低,如果基于其他字段的排序需要在搜索過程中訪問存儲(chǔ)字段,速度回大大降低,因此非常是不可取的。

          但這里也有一個(gè)折中的解決方法:在搜索過程中能夠影響排序結(jié)果的只有索引中已經(jīng)存儲(chǔ)的docID和score這2個(gè)參數(shù),所以,基于score以外的排序,其實(shí)可以通過將數(shù)據(jù)源預(yù)先排好序,然后根據(jù)docID進(jìn)行排序來實(shí)現(xiàn)。這樣就避免了在LUCENE搜索結(jié)果外對(duì)結(jié)果再次進(jìn)行排序和在搜索過程中訪問不在索引中的某個(gè)字段值。

          這里需要修改的是IndexSearcher中的HitCollector過程:

          ...
           scorer.score(new HitCollector() {
          private float minScore = 0.0f;
          public final void collect(int doc, float score) {
          if (score > 0.0f && // ignore zeroed buckets
          (bits==null || bits.get(doc))) { // skip docs not in bits
          totalHits[0]++;
          if (score >= minScore) {
          /* 原先:Lucene將docID和相應(yīng)的匹配度score例入結(jié)果命中列表中:
          * hq.put(new ScoreDoc(doc, score)); // update hit queue
          * 如果用doc 或 1/doc 代替 score,就實(shí)現(xiàn)了根據(jù)docID順排或逆排
          * 假設(shè)數(shù)據(jù)源索引時(shí)已經(jīng)按照某個(gè)字段排好了序,而結(jié)果根據(jù)docID排序也就實(shí)現(xiàn)了
          * 針對(duì)某個(gè)字段的排序,甚至可以實(shí)現(xiàn)更復(fù)雜的score和docID的擬合。
          */
          hq.put(new ScoreDoc(doc, (float) 1/doc ));
          if (hq.size() > nDocs) { // if hit queue overfull
          hq.pop(); // remove lowest in hit queue
          minScore = ((ScoreDoc)hq.top()).score; // reset minScore
          }
          }
          }
          }
          }, reader.maxDoc());

          更通用的輸入輸出接口

          雖然lucene沒有定義一個(gè)確定的輸入文檔格式,但越來越多的人想到使用一個(gè)標(biāo)準(zhǔn)的中間格式作為Lucene的數(shù)據(jù)導(dǎo)入接口,然后其他數(shù)據(jù),比如PDF只需要通過解析器轉(zhuǎn)換成標(biāo)準(zhǔn)的中間格式就可以進(jìn)行數(shù)據(jù)索引了。這個(gè)中間格式主要以XML為主,類似實(shí)現(xiàn)已經(jīng)不下4,5個(gè):

          數(shù)據(jù)源: WORD       PDF     HTML    DB       other
          \ | | | /
          XML中間格式
          |
          Lucene INDEX

          目前還沒有針對(duì)MSWord文檔的解析器,因?yàn)閃ord文檔和基于ASCII的RTF文檔不同,需要使用COM對(duì)象機(jī)制解析。這個(gè)是我在Google上查的相關(guān)資料:http://www.intrinsyc.com/products/enterprise_applications.asp
          另外一個(gè)辦法就是把Word文檔轉(zhuǎn)換成text:http://www.winfield.demon.nl/index.html


          索引過程優(yōu)化

          索引一般分2種情況,一種是小批量的索引擴(kuò)展,一種是大批量的索引重建。在索引過程中,并不是每次新的DOC加入進(jìn)去索引都重新進(jìn)行一次索引文件的寫入操作(文件I/O是一件非常消耗資源的事情)。

          Lucene先在內(nèi)存中進(jìn)行索引操作,并根據(jù)一定的批量進(jìn)行文件的寫入。這個(gè)批次的間隔越大,文件的寫入次數(shù)越少,但占用內(nèi)存會(huì)很多。反之占用內(nèi)存少,但文件IO操作頻繁,索引速度會(huì)很慢。在IndexWriter中有一個(gè)MERGE_FACTOR參數(shù)可以幫助你在構(gòu)造索引器后根據(jù)應(yīng)用環(huán)境的情況充分利用內(nèi)存減少文件的操作。根據(jù)我的使用經(jīng)驗(yàn):缺省Indexer是每20條記錄索引后寫入一次,每將MERGE_FACTOR增加50倍,索引速度可以提高1倍左右。

          搜索過程優(yōu)化

          lucene支持內(nèi)存索引:這樣的搜索比基于文件的I/O有數(shù)量級(jí)的速度提升。
          http://www.onjava.com/lpt/a/3273
          而盡可能減少IndexSearcher的創(chuàng)建和對(duì)搜索結(jié)果的前臺(tái)的緩存也是必要的。

          Lucene面向全文檢索的優(yōu)化在于首次索引檢索后,并不把所有的記錄(Document)具體內(nèi)容讀取出來,而起只將所有結(jié)果中匹配度最高的頭100條結(jié)果(TopDocs)的ID放到結(jié)果集緩存中并返回,這里可以比較一下數(shù)據(jù)庫檢索:如果是一個(gè)10,000條的數(shù)據(jù)庫檢索結(jié)果集,數(shù)據(jù)庫是一定要把所有記錄內(nèi)容都取得以后再開始返回給應(yīng)用結(jié)果集的。所以即使檢索匹配總數(shù)很多,Lucene的結(jié)果集占用的內(nèi)存空間也不會(huì)很多。對(duì)于一般的模糊檢索應(yīng)用是用不到這么多的結(jié)果的,頭100條已經(jīng)可以滿足90%以上的檢索需求。

          如果首批緩存結(jié)果數(shù)用完后還要讀取更后面的結(jié)果時(shí)Searcher會(huì)再次檢索并生成一個(gè)上次的搜索緩存數(shù)大1倍的緩存,并再重新向后抓取。所以如果構(gòu)造一個(gè)Searcher去查1-120條結(jié)果,Searcher其實(shí)是進(jìn)行了2次搜索過程:頭100條取完后,緩存結(jié)果用完,Searcher重新檢索再構(gòu)造一個(gè)200條的結(jié)果緩存,依此類推,400條緩存,800條緩存。由于每次Searcher對(duì)象消失后,這些緩存也訪問那不到了,你有可能想將結(jié)果記錄緩存下來,緩存數(shù)盡量保證在100以下以充分利用首次的結(jié)果緩存,不讓Lucene浪費(fèi)多次檢索,而且可以分級(jí)進(jìn)行結(jié)果緩存。

          Lucene的另外一個(gè)特點(diǎn)是在收集結(jié)果的過程中將匹配度低的結(jié)果自動(dòng)過濾掉了。這也是和數(shù)據(jù)庫應(yīng)用需要將搜索的結(jié)果全部返回不同之處。

          我的一些嘗試

          • 支持中文的Tokenizer:這里有2個(gè)版本,一個(gè)是通過JavaCC生成的,對(duì)CJK部分按一個(gè)字符一個(gè)TOKEN索引,另外一個(gè)是從SimpleTokenizer改寫的,對(duì)英文支持?jǐn)?shù)字和字母TOKEN,對(duì)中文按迭代索引。
          • 基于XML數(shù)據(jù)源的索引器:XMLIndexer,因此所有數(shù)據(jù)源只要能夠按照DTD轉(zhuǎn)換成指定的XML,就可以用XMLIndxer進(jìn)行索引了。
          • 根據(jù)某個(gè)字段排序:按記錄索引順序排序結(jié)果的搜索器:IndexOrderSearcher,因此如果需要讓搜索結(jié)果根據(jù)某個(gè)字段排序,可以讓數(shù)據(jù)源先按某個(gè)字段排好序(比如:PriceField),這樣索引后,然后在利用這個(gè)按記錄的ID順序檢索的搜索器,結(jié)果就是相當(dāng)于是那個(gè)字段排序的結(jié)果了。

          從Lucene學(xué)到更多

          Luene的確是一個(gè)面對(duì)對(duì)象設(shè)計(jì)的典范

          • 所有的問題都通過一個(gè)額外抽象層來方便以后的擴(kuò)展和重用:你可以通過重新實(shí)現(xiàn)來達(dá)到自己的目的,而對(duì)其他模塊而不需要;
          • 簡單的應(yīng)用入口Searcher, Indexer,并調(diào)用底層一系列組件協(xié)同的完成搜索任務(wù);
          • 所有的對(duì)象的任務(wù)都非常專一:比如搜索過程:QueryParser分析將查詢語句轉(zhuǎn)換成一系列的精確查詢的組合(Query),通過底層的索引讀取結(jié)構(gòu)IndexReader進(jìn)行索引的讀取,并用相應(yīng)的打分器給搜索結(jié)果進(jìn)行打分/排序等。所有的功能模塊原子化程度非常高,因此可以通過重新實(shí)現(xiàn)而不需要修改其他模塊。?
          • 除了靈活的應(yīng)用接口設(shè)計(jì),Lucene還提供了一些適合大多數(shù)應(yīng)用的語言分析器實(shí)現(xiàn)(SimpleAnalyser,StandardAnalyser),這也是新用戶能夠很快上手的重要原因之一。

          這些優(yōu)點(diǎn)都是非常值得在以后的開發(fā)中學(xué)習(xí)借鑒的。作為一個(gè)通用工具包,Lunece的確給予了需要將全文檢索功能嵌入到應(yīng)用中的開發(fā)者很多的便利。

          此外,通過對(duì)Lucene的學(xué)習(xí)和使用,我也更深刻地理解了為什么很多數(shù)據(jù)庫優(yōu)化設(shè)計(jì)中要求,比如:

          • 盡可能對(duì)字段進(jìn)行索引來提高查詢速度,但過多的索引會(huì)對(duì)數(shù)據(jù)庫表的更新操作變慢,而對(duì)結(jié)果過多的排序條件,實(shí)際上往往也是性能的殺手之一。
          • 很多商業(yè)數(shù)據(jù)庫對(duì)大批量的數(shù)據(jù)插入操作會(huì)提供一些優(yōu)化參數(shù),這個(gè)作用和索引器的merge_factor的作用是類似的,
          • 20%/80%原則:查的結(jié)果多并不等于質(zhì)量好,尤其對(duì)于返回結(jié)果集很大,如何優(yōu)化這頭幾十條結(jié)果的質(zhì)量往往才是最重要的。
          • 盡可能讓應(yīng)用從數(shù)據(jù)庫中獲得比較小的結(jié)果集,因?yàn)榧词箤?duì)于大型數(shù)據(jù)庫,對(duì)結(jié)果集的隨機(jī)訪問也是一個(gè)非常消耗資源的操作。

          參考資料:

          Apache: Lucene Project
          http://jakarta.apache.org/lucene/
          Lucene開發(fā)/用戶郵件列表歸檔
          Lucene-dev@jakarta.apache.org
          Lucene-user@jakarta.apache.org

          The Lucene search engine: Powerful, flexible, and free
          http://www.javaworld.com/javaworld/jw-09-2000/jw-0915-Lucene_p.html

          Lucene Tutorial
          http://www.darksleep.com/puff/lucene/lucene.html

          Notes on distributed searching with Lucene
          http://home.clara.net/markharwood/lucene/

          中文語言的切分詞
          http://www.google.com/search?sourceid=navclient&hl=zh-CN&q=chinese+word+segment

          搜索引擎工具介紹
          http://searchtools.com/

          Lucene作者Cutting的幾篇論文和專利
          http://lucene.sourceforge.net/publications.html?

          Lucene的.NET實(shí)現(xiàn):dotLucene
          http://sourceforge.net/projects/dotlucene/

          Lucene作者Cutting的另外一個(gè)項(xiàng)目:基于Java的搜索引擎Nutch
          http://www.nutch.org/ ? http://sourceforge.net/projects/nutch/

          關(guān)于基于詞表和N-Gram的切分詞比較
          http://china.nikkeibp.co.jp/cgi-bin/china/news/int/int200302100112.html

          2005-01-08 Cutting在Pisa大學(xué)做的關(guān)于Lucene的講座:非常詳細(xì)的Lucene架構(gòu)解說

          posted @ 2006-09-06 13:34 boddi 閱讀(448) | 評(píng)論 (0)編輯 收藏

          僅列出標(biāo)題
          共3頁: 1 2 3 下一頁 
          主站蜘蛛池模板: 辉南县| 武乡县| 洛阳市| 定日县| 沅江市| 五华县| 临城县| 呼图壁县| 赤城县| 伊吾县| 曲麻莱县| 东丽区| 贞丰县| 吉安市| 怀远县| 凉山| 疏勒县| 天峨县| 故城县| 台北市| 蓬安县| 文安县| 竹溪县| 昭苏县| 乌兰察布市| 天门市| 黄大仙区| 德清县| 霍山县| 罗江县| 榆中县| 保亭| 鄂伦春自治旗| 读书| 娄烦县| 深泽县| 大竹县| 平舆县| 肇源县| 遵化市| 长武县|