jhengfei
          愛JAVA,愛生活

          2007年2月28日

          北北 發(fā)表于 2006-8-21 20:46:15

          前言:

          半年前我對正則表達式產(chǎn)生了興趣,在網(wǎng)上查找過不少資料,看過不少的教程,最后在使用一個正則表達式工具RegexBuddy時發(fā)現(xiàn)他的教程寫的非常好,可以說是我目前見過最好的正則表達式教程。于是一直想把他翻譯過來。這個愿望直到這個五一長假才得以實現(xiàn),結果就有了這篇文章。關于本文的名字,使用“深入淺出”似乎已經(jīng)太俗。但是通讀原文以后,覺得只有用“深入淺出”才能準確的表達出該教程給我的感受,所以也就不能免俗了。

          本文是Jan Goyvaerts為RegexBuddy寫的教程的譯文,版權歸原作者所有,歡迎轉載。但是為了尊重原作者和譯者的勞動,請注明出處!謝謝!

          ?1.什么是正則表達式

          基本說來,正則表達式是一種用來描述一定數(shù)量文本的模式。Regex代表Regular Express。本文將用<<regex>>來表示一段具體的正則表達式。

          一段文本就是最基本的模式,簡單的匹配相同的文本。

          ?2.不同的正則表達式引擎

          正則表達式引擎是一種可以處理正則表達式的軟件。通常,引擎是更大的應用程序的一部分。在軟件世界,不同的正則表達式并不互相兼容。本教程會集中討論Perl 5 類型的引擎,因為這種引擎是應用最廣泛的引擎。同時我們也會提到一些和其他引擎的區(qū)別。許多近代的引擎都很類似,但不完全一樣。例如.NET正則庫,JDK正則包。

          ?3.文字符號

          最基本的正則表達式由單個文字符號組成。如<<a>>,它將匹配字符串中第一次出現(xiàn)的字符“a”。如對字符串“Jack is a boy”。“J”后的“a”將被匹配。而第二個“a”將不會被匹配。

          正則表達式也可以匹配第二個“a”,這必須是你告訴正則表達式引擎從第一次匹配的地方開始搜索。在文本編輯器中,你可以使用“查找下一個”。在編程語言中,會有一個函數(shù)可以使你從前一次匹配的位置開始繼續(xù)向后搜索。

          類似的,<<cat>>會匹配“About cats and dogs”中的“cat”。這等于是告訴正則表達式引擎,找到一個<<c>>,緊跟一個<<a>>,再跟一個<<t>>。

          要注意,正則表達式引擎缺省是大小寫敏感的。除非你告訴引擎忽略大小寫,否則<<cat>>不會匹配“Cat”。

          ?· 特殊字符

          對于文字字符,有11個字符被保留作特殊用途。他們是:

          [ ] \ ^ $ . | ? * + ( )

          這些特殊字符也被稱作元字符。

          如果你想在正則表達式中將這些字符用作文本字符,你需要用反斜杠“\”對其進行換碼 (escape)。例如你想匹配“1+1=2”,正確的表達式為<<1\+1=2>>.

          需要注意的是,<<1+1=2>>也是有效的正則表達式。但它不會匹配“1+1=2”,而會匹配“123+111=234”中的“111=2”。因為“+”在這里表示特殊含義(重復1次到多次)。

          在編程語言中,要注意,一些特殊的字符會先被編譯器處理,然后再傳遞給正則引擎。因此正則表達式<<1\+2=2>>在C++中要寫成“1\\+1=2”。為了匹配“C:\temp”,你要用正則表達式<<C:\\temp>>。而在C++中,正則表達式則變成了“C:\\\\temp”。

          ?·不可顯示字符

          可以使用特殊字符序列來代表某些不可顯示字符:

          <<\t>>代表Tab(0x09)

          <<\r>>代表回車符(0x0D)

          <<\n>>代表換行符(0x0A)

          要注意的是Windows中文本文件使用“\r\n”來結束一行而Unix使用“\n”。

          ?4.正則表達式引擎的內部工作機制

          知道正則表達式引擎是如何工作的有助于你很快理解為何某個正則表達式不像你期望的那樣工作。

          有兩種類型的引擎:文本導向(text-directed)的引擎和正則導向(regex-directed)的引擎。Jeffrey Friedl把他們稱作DFA和NFA引擎。本文談到的是正則導向的引擎。這是因為一些非常有用的特性,如“惰性”量詞(lazy quantifiers)和反向引用(backreferences),只能在正則導向的引擎中實現(xiàn)。所以毫不意外這種引擎是目前最流行的引擎。

          你可以輕易分辨出所使用的引擎是文本導向還是正則導向。如果反向引用或“惰性”量詞被實現(xiàn),則可以肯定你使用的引擎是正則導向的。你可以作如下測試:將正則表達式<<regex|regex not>>應用到字符串“regex not”。如果匹配的結果是regex,則引擎是正則導向的。如果結果是regex not,則是文本導向的。因為正則導向的引擎是“猴急”的,它會很急切的進行表功,報告它找到的第一個匹配 。

          ?·正則導向的引擎總是返回最左邊的匹配

          這是需要你理解的很重要的一點:即使以后有可能發(fā)現(xiàn)一個“更好”的匹配,正則導向的引擎也總是返回最左邊的匹配。

          當把<<cat>>應用到“He captured a catfish for his cat”,引擎先比較<<c>>和“H”,結果失敗了。于是引擎再比較<<c>>和“e”,也失敗了。直到第四個字符,<<c>>匹配了“c”。<<a>>匹配了第五個字符。到第六個字符<<t>>沒能匹配“p”,也失敗了。引擎再繼續(xù)從第五個字符重新檢查匹配性。直到第十五個字符開始,<<cat>>匹配上了“catfish”中的“cat”,正則表達式引擎急切的返回第一個匹配的結果,而不會再繼續(xù)查找是否有其他更好的匹配。

          ?5.字符集

          字符集是由一對方括號“[]”括起來的字符集合。使用字符集,你可以告訴正則表達式引擎僅僅匹配多個字符中的一個。如果你想匹配一個“a”或一個“e”,使用<<[ae]>>。你可以使用<<gr[ae]y>>匹配gray或grey。這在你不確定你要搜索的字符是采用美國英語還是英國英語時特別有用。相反,<<gr[ae]y>>將不會匹配graay或graey。字符集中的字符順序并沒有什么關系,結果都是相同的。

          你可以使用連字符“-”定義一個字符范圍作為字符集。<<[0-9]>>匹配0到9之間的單個數(shù)字。你可以使用不止一個范圍。<<[0-9a-fA-F] >>匹配單個的十六進制數(shù)字,并且大小寫不敏感。你也可以結合范圍定義與單個字符定義。<<[0-9a-fxA-FX]>>匹配一個十六進制數(shù)字或字母X。再次強調一下,字符和范圍定義的先后順序對結果沒有影響。

          ?·字符集的一些應用

          查找一個可能有拼寫錯誤的單詞,比如<<sep[ae]r[ae]te>> 或 <<li[cs]en[cs]e>>。

          查找程序語言的標識符,<<A-Za-z_][A-Za-z_0-9]*>>。(*表示重復0或多次)

          查找C風格的十六進制數(shù)<<0[xX][A-Fa-f0-9]+>>。(+表示重復一次或多次)

          ?·取反字符集

          在左方括號“[”后面緊跟一個尖括號“^”,將會對字符集取反。結果是字符集將匹配任何不在方括號中的字符。不像“.”,取反字符集是可以匹配回車換行符的。

          需要記住的很重要的一點是,取反字符集必須要匹配一個字符。<<q[^u]>>并不意味著:匹配一個q,后面沒有u跟著。它意味著:匹配一個q,后面跟著一個不是u的字符。所以它不會匹配“Iraq”中的q,而會匹配“Iraq is a country”中的q和一個空格符。事實上,空格符是匹配中的一部分,因為它是一個“不是u的字符”。

          如果你只想匹配一個q,條件是q后面有一個不是u的字符,我們可以用后面將講到的向前查看來解決。

          ?·字符集中的元字符

          需要注意的是,在字符集中只有4個 字符具有特殊含義。它們是:“] \ ^ -”。“]”代表字符集定義的結束;“\”代表轉義;“^”代表取反;“-”代表范圍定義。其他常見的元字符在字符集定義內部都是正常字符,不需要轉義。例如,要搜索星號*或加號+,你可以用<<[+*]>>。當然,如果你對那些通常的元字符進行轉義,你的正則表達式一樣會工作得很好,但是這會降低可讀性。

          在字符集定義中為了將反斜杠“\”作為一個文字字符而非特殊含義的字符,你需要用另一個反斜杠對它進行轉義。<<[\\x]>>將會匹配一個反斜杠和一個X。“]^-”都可以用反斜杠進行轉義,或者將他們放在一個不可能使用到他們特殊含義的位置。我們推薦后者,因為這樣可以增加可讀性。比如對于字符“^”,將它放在除了左括號“[”后面的位置,使用的都是文字字符含義而非取反含義。如<<[x^]>>會匹配一個x或^。<<[]x]>>會匹配一個“]”或“x”。<<[-x]>>或<<[x-]>>都會匹配一個“-”或“x”。

          ?·字符集的簡寫

          因為一些字符集非常常用,所以有一些簡寫方式。

          <<\d>>代表<<[0-9]>>;

          <<\w>>代表單詞字符。這個是隨正則表達式實現(xiàn)的不同而有些差異。絕大多數(shù)的正則表達式實現(xiàn)的單詞字符集都包含了<<A-Za-z0-9_]>>。

          <<\s>>代表“白字符”。這個也是和不同的實現(xiàn)有關的。在絕大多數(shù)的實現(xiàn)中,都包含了空格符和Tab符,以及回車換行符<<\r\n>>。

          字符集的縮寫形式可以用在方括號之內或之外。<<\s\d>>匹配一個白字符后面緊跟一個數(shù)字。<<[\s\d]>>匹配單個白字符或數(shù)字。<<[\da-fA-F]>>將匹配一個十六進制數(shù)字。

          取反字符集的簡寫

          <<[\S]>> = <<[^\s]>>

          <<[\W]>> = <<[^\w]>>

          <<[\D]>> = <<[^\d]>>

          ·字符集的重復

          如果你用“?*+”操作符來重復一個字符集,你將會重復整個字符集。而不僅是它匹配的那個字符。正則表達式<<[0-9]+>>會匹配837以及222。

          如果你僅僅想重復被匹配的那個字符,可以用向后引用達到目的。我們以后將講到向后引用。

          ?6.使用?*或+ 進行重復

          ?:告訴引擎匹配前導字符0次或一次。事實上是表示前導字符是可選的。

          +:告訴引擎匹配前導字符1次或多次

          *:告訴引擎匹配前導字符0次或多次

          <[A-Za-z][A-Za-z0-9]*>匹配沒有屬性的HTML標簽,“<”以及“>”是文字符號。第一個字符集匹配一個字母,第二個字符集匹配一個字母或數(shù)字。

          我們似乎也可以用<[A-Za-z0-9]+>。但是它會匹配<1>。但是這個正則表達式在你知道你要搜索的字符串不包含類似的無效標簽時還是足夠有效的。

          ?·限制性重復

          許多現(xiàn)代的正則表達式實現(xiàn),都允許你定義對一個字符重復多少次。詞法是:{min,max}。min和max都是非負整數(shù)。如果逗號有而max被忽略了,則max沒有限制。如果逗號和max都被忽略了,則重復min次。

          因此{0,}和*一樣,{1,}和+ 的作用一樣。

          你可以用<<\b[1-9][0-9]{3}\b>>匹配1000~9999之間的數(shù)字(“\b”表示單詞邊界)。<<\b[1-9][0-9]{2,4}\b>>匹配一個在100~99999之間的數(shù)字。

          ?·注意貪婪性

          假設你想用一個正則表達式匹配一個HTML標簽。你知道輸入將會是一個有效的HTML文件,因此正則表達式不需要排除那些無效的標簽。所以如果是在兩個尖括號之間的內容,就應該是一個HTML標簽。

          許多正則表達式的新手會首先想到用正則表達式<< <.+> >>,他們會很驚訝的發(fā)現(xiàn),對于測試字符串,“This is a <EM>first</EM> test”,你可能期望會返回<EM>,然后繼續(xù)進行匹配的時候,返回</EM>。

          但事實是不會。正則表達式將會匹配“<EM>first</EM>”。很顯然這不是我們想要的結果。原因在于“+”是貪婪的。也就是說,“+”會導致正則表達式引擎試圖盡可能的重復前導字符。只有當這種重復會引起整個正則表達式匹配失敗的情況下,引擎會進行回溯。也就是說,它會放棄最后一次的“重復”,然后處理正則表達式余下的部分。

          和“+”類似,“?*”的重復也是貪婪的。

          ?·深入正則表達式引擎內部

          讓我們來看看正則引擎如何匹配前面的例子。第一個記號是“<”,這是一個文字符號。第二個符號是“.”,匹配了字符“E”,然后“+”一直可以匹配其余的字符,直到一行的結束。然后到了換行符,匹配失敗(“.”不匹配換行符)。于是引擎開始對下一個正則表達式符號進行匹配。也即試圖匹配“>”。到目前為止,“<.+”已經(jīng)匹配了“<EM>first</EM> test”。引擎會試圖將“>”與換行符進行匹配,結果失敗了。于是引擎進行回溯。結果是現(xiàn)在“<.+”匹配“<EM>first</EM> tes”。于是引擎將“>”與“t”進行匹配。顯然還是會失敗。這個過程繼續(xù),直到“<.+”匹配“<EM>first</EM”,“>”與“>”匹配。于是引擎找到了一個匹配“<EM>first</EM>”。記住,正則導向的引擎是“急切的”,所以它會急著報告它找到的第一個匹配。而不是繼續(xù)回溯,即使可能會有更好的匹配,例如“<EM>”。所以我們可以看到,由于“+”的貪婪性,使得正則表達式引擎返回了一個最左邊的最長的匹配。

          ?·用懶惰性取代貪婪性

          一個用于修正以上問題的可能方案是用“+”的惰性代替貪婪性。你可以在“+”后面緊跟一個問號“?”來達到這一點。“*”,“{}”和“?”表示的重復也可以用這個方案。因此在上面的例子中我們可以使用“<.+?>”。讓我們再來看看正則表達式引擎的處理過程。

          再一次,正則表達式記號“<”會匹配字符串的第一個“<”。下一個正則記號是“.”。這次是一個懶惰的“+”來重復上一個字符。這告訴正則引擎,盡可能少的重復上一個字符。因此引擎匹配“.”和字符“E”,然后用“>”匹配“M”,結果失敗了。引擎會進行回溯,和上一個例子不同,因為是惰性重復,所以引擎是擴展惰性重復而不是減少,于是“<.+”現(xiàn)在被擴展為“<EM”。引擎繼續(xù)匹配下一個記號“>”。這次得到了一個成功匹配。引擎于是報告“<EM>”是一個成功的匹配。整個過程大致如此。

          ?·惰性擴展的一個替代方案

          我們還有一個更好的替代方案。可以用一個貪婪重復與一個取反字符集:“<[^>]+>”。之所以說這是一個更好的方案在于使用惰性重復時,引擎會在找到一個成功匹配前對每一個字符進行回溯。而使用取反字符集則不需要進行回溯。

          最后要記住的是,本教程僅僅談到的是正則導向的引擎。文本導向的引擎是不回溯的。但是同時他們也不支持惰性重復操作。

          ?7.使用“.”匹配幾乎任意字符

          在正則表達式中,“.”是最常用的符號之一。不幸的是,它也是最容易被誤用的符號之一。

          “.”匹配一個單個的字符而不用關心被匹配的字符是什么。唯一的例外是新行符。在本教程中談到的引擎,缺省情況下都是不匹配新行符的。因此在缺省情況下,“.”等于是字符集[^\n\r](Window)或[^\n]( Unix)的簡寫。

          這個例外是因為歷史的原因。因為早期使用正則表達式的工具是基于行的。它們都是一行一行的讀入一個文件,將正則表達式分別應用到每一行上去。在這些工具中,字符串是不包含新行符的。因此“.”也就從不匹配新行符。

          現(xiàn)代的工具和語言能夠將正則表達式應用到很大的字符串甚至整個文件上去。本教程討論的所有正則表達式實現(xiàn)都提供一個選項,可以使“.”匹配所有的字符,包括新行符。在RegexBuddy, EditPad Pro或PowerGREP等工具中,你可以簡單的選中“點號匹配新行符”。在Perl中,“.”可以匹配新行符的模式被稱作“單行模式”。很不幸,這是一個很容易混淆的名詞。因為還有所謂“多行模式”。多行模式只影響行首行尾的錨定(anchor),而單行模式只影響“.”。

          其他語言和正則表達式庫也采用了Perl的術語定義。當在.NET Framework中使用正則表達式類時,你可以用類似下面的語句來激活單行模式:Regex.Match(“string”,”regex”,RegexOptions.SingleLine)

          ?·保守的使用點號“.”

          點號可以說是最強大的元字符。它允許你偷懶:用一個點號,就能匹配幾乎所有的字符。但是問題在于,它也常常會匹配不該匹配的字符。

          我會以一個簡單的例子來說明。讓我們看看如何匹配一個具有“mm/dd/yy”格式的日期,但是我們想允許用戶來選擇分隔符。很快能想到的一個方案是<<\d\d.\d\d.\d\d>>。看上去它能匹配日期“02/12/03”。問題在于02512703也會被認為是一個有效的日期。

          <<\d\d[-/.]\d\d[-/.]\d\d>>看上去是一個好一點的解決方案。記住點號在一個字符集里不是元字符。這個方案遠不夠完善,它會匹配“99/99/99”。而<<[0-1]\d[-/.][0-3]\d[-/.]\d\d>>又更進一步。盡管他也會匹配“19/39/99”。你想要你的正則表達式達到如何完美的程度取決于你想達到什么樣的目的。如果你想校驗用戶輸入,則需要盡可能的完美。如果你只是想分析一個已知的源,并且我們知道沒有錯誤的數(shù)據(jù),用一個比較好的正則表達式來匹配你想要搜尋的字符就已經(jīng)足夠。

          ?8.字符串開始和結束的錨定

          錨定和一般的正則表達式符號不同,它不匹配任何字符。相反,他們匹配的是字符之前或之后的位置。“^”匹配一行字符串第一個字符前的位置。<<^a>>將會匹配字符串“abc”中的a。<<^b>>將不會匹配“abc”中的任何字符。

          類似的,$匹配字符串中最后一個字符的后面的位置。所以<<c$>>匹配“abc”中的c。

          ?·錨定的應用

          在編程語言中校驗用戶輸入時,使用錨定是非常重要的。如果你想校驗用戶的輸入為整數(shù),用<<^\d+$>>。

          用戶輸入中,常常會有多余的前導空格或結束空格。你可以用<<^\s*>>和<<\s*$>>來匹配前導空格或結束空格。

          ?·使用“^”和“$”作為行的開始和結束錨定

          如果你有一個包含了多行的字符串。例如:“first line\n\rsecond line”(其中\(zhòng)n\r表示一個新行符)。常常需要對每行分別處理而不是整個字符串。因此,幾乎所有的正則表達式引擎都提供一個選項,可以擴展這兩種錨定的含義。“^”可以匹配字串的開始位置(在f之前),以及每一個新行符的后面位置(在\n\r和s之間)。類似的,$會匹配字串的結束位置(最后一個e之后),以及每個新行符的前面(在e與\n\r之間)。

          在.NET中,當你使用如下代碼時,將會定義錨定匹配每一個新行符的前面和后面位置:Regex.Match("string", "regex", RegexOptions.Multiline)

          應用:string str = Regex.Replace(Original, "^", "> ", RegexOptions.Multiline)--將會在每行的行首插入“> ”。

          · 絕對錨定

          <<\A>>只匹配整個字符串的開始位置,<<\Z>>只匹配整個字符串的結束位置。即使你使用了“多行模式”,<<\A>>和<<\Z>>也從不匹配新行符。

          即使\Z和$只匹配字符串的結束位置,仍然有一個例外的情況。如果字符串以新行符結束,則\Z和$將會匹配新行符前面的位置,而不是整個字符串的最后面。這個“改進”是由Perl引進的,然后被許多的正則表達式實現(xiàn)所遵循,包括Java,.NET等。如果應用<<^[a-z]+$>>到“joe\n”,則匹配結果是“joe”而不是“joe\n”。

          posted @ 2007-02-28 18:35 點滴鑄就輝煌 閱讀(207) | 評論 (0)編輯 收藏

          2006年12月19日

          radic 發(fā)表于 2006-12-15 12:24:05
          作者:Radic???? 來源:sun
          評論數(shù):5 點擊數(shù):592???? 投票總得分:6 投票總人次:2
          關鍵字:Java;安全編碼

          摘要:

          本文是來自Sun官方站點的一篇關于如何編寫安全的Java代碼的指南,開發(fā)者在編寫一般代碼時,可以參照本文的指南
          本文是來自Sun官方站點的一篇關于如何編寫安全的Java代碼的指南,開發(fā)者在編寫一般代碼時,可以參照本文的指南:

          ?????????靜態(tài)字段
          ?????????縮小作用域
          ?????????公共方法和字段
          ?????????保護包
          ?????????equals方法
          ?????????如果可能使對象不可改變
          ?????????不要返回指向包含敏感數(shù)據(jù)的內部數(shù)組的引用
          ?????????不要直接存儲用戶提供的數(shù)組
          ?????????序列化
          ?????????原生函數(shù)
          ?????????清除敏感信息


          靜態(tài)字段
          ?????????避免使用非final的公共靜態(tài)變量
          應盡可能地避免使用非final公共靜態(tài)變量,因為無法判斷代碼有無權限改變這些變量值。
          ?????????一般地,應謹慎使用易變的靜態(tài)狀態(tài),因為這可能導致設想中相互獨立的子系統(tǒng)之間發(fā)生不可預知的交互。

          縮小作用域
          作為一個慣例,盡可能縮小方法和字段的作用域。檢查包訪問權限的成員能否改成私有的,保護類型的成員可否改成包訪問權限的或者私有的,等等。

          公共方法/字段
          避免使用公共變量,而是使用訪問器方法訪問這些變量。用這種方式,如果需要,可能增加集中安全控制。
          對于任何公共方法,如果它們能夠訪問或修改任何敏感內部狀態(tài),務必使它們包含安全控制。
          參考如下代碼段,該代碼段中不可信任代碼可能設置TimeZone的值:
          private static TimeZone??defaultZone = null;

          ??????public static synchronized void setDefault(TimeZone zone)
          ??????{
          ??????????defaultZone = zone;
          ??????}


          保護包
          有時需要在全局防止包被不可信任代碼訪問,本節(jié)描述了一些防護技術:
          ?????????防止包注入:如果不可信任代碼想要訪問類的包保護成員,可以嘗試在被攻擊的包內定義自己的新類用以獲取這些成員的訪問權。防止這類攻擊的方式有兩種:
          1.????????通過向java.security.properties文件中加入如下文字防止包內被注入惡意類。
          ??????????... 
          package.definition=Package#1 [,Package#2,...,Package#n]

          ...


          這會導致當試圖在包內定義新類時類裝載器的defineClass方法會拋出異常,除非賦予代碼一下權限:
          ... 
          RuntimePermission("defineClassInPackage."+package)

          ...


          2.????????另一種方式是通過將包內的類加入到封裝的Jar文件里。
          (參看http://java.sun.com/j2se/sdk/1.2/docs/guide/extensions/spec.html)
          ????通過使用這種技巧,代碼無法獲得擴展包的權限,因此也無須修改java.security.properties文件。
          ?????????防止包訪問:通過限制包訪問并僅賦予特定代碼訪問權限防止不可信任代碼對包成員的訪問。通過向java.security.properties文件中加入如下文字可以達到這一目的:
          ??????... 
          package.access=Package#1 [,Package#2,...,Package#n]

          ...


          這會導致當試圖在包內定義新類時類裝載器的defineClass方法會拋出異常,除非賦予代碼一下權限:
          ... 
          RuntimePermission("defineClassInPackage."+package)

          ...


          如果可能使對象不可改變
          如果可能,使對象不可改變。如果不可能,使得它們可以被克隆并返回一個副本。如果返回的對象是數(shù)組、向量或哈希表等,牢記這些對象不能被改變,調用者修改這些對象的內容可能導致安全漏洞。此外,因為不用上鎖,不可改變性能夠提高并發(fā)性。參考Clear sensitive information了解該慣例的例外情況。

          不要返回指向包含敏感數(shù)據(jù)的內部數(shù)組的引用
          該慣例僅僅是不可變慣例的變型,在這兒提出是因為常常在這里犯錯。即使數(shù)組中包含不可變的對象(如字符串),也要返回一個副本這樣調用者不能修改數(shù)組中的字符串。不要傳回一個數(shù)組,而是數(shù)組的拷貝。

          不要直接在用戶提供的數(shù)組里存儲
          該慣例僅僅是不可變慣例的另一個變型。使用對象數(shù)組的構造器和方法,比如說PubicKey數(shù)組,應當在將數(shù)組存儲到內部之前克隆數(shù)組,而不是直接將數(shù)組引用賦給同樣類型的內部變量。缺少這個警惕,用戶對外部數(shù)組做得任何變動(在使用討論中的構造器創(chuàng)建對象后)可能意外地更改對象的內部狀態(tài),即使該對象可能是無法改變的

          序列化
          當對對象序列化時,直到它被反序列化,它不在Java運行時環(huán)境的控制之下,因此也不在Java平臺提供的安全控制范圍內。
          在實現(xiàn)Serializable時務必將以下事宜牢記在心:
          ?????????transient

          在包含系統(tǒng)資源的直接句柄和相對地址空間信息的字段前使用transient關鍵字。 如果資源,如文件句柄,不被聲明為transient,該對象在序列化狀態(tài)下可能會被修改,從而使得被反序列化后獲取對資源的不當訪問。

          ?????????特定類的序列化/反序列化方法

          為了確保反序列化對象不包含違反一些不變量集合的狀態(tài),類應該定義自己的反序列化方法并使用ObjectInputValidation接口驗證這些變量。

          如果一個類定義了自己的序列化方法,它就不能向任何DataInput/DataOuput方法傳遞內部數(shù)組。所有的DataInput/DataOuput方法都能被重寫。注意默認序列化不會向DataInput/DataOuput字節(jié)數(shù)組方法暴露私有字節(jié)數(shù)組字段。

          如果Serializable類直接向DataOutput(write(byte [] b))方法傳遞了一個私有數(shù)組,那么黑客可以創(chuàng)建ObjectOutputStream的子類并覆蓋write(byte [] b)方法,這樣他可以訪問并修改私有數(shù)組。下面示例說明了這個問題。
          你的類:
          ??????public class YourClass implements Serializable {

          ????????????private byte [] internalArray;
          ....
          private synchronized void writeObject(ObjectOutputStream stream) {
          ...

          ?????????????? stream.write(internalArray);
          ????????????????...
          }
          }


          黑客代碼

          ?????? public class HackerObjectOutputStream extends ObjectOutputStream{
          ????????????public void write (byte [] b) {
          ?????????????? Modify b
          ??????}
          }
          ...
          ???????????? YourClass yc = new YourClass();
          ??????????????...

          ???????????? HackerObjectOutputStream hoos = new HackerObjectOutputStream();

          ??????????????hoos.writeObject(yc);


          ?????????字節(jié)流加密

          保護虛擬機外的字節(jié)流的另一方式是對序列化包產(chǎn)生的流進行加密。字節(jié)流加密防止解碼或讀取被序列化的對象的私有狀態(tài)。如果決定加密,應該管理好密鑰,密鑰的存放地點以及將密鑰交付給反序列化程序的方式等。

          ?????????需要提防的其他事宜

          如果不可信任代碼無法創(chuàng)建對象,務必確保不可信任代碼也不能反序列化對象。切記對對象反序列化是創(chuàng)建對象的另一途徑。
          比如說,如果一個applet創(chuàng)建了一個frame,在該frame上創(chuàng)建了警告標簽。如果該frame被另一應用程序序列化并被一個applet反序列化,務必使該frame出現(xiàn)時帶有同一個警告標簽。

          原生方法
          應從以下幾個方面檢查原生方法:
          ?????????它們返回什么
          ?????????它們需要什么參數(shù)
          ?????????它們是否繞過了安全檢查
          ?????????它們是否是公共的,私有的等
          ?????????它們是否包含能繞過包邊界的方法調用,從而繞過包保護

          清除敏感信息
          當保存敏感信息時,如機密,盡量保存在如數(shù)組這樣的可變數(shù)據(jù)類型中,而不是保存在字符串這樣的不可變對象中,這樣使得敏感信息可以盡早顯式地被清除。不要指望Java平臺的自動垃圾回收來做這種清除,因為回收器可能不會清除這段內存,或者很久后才會回收。盡早清除信息使得來自虛擬機外部的堆檢查攻擊變得困難。
          posted @ 2006-12-19 09:29 點滴鑄就輝煌 閱讀(217) | 評論 (0)編輯 收藏

          2006年11月14日

          MySQL從3.23.15版本以后提供數(shù)據(jù)庫復制功能。利用該功能可以實現(xiàn)兩個數(shù)據(jù)庫同步,主從模式,互相備份模式的功能

          數(shù)據(jù)庫同步復制功能的設置都在mysql的設置文件中體現(xiàn)。mysql的配置文件(一般是my.cnf),在unix環(huán)境下在/etc/mysql/my.cnf 或者在mysql用戶的home目錄下的my.cnf。

          window環(huán)境中,如果c:根目錄下有my.cnf文件則取該配置文件。當運行mysql的winmysqladmin.exe工具時候,該工具會把c:根目錄下的my.cnf 命名為mycnf.bak。并在winnt目錄下創(chuàng)建my.ini。mysql服務器啟動時候會讀該配置文件。所以可以把my.cnf中的內容拷貝到my.ini文件中,用my.ini文件作為mysql服務器的配置文件。

          設置方法:

          設置范例環(huán)境:

          操作系統(tǒng):window2000 professional

          mysql:4.0.4-beta-max-nt-log

          A ip:10.10.10.22

          B ip:10.10.10.53

          A:設置

          1.增加一個用戶最為同步的用戶帳號:

          																GRANT FILE ON *.* TO backup@'10.10.10.53' IDENTIFIED BY ‘1234’
          														

          2.增加一個數(shù)據(jù)庫作為同步數(shù)據(jù)庫:

          																create database backup
          														

          B:設置

          1.增加一個用戶最為同步的用戶帳號:

          																GRANT FILE ON *.* TO backup@'10.10.10.22' IDENTIFIED BY ‘1234’
          														

          2.增加一個數(shù)據(jù)庫作為同步數(shù)據(jù)庫:

          																create database backup
          														

          主從模式:A->B

          A為master

          修改A mysql的my.ini文件。在mysqld配置項中加入下面配置:

          server-id=1log-bin#設置需要記錄log 可以設置log-bin=c:mysqlbakmysqllog 設置日志文件的目錄,#其中mysqllog是日志文件的名稱,mysql將建立不同擴展名,文件名為mysqllog的幾個日志文件。binlog-do-db=backup #指定需要日志的數(shù)據(jù)庫

          重起數(shù)據(jù)庫服務。

          用show master status 命令看日志情況。

          B為slave

          修改B mysql的my.ini文件。在mysqld配置項中加入下面配置:

          																server-id=2master-host=10.10.10.22master-user=backup
          														

          #同步用戶帳號

          																master-password=1234master-port=3306master-connect-retry=60
          														

          預設重試間隔60秒replicate-do-db=backup 告訴slave只做backup數(shù)據(jù)庫的更新

          重起數(shù)據(jù)庫

          用show slave status看同步配置情況。

          注意:由于設置了slave的配置信息,mysql在數(shù)據(jù)庫目錄下生成master.info,所以如有要修改相關slave的配置要先刪除該文件。否則修改的配置不能生效。

          雙機互備模式。

          如果在A加入slave設置,在B加入master設置,則可以做B->A的同步。

          在A的配置文件中 mysqld 配置項加入以下設置:

          																master-host=10.10.10.53master-user=backupmaster-password=1234replicate-do-db=
          backupmaster-connect-retry=10
          														

          在B的配置文件中 mysqld 配置項加入以下設置:

          																log-bin=c:mysqllogmysqllogbinlog-do-db=backup
          														

          注意:當有錯誤產(chǎn)生時*.err日志文件。同步的線程退出,當糾正錯誤后要讓同步機制進行工作,運行slave start

          重起AB機器,則可以實現(xiàn)雙向的熱備。

          測試:

          向B批量插入大數(shù)據(jù)量表AA(1872000)條,A數(shù)據(jù)庫每秒鐘可以更新2500條數(shù)據(jù)。

          posted @ 2006-11-14 11:29 點滴鑄就輝煌 閱讀(265) | 評論 (0)編輯 收藏

          2006年11月12日

          優(yōu)化數(shù)據(jù)庫的思想:
          ? ================
          ? 1、關鍵字段建立索引。
          ? 2、使用存儲過程,它使SQL變得更加靈活和高效。
          ? 3、備份數(shù)據(jù)庫和清除垃圾數(shù)據(jù)。
          ? 4、SQL語句語法的優(yōu)化。(可以用Sybase的SQL Expert,可惜我沒找到unexpired的
          序列號)
          ? 5、清理刪除日志。

          ? SQL語句優(yōu)化的原則: ?
          ? ==================
          ? 1、使用索引來更快地遍歷表。
          ? 缺省情況下建立的索引是非群集索引,但有時它并不是最佳的。在非群集索引下,數(shù)據(jù)在物理上隨機存放在數(shù)據(jù)頁上。合理的索引設計要建立在對各種查詢的分析和預測上。一般來說:
          ? ①.有大量重復值、且經(jīng)常有范圍查詢
          ? (between, > ,< ,> =,< =)和order by、group by發(fā)生的列,可考慮建立群集索引;
          ? ②.經(jīng)常同時存取多列,且每列都含有重復值可考慮建立組合索引;
          ? ③.組合索引要盡量使關鍵查詢形成索引覆蓋,其前導列一定是使用最頻繁的列。索引雖有助于提高性能但不是索引越多越好,恰好相反過多的索引會導致系統(tǒng)低效。用戶在表中每加進一個索引,維護索引集合就要做相應的更新工作。
          ? 2、IS NULL 與 IS NOT NULL
          ? 不能用null作索引,任何包含null值的列都將不會被包含在索引中。即使索引有多列這樣的情況下,只要這些列中有一列含有null,該列就會從索引中排除。也就是說如果某列存在空值,即使對該列建索引也不會提高性能。任何在where子句中使用is null或is not null的語句優(yōu)化器是不允許使用索引的。
          ? 3、IN和EXISTS
          ? EXISTS要遠比IN的效率高。里面關系到full table scan和range scan。幾乎將所有的IN操作符子查詢改寫為使用EXISTS的子查詢。
          ? 4、在海量查詢時盡量少用格式轉換。
          ? 5、當在SQL SERVER 2000中,如果存儲過程只有一個參數(shù),并且是OUTPUT類型的,必須在調用這個存儲過程的時候給這個參數(shù)一個初始的值,否則會出現(xiàn)調用錯誤。
          ? 6、ORDER BY和GROPU BY
          ? 使用ORDER BY和GROUP BY短語,任何一種索引都有助于SELECT的性能提高。注意如果索引列里面有NULL值,Optimizer將無法優(yōu)化。
          ? 7、任何對列的操作都將導致表掃描,它包括數(shù)據(jù)庫函數(shù)、計算表達式等等,查詢時要盡可能將操作移至等號右邊。
          ? 8、IN、OR子句常會使用工作表,使索引失效。如果不產(chǎn)生大量重復值,可以考慮把子句拆開。拆開的子句中應該包含索引。
          ? 9、SET SHOWPLAN_ALL ON 查看執(zhí)行方案。DBCC檢查數(shù)據(jù)庫數(shù)據(jù)完整性。
          ? DBCC(DataBase Consistency Checker)是一組用于驗證 SQL Server 數(shù)據(jù)庫完整性的程序。
          ? 10、慎用游標
          ? 在某些必須使用游標的場合,可考慮將符合條件的數(shù)據(jù)行轉入臨時表中,再對臨時表定義游標進行操作,這樣可使性能得到明顯提高。

          ? 總結:
          ? 所謂優(yōu)化即WHERE子句利用了索引,不可優(yōu)化即發(fā)生了表掃描或額外開銷。經(jīng)驗顯示,SQL Server性能的最大改進得益于邏輯的數(shù)據(jù)庫設計、索引設計和查詢設計方面。反過來說,最大的性能問題常常是由其中這些相同方面中的不足引起的。其實SQL優(yōu)化的實質就是在結果正確的前提下,用優(yōu)化器可以識別的語句,充份利用索引,減少表掃描的I/O次數(shù),盡量避免表搜索的發(fā)生。其實SQL的性能優(yōu)化是一個復雜的過程,上述這些只是在應用層次的一種體現(xiàn),深入研究還會涉及數(shù)據(jù)庫層的資源配置、網(wǎng)絡層的流量控制以及操作系統(tǒng)層的總體設計。?
          posted @ 2006-11-12 11:27 點滴鑄就輝煌 閱讀(241) | 評論 (0)編輯 收藏

          2006年11月11日

          關注beanaction時,查到的資料,順便做個備份

          多數(shù)IT 組織都必須解決三個主要問題:1.幫助組織減少成本 2.增加并且保持客戶 3.加快業(yè)務效率。完成這些問題一般都需要實現(xiàn)對多個業(yè)務系統(tǒng)的數(shù)據(jù)和業(yè)務邏輯的無縫訪問,也就是說,要實施系統(tǒng)集成工程,以便聯(lián)結業(yè)務流程、實現(xiàn)數(shù)據(jù)的訪問與共享。

          JpetStore 4.0是ibatis的最新示例程序,基于Struts MVC框架(注:非傳統(tǒng)Struts開發(fā)模式),以ibatis作為持久化層。該示例程序設計優(yōu)雅,層次清晰,可以學習以及作為一個高效率的編程模型參考。本文是在其基礎上,采用Spring對其中間層(業(yè)務層)進行改造。使開發(fā)量進一步減少,同時又擁有了Spring的一些好處…

          1. 前言
          JpetStore 4.0是ibatis的最新示例程序。ibatis是開源的持久層產(chǎn)品,包含SQL Maps 2.0 和 Data Access Objects 2.0 框架。JpetStore示例程序很好的展示了如何利用ibatis來開發(fā)一個典型的J2EE web應用程序。JpetStore有如下特點:

          • ibatis數(shù)據(jù)層
          • POJO業(yè)務層
          • POJO領域類
          • Struts MVC
          • JSP 表示層

          以下是本文用到的關鍵技術介紹,本文假設您已經(jīng)對Struts,SpringFramewok,ibatis有一定的了解,如果不是,請首先查閱附錄中的參考資料。

          • Struts 是目前Java Web MVC框架中不爭的王者。經(jīng)過長達五年的發(fā)展,Struts已經(jīng)逐漸成長為一個穩(wěn)定、成熟的框架,并且占有了MVC框架中最大的市場份額。但是Struts某些技術特性上已經(jīng)落后于新興的MVC框架。面對Spring MVC、Webwork2 這些設計更精密,擴展性更強的框架,Struts受到了前所未有的挑戰(zhàn)。但站在產(chǎn)品開發(fā)的角度而言,Struts仍然是最穩(wěn)妥的選擇。本文的原型例子JpetStore 4.0就是基于Struts開發(fā)的,但是不拘泥于Struts的傳統(tǒng)固定用法,例如只用了一個自定義Action類,并且在form bean類的定義上也是開創(chuàng)性的,令人耳目一新,稍后將具體剖析一下。
          • Spring Framework 實際上是Expert One-on-One J2EE Design and Development 一書中所闡述的設計思想的具體實現(xiàn)。Spring Framework的功能非常多。包含AOP、ORM、DAO、Context、Web、MVC等幾個部分組成。Web、MVC暫不用考慮,JpetStore 4.0用的是更成熟的Struts和JSP;DAO由于目前Hibernate、JDO、ibatis的流行,也不考慮,JpetStore 4.0用的就是ibatis。因此最需要用的是AOP、ORM、Context。Context中,最重要的是Beanfactory,它能將接口與實現(xiàn)分開,非常強大。目前AOP應用最成熟的還是在事務管理上。
          • ibatis 是一個功能強大實用的SQL Map工具,不同于其他ORM工具(如hibernate),它是將SQL語句映射成Java對象,而對于ORM工具,它的SQL語句是根據(jù)映射定義生成的。ibatis 以SQL開發(fā)的工作量和數(shù)據(jù)庫移植性上的讓步,為系統(tǒng)設計提供了更大的自由空間。有ibatis代碼生成的工具,可以根據(jù)DDL自動生成ibatis代碼,能減少很多工作量。

          2. JpetStore簡述

          2.1. 背景
          最初是Sun公司的J2EE petstore,其最主要目的是用于學習J2EE,但是其缺點也很明顯,就是過度設計了。接著Oracle用J2EE petstore來比較各應用服務器的性能。微軟推出了基于.Net平臺的 Pet shop,用于競爭J2EE petstore。而JpetStore則是經(jīng)過改良的基于struts的輕便框架J2EE web應用程序,相比來說,JpetStore設計和架構更優(yōu)良,各層定義清晰,使用了很多最佳實踐和模式,避免了很多"反模式",如使用存儲過程,在java代碼中嵌入SQL語句,把HTML存儲在數(shù)據(jù)庫中等等。最新版本是JpetStore 4.0。

          2.2. JpetStore開發(fā)運行環(huán)境的建立
          1、開發(fā)環(huán)境

          • Java SDK 1.4.2
          • Apache Tomcat 4.1.31
          • Eclipse-SDK-3.0.1-win32
          • HSQLDB 1.7.2

          2、Eclipse插件

          • EMF SDK 2.0.1:Eclipse建模框架,lomboz插件需要,可以使用runtime版本。
          • lomboz 3.0:J2EE插件,用來在Eclipse中開發(fā)J2EE應用程序
          • Spring IDE 1.0.3:Spring Bean配置管理插件
          • xmlbuddy_2.0.10:編輯XML,用免費版功能即可
          • tomcatPluginV3:tomcat管理插件
          • Properties Editor:編輯java的屬性文件,并可以預覽以及自動存盤為Unicode格式。免去了手工或者ANT調用native2ascii的麻煩。

          3、示例源程序

          • ibatis示例程序JpetStore 4.0 http://www.ibatis.com/jpetstore/jpetstore.html
          • 改造后的源程序(+spring)(源碼鏈接)

          2.3. 架構

          圖1 JpetStore架構圖
          圖1 JpetStore架構圖

          圖1 是JPetStore架構圖,更詳細的內容請參見JPetStore的白皮書。參照這個架構圖,讓我們稍微剖析一下源代碼,得出JpetStore 4.0的具體實現(xiàn)圖(見圖2),思路一下子就豁然開朗了。前言中提到的非傳統(tǒng)的struts開發(fā)模式,關鍵就在struts Action類和form bean類上。

          struts Action類只有一個:BeanAction。沒錯,確實是一個!與傳統(tǒng)的struts編程方式很不同。再仔細研究BeanAction類,發(fā)現(xiàn)它其實是一個通用類,利用反射原理,根據(jù)URL來決定調用formbean的哪個方法。BeanAction大大簡化了struts的編程模式,降低了對struts的依賴(與struts以及WEB容器有關的幾個類都放在com.ibatis.struts包下,其它的類都可以直接復用)。利用這種模式,我們會很容易的把它移植到新的框架如JSF,spring。

          這樣重心就轉移到form bean上了,它已經(jīng)不是普通意義上的form bean了。查看源代碼,可以看到它不僅僅有數(shù)據(jù)和校驗/重置方法,而且已經(jīng)具有了行為,從這個意義上來說,它更像一個BO(Business Object)。這就是前文講到的,BeanAction類利用反射原理,根據(jù)URL來決定調用form bean的哪個方法(行為)。form bean的這些方法的簽名很簡單,例如:


          												
           public String myActionMethod() {
             //..work
             return "success";
           }
           
          										

          方法的返回值直接就是字符串,對應的是forward的名稱,而不再是ActionForward對象,創(chuàng)建ActionForward對象的任務已經(jīng)由BeanAction類代勞了。

          另外,程序還提供了ActionContext工具類,該工具類封裝了request 、response、form parameters、request attributes、session attributes和 application attributes中的數(shù)據(jù)存取操作,簡單而線程安全,form bean類使用該工具類可以進一步從表現(xiàn)層框架解耦。

          在這里需要特別指出的是,BeanAction類是對struts擴展的一個有益嘗試,雖然提供了非常好的應用開發(fā)模式,但是它還非常新,一直在發(fā)展中。

          圖2 JpetStore 4.0具體實現(xiàn)
          圖2 JpetStore 4.0具體實現(xiàn)

          2.4. 代碼剖析
          下面就讓我們開始進一步分析JpetStore4.0的源代碼,為下面的改造鋪路。

          • BeanAction.java是唯一一個Struts action類,位于com.ibatis.struts包下。正如上文所言,它是一個通用的控制類,利用反射機制,把控制轉移到form bean的某個方法來處理。詳細處理過程參考其源代碼,簡單明晰。
          • Form bean類位于com.ibatis.jpetstore.presentation包下,命名規(guī)則為***Bean。Form bean類全部繼承于BaseBean類,而BaseBean類實際繼承于ActionForm,因此,F(xiàn)orm bean類就是Struts的 ActionForm,F(xiàn)orm bean類的屬性數(shù)據(jù)就由struts框架自動填充。而實際上,JpetStore4.0擴展了struts中ActionForm的應用: Form bean類還具有行為,更像一個BO,其行為(方法)由BeanAction根據(jù)配置(struts-config.xml)的URL來調用。雖然如此,我們還是把Form bean類定位于表現(xiàn)層。

            Struts-config.xml的配置里有3種映射方式,來告訴BeanAction把控制轉到哪個form bean對象的哪個方法來處理。

            以這個請求連接為例http://localhost/jpetstore4/shop/viewOrder.do

            1. URL Pattern

            																
                <action path="/shop/viewOrder" type="com.ibatis.struts.BeanAction"
                name="orderBean" scope="session"
                validate="false">
                <forward name="success" path="/order/ViewOrder.jsp"/>
              </action>
              
            														

            此種方式表示,控制將被轉發(fā)到"orderBean"這個form bean對象 的"viewOrder"方法(行為)來處理。方法名取"path"參數(shù)的以"/"分隔的最后一部分。

            2. Method Parameter

            																
                <action path="/shop/viewOrder" type="com.ibatis.struts.BeanAction"
                name="orderBean" parameter="viewOrder" scope="session"
                validate="false">
                <forward name="success" path="/order/ViewOrder.jsp"/>
              </action>
              
            														

            此種方式表示,控制將被轉發(fā)到"orderBean"這個form bean對象的"viewOrder"方法(行為)來處理。配置中的"parameter"參數(shù)表示form bean類上的方法。"parameter"參數(shù)優(yōu)先于"path"參數(shù)。

            3. No Method call

            																
                <action path="/shop/viewOrder" type="com.ibatis.struts.BeanAction"
                name="orderBean" parameter="*" scope="session"
                validate="false">
                <forward name="success" path="/order/ViewOrder.jsp"/>
              </action>
              
            														

            此種方式表示,form bean上沒有任何方法被調用。如果存在"name"屬性,則struts把表單參數(shù)等數(shù)據(jù)填充到form bean對象后,把控制轉發(fā)到"success"。否則,如果name為空,則直接轉發(fā)控制到"success"。

            這就相當于struts內置的org.apache.struts.actions.ForwardAction的功能

            																
             <action path="/shop/viewOrder" type="org.apache.struts.actions.ForwardAction"
                parameter="/order/ViewOrder.jsp " scope="session" validate="false">
             </action>
             
            														
          • Service類位于com.ibatis.jpetstore.service包下,屬于業(yè)務層。這些類封裝了業(yè)務以及相應的事務控制。Service類由form bean類來調用。
          • com.ibatis.jpetstore.persistence.iface包下的類是DAO接口,屬于業(yè)務層,其屏蔽了底層的數(shù)據(jù)庫操作,供具體的Service類來調用。DaoConfig類是工具類(DAO工廠類),Service類通過DaoConfig類來獲得相應的DAO接口,而不用關心底層的具體數(shù)據(jù)庫操作,實現(xiàn)了如圖2中{耦合2}的解耦。
          • com.ibatis.jpetstore.persistence.sqlmapdao包下的類是對應DAO接口的具體實現(xiàn),在JpetStore4.0中采用了ibatis來實現(xiàn)ORM。這些實現(xiàn)類繼承BaseSqlMapDao類,而BaseSqlMapDao類則繼承ibatis DAO 框架中的SqlMapDaoTemplate類。ibatis的配置文件存放在com.ibatis.jpetstore.persistence.sqlmapdao.sql目錄下。這些類和配置文件位于數(shù)據(jù)層
          • Domain類位于com.ibatis.jpetstore.domain包下,是普通的javabean。在這里用作數(shù)據(jù)傳輸對象(DTO),貫穿視圖層、業(yè)務層和數(shù)據(jù)層,用于在不同層之間傳輸數(shù)據(jù)。

          剩下的部分就比較簡單了,請看具體的源代碼,非常清晰。

          2.5. 需要改造的地方
          JpetStore4.0的關鍵就在struts Action類和form bean類上,這也是其精華之一(雖然該實現(xiàn)方式是試驗性,待擴充和驗證),在此次改造中我們要保留下來,即控制層一點不變,表現(xiàn)層獲取相應業(yè)務類的方式變了(要加載spring環(huán)境),其它保持不變。要特別關注的改動是業(yè)務層和持久層,幸運的是JpetStore4.0設計非常好,需要改動的地方非常少,而且由模式可循,如下:

          1. 業(yè)務層和數(shù)據(jù)層用Spring BeanFactory機制管理。

          2. 業(yè)務層的事務由spring 的aop通過聲明來完成。

          3. 表現(xiàn)層(form bean)獲取業(yè)務類的方法改由自定義工廠類來實現(xiàn)(加載spring環(huán)境)。

          3. JPetStore的改造

          3.1. 改造后的架構


          其中紅色部分是要增加的部分,藍色部分是要修改的部分。下面就讓我們逐一剖析。

          3.2. Spring Context的加載
          為了在Struts中加載Spring Context,一般會在struts-config.xml的最后添加如下部分:


          												
          <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
          <set-property property="contextConfigLocation"
          value="/WEB-INF/applicationContext.xml" />
          </plug-in>
          
          										

          Spring在設計時就充分考慮到了與Struts的協(xié)同工作,通過內置的Struts Plug-in在兩者之間提供了良好的結合點。但是,因為在這里我們一點也不改動JPetStore的控制層(這是JpetStore4.0的精華之一),所以本文不準備采用此方式來加載ApplicationContext。我們利用的是spring framework 的BeanFactory機制,采用自定義的工具類(bean工廠類)來加載spring的配置文件,從中可以看出Spring有多靈活,它提供了各種不同的方式來使用其不同的部分/層次,您只需要用你想用的,不需要的部分可以不用。

          具體的來說,就是在com.ibatis.spring包下創(chuàng)建CustomBeanFactory類,spring的配置文件applicationContext.xml也放在這個目錄下。以下就是該類的全部代碼,很簡單:


          												
          public final class CustomBeanFactory {
          	static XmlBeanFactory factory = null;
          	static {
          		Resource is = new
          InputStreamResource( CustomBeanFactory.class.getResourceAsStream("applicationContext.xml"));
          		factory = new XmlBeanFactory(is);			
          	}
          	public static Object getBean(String beanName){
          		return factory.getBean(beanName);
          	}
          }
          
          										

          實際上就是封裝了Spring 的XMLBeanFactory而已,并且Spring的配置文件只需要加載一次,以后就可以直接用CustomBeanFactory.getBean("someBean")來獲得需要的對象了(例如someBean),而不需要知道具體的類。CustomBeanFactory類用于{耦合1}的解耦。

          CustomBeanFactory類在本文中只用于表現(xiàn)層的form bean對象獲得service類的對象,因為我們沒有把form bean對象配置在applicationContext.xml中。但是,為什么不把表現(xiàn)層的form bean類也配置起來呢,這樣就用不著這CustomBeanFactory個類了,Spring會幫助我們創(chuàng)建需要的一切?問題的答案就在于form bean類是struts的ActionForm類!如果大家熟悉struts,就會知道ActionForm類是struts自動創(chuàng)建的:在一次請求中,struts判斷,如果ActionForm實例不存在,就創(chuàng)建一個ActionForm對象,把客戶提交的表單數(shù)據(jù)保存到ActionForm對象中。因此formbean類的對象就不能由spring來創(chuàng)建,但是service類以及數(shù)據(jù)層的DAO類可以,所以只有他們在spring中配置。

          所以,很自然的,我們就創(chuàng)建了CustomBeanFactory類,在表現(xiàn)層來銜接struts和spring。就這么簡單,實現(xiàn)了另一種方式的{耦合一}的解耦。

          3.3. 表現(xiàn)層
          上 面分析到,struts和spring是在表現(xiàn)層銜接起來的,那么表現(xiàn)層就要做稍微的更改,即所需要的service類的對象創(chuàng)建上。以表現(xiàn)層的AccountBean類為例:

          原來的源代碼如下


          												
          														 private static final AccountService accountService = AccountService.getInstance();
            private static final CatalogService catalogService = CatalogService.getInstance();
            
          												
          										

          改造后的源代碼如下


          												
            private static final AccountService accountService = (AccountService)CustomBeanFactory.getBean("AccountService");
            private static final CatalogService catalogService = (CatalogService)CustomBeanFactory.getBean("CatalogService");
          
          										

          其他的幾個presentation類以同樣方式改造。這樣,表現(xiàn)層就完成了。關于表現(xiàn)層的其它部分如JSP等一概不動。也許您會說,沒有看出什么特別之處的好處啊?你還是額外實現(xiàn)了一個工廠類。別著急,帷幕剛剛開啟,spring是在表現(xiàn)層引入,但您發(fā)沒發(fā)現(xiàn):

          • presentation類僅僅面向service類的接口編程,具體"AccountService"是哪個實現(xiàn)類,presentation類不知道,是在spring的配置文件里配置。(本例中,為了最大限度的保持原來的代碼不作變化,沒有抽象出接口)。Spring鼓勵面向接口編程,因為是如此的方便和自然,當然您也可以不這么做。
          • CustomBeanFactory這個工廠類為什么會如此簡單,因為其直接使用了Spring的BeanFactory。Spring從其核心而言,是一個DI容器,其設計哲學是提供一種無侵入式的高擴展性的框架。為了實現(xiàn)這個目標,Spring 大量引入了Java 的Reflection機制,通過動態(tài)調用的方式避免硬編碼方式的約束,并在此基礎上建立了其核心組件BeanFactory,以此作為其依賴注入機制的實現(xiàn)基礎。org.springframework.beans包中包括了這些核心組件的實現(xiàn)類,核心中的核心為BeanWrapper和BeanFactory類。

          3.4. 持久層
          在討論業(yè)務層之前,我們先看一下持久層,如下圖所示:


          在上文中,我們把iface包下的DAO接口歸為業(yè)務層,在這里不需要做修改。ibatis的sql配置文件也不需要改。要改的是DAO實現(xiàn)類,并在spring的配置文件中配置起來。

          1、修改基類

          所有的DAO實現(xiàn)類都繼承于BaseSqlMapDao類。修改BaseSqlMapDao類如下:


          												
          public class BaseSqlMapDao extends SqlMapClientDaoSupport {
            protected static final int PAGE_SIZE = 4;
            protected SqlMapClientTemplate smcTemplate = this.getSqlMapClientTemplate();
            public BaseSqlMapDao() { 
          	}
          }
          
          										

          使BaseSqlMapDao類改為繼承于Spring提供的SqlMapClientDaoSupport類,并定義了一個保護屬性smcTemplate,其類型為SqlMapClientTemplate。關于SqlMapClientTemplate類的詳細說明請參照附錄中的"Spring中文參考手冊"

          2、修改DAO實現(xiàn)類

          所有的DAO實現(xiàn)類還是繼承于BaseSqlMapDao類,實現(xiàn)相應的DAO接口,但其相應的DAO操作委托SqlMapClientTemplate來執(zhí)行,以AccountSqlMapDao類為例,部分代碼如下:


          												
              public List getUsernameList() {
              return smcTemplate.queryForList("getUsernameList", null);
            }
            public Account getAccount(String username, String password) {
              Account account = new Account();
              account.setUsername(username);
              account.setPassword(password);
              return (Account) smcTemplate.queryForObject("getAccountByUsernameAndPassword", account);
            }
            public void insertAccount(Account account) {
            	smcTemplate.update("insertAccount", account);
            	smcTemplate.update("insertProfile", account);
            	smcTemplate.update("insertSignon", account);
            }
            
          										

          就這么簡單,所有函數(shù)的簽名都是一樣的,只需要查找替換就可以了!

          3、除去工廠類以及相應的配置文件

          除去DaoConfig.java這個DAO工廠類和相應的配置文件dao.xml,因為DAO的獲取現(xiàn)在要用spring來管理。

          4、DAO在Spring中的配置(applicationContext.xml)


          												
              <bean id="dataSource" 
                  class="org.springframework.jdbc.datasource.DriverManagerDataSource">
                  <property name="driverClassName">
                      <value>org.hsqldb.jdbcDriver</value>
                  </property>
                  <property name="url">
                      <value>jdbc:hsqldb:hsql://localhost/xdb</value>
                  </property>
                  <property name="username">
                      <value>sa</value>
                  </property>
                  <property name="password">
                      <value></value>
                  </property>
              </bean>    
              <!-- ibatis sqlMapClient config -->
              <bean id="sqlMapClient" 
                  class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
                  <property name="configLocation">
                      <value> 
                          classpath:com\ibatis\jpetstore\persistence\sqlmapdao\sql\sql-map-config.xml
                      </value>
                  </property>
                  <property name="dataSource">
                      <ref bean="dataSource"/>
                  </property>    
              </bean>
              <!-- Transactions -->
              <bean id="TransactionManager" 
                  class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
                  <property name="dataSource">
                      <ref bean="dataSource"/>
                  </property>
              </bean>
              <!-- persistence layer -->
              <bean id="AccountDao" 
                  class="com.ibatis.jpetstore.persistence.sqlmapdao.AccountSqlMapDao">
                  <property name="sqlMapClient">
                      <ref local="sqlMapClient"/>
                  </property>
              </bean>
              
          										

          具體的語法請參照附錄中的"Spring中文參考手冊"。在這里只簡單解釋一下:

          1. 我們首先創(chuàng)建一個數(shù)據(jù)源dataSource,在這里配置的是hsqldb數(shù)據(jù)庫。如果是ORACLE數(shù)據(jù)庫,driverClassName的值是"oracle.jdbc.driver.OracleDriver",URL的值類似于"jdbc:oracle:thin:@wugfMobile:1521:cdcf"。數(shù)據(jù)源現(xiàn)在由spring來管理,那么現(xiàn)在我們就可以去掉properties目錄下database.properties這個配置文件了;還有不要忘記修改sql-map-config.xml,去掉<properties resource="properties/database.properties"/>對它的引用。

          2. sqlMapClient節(jié)點。這個是針對ibatis SqlMap的SqlMapClientFactoryBean配置。實際上配置了一個sqlMapClient的創(chuàng)建工廠類。configLocation屬性配置了ibatis映射文件的名稱。dataSource屬性指向了使用的數(shù)據(jù)源,這樣所有使用sqlMapClient的DAO都默認使用了該數(shù)據(jù)源,除非在DAO的配置中另外顯式指定。

          3. TransactionManager節(jié)點。定義了事務,使用的是DataSourceTransactionManager。

          4. 下面就可以定義DAO節(jié)點了,如AccountDao,它的實現(xiàn)類是com.ibatis.jpetstore.persistence.sqlmapdao.AccountSqlMapDao,使用的SQL配置從sqlMapClient中讀取,數(shù)據(jù)庫連接沒有特別列出,那么就是默認使用sqlMapClient配置的數(shù)據(jù)源datasource。

          這樣,我們就把持久層改造完了,其他的DAO配置類似于AccountDao。怎么樣?簡單吧。這次有接口了:) AccountDao接口->AccountSqlMapDao實現(xiàn)。

          3.5. 業(yè)務層
          業(yè)務層的位置以及相關類,如下圖所示:


          在這個例子中只有3個業(yè)務類,我們以OrderService類為例來改造,這個類是最復雜的,其中涉及了事務。

          1、在ApplicationContext配置文件中增加bean的配置:


          												
              <bean id="OrderService" 
                  class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
                  <property name="transactionManager">
                      <ref local="TransactionManager"></ref>
                  </property>
                  <property name="target">
                      <bean class="com.ibatis.jpetstore.service.OrderService">
                          <property name="itemDao">
                              <ref bean="ItemDao"/>
                          </property>
                          <property name="orderDao">
                              <ref bean="OrderDao"/>
                          </property>
                          <property name="sequenceDao">
                              <ref bean="SequenceDao"/>
                          </property>
                      </bean>
                  </property>
                  <property name="transactionAttributes">
                      <props>
                          <prop key="insert*">PROPAGATION_REQUIRED</prop>
                      </props>
                  </property>
              </bean>
              
          										

          定義了一個OrderService,還是很容易懂的。為了簡單起見,使用了嵌套bean,其實現(xiàn)類是com.ibatis.jpetstore.service.OrderService,分別引用了ItemDao,OrderDao,SequenceDao。該bean的insert*實現(xiàn)了事務管理(AOP方式)。TransactionProxyFactoryBean自動創(chuàng)建一個事務advisor, 該advisor包括一個基于事務屬性的pointcut,因此只有事務性的方法被攔截。

          2、業(yè)務類的修改

          以OrderService為例:


          												
          public class OrderService {
          
             /* Private Fields */
            private ItemDao itemDao;
            private OrderDao orderDao;
            private SequenceDao sequenceDao;
          
            /* Constructors */
          
            public OrderService() {
            }
          
          /**
           * @param itemDao 要設置的 itemDao。
           */
          public final void setItemDao(ItemDao itemDao) {
          	this.itemDao = itemDao;
          }
          /**
           * @param orderDao 要設置的 orderDao。
           */
          public final void setOrderDao(OrderDao orderDao) {
          	this.orderDao = orderDao;
          }
          /**
           * @param sequenceDao 要設置的 sequenceDao。
           */
          public final void setSequenceDao(SequenceDao sequenceDao) {
          	this.sequenceDao = sequenceDao;
          }
          //剩下的部分
          …….
          }
          
          										

          紅色部分為修改部分。Spring采用的是Type2的設置依賴注入,所以我們只需要定義屬性和相應的設值函數(shù)就可以了,ItemDao,OrderDao,SequenceDao的值由spring在運行期間注入。構造函數(shù)就可以為空了,另外也不需要自己編寫代碼處理事務了(事務在配置中聲明),daoManager.startTransaction();等與事務相關的語句也可以去掉了。和原來的代碼比較一下,是不是處理精簡了很多!可以更關注業(yè)務的實現(xiàn)。

          4. 結束語
          ibatis是一個功能強大實用的SQL Map工具,可以直接控制SQL,為系統(tǒng)設計提供了更大的自由空間。其提供的最新示例程序JpetStore 4.0,設計優(yōu)雅,應用了迄今為止很多最佳實踐和設計模式,非常適于學習以及在此基礎上創(chuàng)建輕量級的J2EE WEB應用程序。JpetStore 4.0是基于struts的,本文在此基礎上,最大程度保持了原有設計的精華以及最小的代碼改動量,在業(yè)務層和持久化層引入了Spring。在您閱讀了本文以及改造后的源代碼后,會深切的感受到Spring帶來的種種好處:自然的面向接口的編程,業(yè)務對象的依賴注入,一致的數(shù)據(jù)存取框架和聲明式的事務處理,統(tǒng)一的配置文件…更重要的是Spring既是全面的又是模塊化的,Spring有分層的體系結構,這意味著您能選擇僅僅使用它任何一個獨立的部分,就像本文,而它的架構又是內部一致。

          posted @ 2006-11-11 09:59 點滴鑄就輝煌 閱讀(380) | 評論 (0)編輯 收藏

          2006年10月24日

          因了需要用到這些信息,所以總結一下,方便以后參閱
          通過request.getHeader("User-Agent")大致可以取得用戶瀏覽器的信息
          如果里面包含:
          "msie"-->MicroSoft?
          "opera" -->Opera Software
          "mozilla"-->Netscape Communications

          如果取瀏覽器版本信息
          String str = request.getHeader("User-Agent");
          MS :? str.substring(str.indexOf("msie") + 5);
          Other :
          tmpString = (str.substring(tmpPos = (str.indexOf("/")) + 1, tmpPos + str.indexOf(" "))).trim(); ?//沒有親自試

          操作系統(tǒng)部分,不啰嗦了
          private void setOs()
          {
          if (this.userAgent.indexOf("win") > -1){
          ? if (this.userAgent.indexOf("windows 95") > -1 || this.userAgent.indexOf("win95") > -1){
          ???? this.os = "Windows 95";
          ? }
          ? if (this.userAgent.indexOf("windows 98") > -1 || this.userAgent.indexOf("win98") > -1){
          ???? this.os = "Windows 98";
          ? }
          ? if (this.userAgent.indexOf("windows nt") > -1 || this.userAgent.indexOf("winnt") > -1){
          ????? this.os = "Windows NT";
          ? }
          ? if (this.userAgent.indexOf("win16") > -1 || this.userAgent.indexOf("windows 3.") > -1){
          ????? this.os = "Windows 3.x";
          ? }
          ?}
          }

          獲取語言request.getHeader("Accept-Language");

          詳細信息可以再分解....

          posted @ 2006-10-24 10:25 點滴鑄就輝煌 閱讀(1089) | 評論 (0)編輯 收藏

          2006年10月17日

          Lucene 是基于 Java 的全文信息檢索包,它目前是 Apache Jakarta 家族下面的一個開源項目。在這篇文章中,我們首先來看如何利用 Lucene 實現(xiàn)高級搜索功能,然后學習如何利用 Lucene 來創(chuàng)建一個健壯的 Web 搜索應用程序。

          在本篇文章中,你會學習到如何利用 Lucene 實現(xiàn)高級搜索功能以及如何利用 Lucene 來創(chuàng)建 Web 搜索應用程序。通過這些學習,你就可以利用 Lucene 來創(chuàng)建自己的搜索應用程序。

          架構概覽

          通常一個 Web 搜索引擎的架構分為前端和后端兩部分,就像圖一中所示。在前端流程中,用戶在搜索引擎提供的界面中輸入要搜索的關鍵詞,這里提到的用戶界面一般是一個帶有輸入框的 Web 頁面,然后應用程序將搜索的關鍵詞解析成搜索引擎可以理解的形式,并在索引文件上進行搜索操作。在排序后,搜索引擎返回搜索結果給用戶。在后端流程中,網(wǎng)絡爬蟲或者機器人從因特網(wǎng)上獲取 Web 頁面,然后索引子系統(tǒng)解析這些 Web 頁面并存入索引文件中。如果你想利用 Lucene 來創(chuàng)建一個 Web 搜索應用程序,那么它的架構也和上面所描述的類似,就如圖一中所示。


          Figure 1. Web 搜索引擎架構
          Web搜索引擎架構

          利用 Lucene 實現(xiàn)高級搜索

          Lucene 支持多種形式的高級搜索,我們在這一部分中會進行探討,然后我會使用 Lucene 的 API 來演示如何實現(xiàn)這些高級搜索功能。

          布爾操作符

          大多數(shù)的搜索引擎都會提供布爾操作符讓用戶可以組合查詢,典型的布爾操作符有 AND, OR, NOT。Lucene 支持 5 種布爾操作符,分別是 AND, OR, NOT, 加(+), 減(-)。接下來我會講述每個操作符的用法。

          • OR: 如果你要搜索含有字符 A 或者 B 的文檔,那么就需要使用 OR 操作符。需要記住的是,如果你只是簡單的用空格將兩個關鍵詞分割開,其實在搜索的時候搜索引擎會自動在兩個關鍵詞之間加上 OR 操作符。例如,“Java OR Lucene” 和 “Java Lucene” 都是搜索含有 Java 或者含有 Lucene 的文檔。
          • AND: 如果你需要搜索包含一個以上關鍵詞的文檔,那么就需要使用 AND 操作符。例如,“Java AND Lucene” 返回所有既包含 Java 又包含 Lucene 的文檔。
          • NOT: Not 操作符使得包含緊跟在 NOT 后面的關鍵詞的文檔不會被返回。例如,如果你想搜索所有含有 Java 但不含有 Lucene 的文檔,你可以使用查詢語句 “Java NOT Lucene”。但是你不能只對一個搜索詞使用這個操作符,比如,查詢語句 “NOT Java” 不會返回任何結果。
          • 加號(+): 這個操作符的作用和 AND 差不多,但它只對緊跟著它的一個搜索詞起作用。例如,如果你想搜索一定包含 Java,但不一定包含 Lucene 的文檔,就可以使用查詢語句“+Java Lucene”。
          • 減號(-): 這個操作符的功能和 NOT 一樣,查詢語句 “Java -Lucene” 返回所有包含 Java 但不包含 Lucene 的文檔。

          接下來我們看一下如何利用 Lucene 提供的 API 來實現(xiàn)布爾查詢。清單1 顯示了如果利用布爾操作符進行查詢的過程。


          清單1:使用布爾操作符
            //Test boolean operator
          public void testOperator(String indexDirectory) throws Exception{
             Directory dir = FSDirectory.getDirectory(indexDirectory,false);
             IndexSearcher indexSearcher = new IndexSearcher(dir);
             String[] searchWords = {"Java AND Lucene", "Java NOT Lucene", "Java OR Lucene", 
                              "+Java +Lucene", "+Java -Lucene"};
             Analyzer language = new StandardAnalyzer();
             Query query;
             for(int i = 0; i < searchWords.length; i++){
                query = QueryParser.parse(searchWords[i], "title", language);
                Hits results = indexSearcher.search(query);
                System.out.println(results.length() + "search results for query " + searchWords[i]);
             }
          }
          

          域搜索(Field Search)

          Lucene 支持域搜索,你可以指定一次查詢是在哪些域(Field)上進行。例如,如果索引的文檔包含兩個域,TitleContent,你就可以使用查詢 “Title: Lucene AND Content: Java” 來返回所有在 Title 域上包含 Lucene 并且在 Content 域上包含 Java 的文檔。清單 2 顯示了如何利用 Lucene 的 API 來實現(xiàn)域搜索。


          清單2:實現(xiàn)域搜索
          //Test field search
          public void testFieldSearch(String indexDirectory) throws Exception{
              Directory dir = FSDirectory.getDirectory(indexDirectory,false);
              IndexSearcher indexSearcher = new IndexSearcher(dir);
              String searchWords = "title:Lucene AND content:Java";
              Analyzer language = new StandardAnalyzer();
              Query query = QueryParser.parse(searchWords, "title", language);
              Hits results = indexSearcher.search(query);
              System.out.println(results.length() + "search results for query " + searchWords);
          }
          

          通配符搜索(Wildcard Search)

          Lucene 支持兩種通配符:問號(?)和星號(*)。你可以使用問號(?)來進行單字符的通配符查詢,或者利用星號(*)進行多字符的通配符查詢。例如,如果你想搜索 tiny 或者 tony,你就可以使用查詢語句 “t?ny”;如果你想查詢 Teach, Teacher 和 Teaching,你就可以使用查詢語句 “Teach*”。清單3 顯示了通配符查詢的過程。


          清單3:進行通配符查詢
          //Test wildcard search
          public void testWildcardSearch(String indexDirectory)throws Exception{
             Directory dir = FSDirectory.getDirectory(indexDirectory,false);
             IndexSearcher indexSearcher = new IndexSearcher(dir);
             String[] searchWords = {"tex*", "tex?", "?ex*"};
             Query query;
             for(int i = 0; i < searchWords.length; i++){
                query = new WildcardQuery(new Term("title",searchWords[i]));
                Hits results = indexSearcher.search(query);
                System.out.println(results.length() + "search results for query " + searchWords[i]);
             }
          }
          

          模糊查詢

          Lucene 提供的模糊查詢基于編輯距離算法(Edit distance algorithm)。你可以在搜索詞的尾部加上字符 ~ 來進行模糊查詢。例如,查詢語句 “think~” 返回所有包含和 think 類似的關鍵詞的文檔。清單 4 顯示了如果利用 Lucene 的 API 進行模糊查詢的代碼。


          清單4:實現(xiàn)模糊查詢
          //Test fuzzy search
          public void testFuzzySearch(String indexDirectory)throws Exception{
             Directory dir = FSDirectory.getDirectory(indexDirectory,false);
             IndexSearcher indexSearcher = new IndexSearcher(dir);
             String[] searchWords = {"text", "funny"};
             Query query;
             for(int i = 0; i < searchWords.length; i++){
                query = new FuzzyQuery(new Term("title",searchWords[i]));
                Hits results = indexSearcher.search(query);
                System.out.println(results.length() + "search results for query " + searchWords[i]);
             }
          }
          

          范圍搜索(Range Search)

          范圍搜索匹配某個域上的值在一定范圍的文檔。例如,查詢 “age:[18 TO 35]” 返回所有 age 域上的值在 18 到 35 之間的文檔。清單5顯示了利用 Lucene 的 API 進行返回搜索的過程。


          清單5:測試范圍搜索
          //Test range search
          public void testRangeSearch(String indexDirectory)throws Exception{
              Directory dir = FSDirectory.getDirectory(indexDirectory,false);
              IndexSearcher indexSearcher = new IndexSearcher(dir);
              Term begin = new Term("birthDay","20000101");
              Term end   = new Term("birthDay","20060606");
              Query query = new RangeQuery(begin,end,true);
              Hits results = indexSearcher.search(query);
              System.out.println(results.length() + "search results is returned");
          }
          





          回頁首


          在 Web 應用程序中集成 Lucene

          接下來我們開發(fā)一個 Web 應用程序利用 Lucene 來檢索存放在文件服務器上的 HTML 文檔。在開始之前,需要準備如下環(huán)境:

          1. Eclipse 集成開發(fā)環(huán)境
          2. Tomcat 5.0
          3. Lucene Library
          4. JDK 1.5

          這個例子使用 Eclipse 進行 Web 應用程序的開發(fā),最終這個 Web 應用程序跑在 Tomcat 5.0 上面。在準備好開發(fā)所必需的環(huán)境之后,我們接下來進行 Web 應用程序的開發(fā)。

          1、創(chuàng)建一個動態(tài) Web 項目

          1. 在 Eclipse 里面,選擇 File > New > Project,然后再彈出的窗口中選擇動態(tài) Web 項目,如圖二所示。

          圖二:創(chuàng)建動態(tài)Web項目
          創(chuàng)建動態(tài)Web項目
          1. 在創(chuàng)建好動態(tài) Web 項目之后,你會看到創(chuàng)建好的項目的結構,如圖三所示,項目的名稱為 sample.dw.paper.lucene。

          圖三:動態(tài) Web 項目的結構
          動態(tài) Web 項目的結構

          2. 設計 Web 項目的架構

          在我們的設計中,把該系統(tǒng)分成如下四個子系統(tǒng):

          1. 用戶接口: 這個子系統(tǒng)提供用戶界面使用戶可以向 Web 應用程序服務器提交搜索請求,然后搜索結果通過用戶接口來顯示出來。我們用一個名為 search.jsp 的頁面來實現(xiàn)該子系統(tǒng)。
          2. 請求管理器: 這個子系統(tǒng)管理從客戶端發(fā)送過來的搜索請求并把搜索請求分發(fā)到搜索子系統(tǒng)中。最后搜索結果從搜索子系統(tǒng)返回并最終發(fā)送到用戶接口子系統(tǒng)。我們使用一個 Servlet 來實現(xiàn)這個子系統(tǒng)。
          3. 搜索子系統(tǒng): 這個子系統(tǒng)負責在索引文件上進行搜索并把搜索結構傳遞給請求管理器。我們使用 Lucene 提供的 API 來實現(xiàn)該子系統(tǒng)。
          4. 索引子系統(tǒng): 這個子系統(tǒng)用來為 HTML 頁面來創(chuàng)建索引。我們使用 Lucene 的 API 以及 Lucene 提供的一個 HTML 解析器來創(chuàng)建該子系統(tǒng)。

          圖4 顯示了我們設計的詳細信息,我們將用戶接口子系統(tǒng)放到 webContent 目錄下面。你會看到一個名為 search.jsp 的頁面在這個文件夾里面。請求管理子系統(tǒng)在包 sample.dw.paper.lucene.servlet 下面,類 SearchController 負責功能的實現(xiàn)。搜索子系統(tǒng)放在包 sample.dw.paper.lucene.search 當中,它包含了兩個類,SearchManagerSearchResultBean,第一個類用來實現(xiàn)搜索功能,第二個類用來描述搜索結果的結構。索引子系統(tǒng)放在包 sample.dw.paper.lucene.index 當中。類 IndexManager 負責為 HTML 文件創(chuàng)建索引。該子系統(tǒng)利用包 sample.dw.paper.lucene.util 里面的類 HTMLDocParser 提供的方法 getTitlegetContent 來對 HTML 頁面進行解析。


          圖四:項目的架構設計
          項目的架構設計

          3. 子系統(tǒng)的實現(xiàn)

          在分析了系統(tǒng)的架構設計之后,我們接下來看系統(tǒng)實現(xiàn)的詳細信息。

          1. 用戶接口: 這個子系統(tǒng)有一個名為 search.jsp 的 JSP 文件來實現(xiàn),這個 JSP 頁面包含兩個部分。第一部分提供了一個用戶接口去向 Web 應用程序服務器提交搜索請求,如圖5所示。注意到這里的搜索請求發(fā)送到了一個名為 SearchController 的 Servlet 上面。Servlet 的名字和具體實現(xiàn)的類的對應關系在 web.xml 里面指定。

          圖5:向Web服務器提交搜索請求
          向Web服務器提交搜索請求

          這個JSP的第二部分負責顯示搜索結果給用戶,如圖6所示:


          圖6:顯示搜索結果
          顯示搜索結果
          1. 請求管理器: 一個名為 SearchController 的 servlet 用來實現(xiàn)該子系統(tǒng)。清單6給出了這個類的源代碼。

          清單6:請求管理器的實現(xiàn)
          package sample.dw.paper.lucene.servlet;
          
          import java.io.IOException;
          import java.util.List;
          
          import javax.servlet.RequestDispatcher;
          import javax.servlet.ServletException;
          import javax.servlet.http.HttpServlet;
          import javax.servlet.http.HttpServletRequest;
          import javax.servlet.http.HttpServletResponse;
          
          import sample.dw.paper.lucene.search.SearchManager;
          
          /**
           * This servlet is used to deal with the search request
           * and return the search results to the client
           */
          public class SearchController extends HttpServlet{
          
              private static final long serialVersionUID = 1L;
          
              public void doPost(HttpServletRequest request, HttpServletResponse response)
                                throws IOException, ServletException{
                  String searchWord = request.getParameter("searchWord");
                  SearchManager searchManager = new SearchManager(searchWord);
                  List searchResult = null;
                  searchResult = searchManager.search();
                  RequestDispatcher dispatcher = request.getRequestDispatcher("search.jsp");
                  request.setAttribute("searchResult",searchResult);
                  dispatcher.forward(request, response);
              }
          
              public void doGet(HttpServletRequest request, HttpServletResponse response)
                               throws IOException, ServletException{
                  doPost(request, response);
              }
          }
          

          清單6中,doPost 方法從客戶端獲取搜索詞并創(chuàng)建類 SearchManager 的一個實例,其中類 SearchManager 在搜索子系統(tǒng)中進行了定義。然后,SearchManager 的方法 search 會被調用。最后搜索結果被返回到客戶端。

          1. 搜索子系統(tǒng): 在這個子系統(tǒng)中,我們定義了兩個類:SearchManagerSearchResultBean。第一個類用來實現(xiàn)搜索功能,第二個類是個JavaBean,用來描述搜索結果的結構。清單7給出了類 SearchManager 的源代碼。

          清單7:搜索功能的實現(xiàn)
          package sample.dw.paper.lucene.search;
          
          import java.io.IOException;
          import java.util.ArrayList;
          import java.util.List;
          
          import org.apache.lucene.analysis.Analyzer;
          import org.apache.lucene.analysis.standard.StandardAnalyzer;
          import org.apache.lucene.queryParser.ParseException;
          import org.apache.lucene.queryParser.QueryParser;
          import org.apache.lucene.search.Hits;
          import org.apache.lucene.search.IndexSearcher;
          import org.apache.lucene.search.Query;
          
          import sample.dw.paper.lucene.index.IndexManager;
          
          /**
           * This class is used to search the 
           * Lucene index and return search results
           */
          public class SearchManager {
          	
              private String searchWord;
              
              private IndexManager indexManager;
              
              private Analyzer analyzer;
              
              public SearchManager(String searchWord){
                  this.searchWord   =  searchWord;
                  this.indexManager =  new IndexManager();
                  this.analyzer     =  new StandardAnalyzer();
              }
              
              /**
               * do search
               */
              public List search(){
                  List searchResult = new ArrayList();
                  if(false == indexManager.ifIndexExist()){
                  try {
                      if(false == indexManager.createIndex()){
                          return searchResult;
                      }
                  } catch (IOException e) {
                    e.printStackTrace();
                    return searchResult;
                  }
                  }
              	
                  IndexSearcher indexSearcher = null;
          
                  try{
                      indexSearcher = new IndexSearcher(indexManager.getIndexDir());
                  }catch(IOException ioe){
                      ioe.printStackTrace();
                  }
          
                  QueryParser queryParser = new QueryParser("content",analyzer);
                  Query query = null;
                  try {
                      query = queryParser.parse(searchWord);
                  } catch (ParseException e) {
                    e.printStackTrace();
                  }
                  if(null != query >> null != indexSearcher){			
                      try {
                          Hits hits = indexSearcher.search(query);
                          for(int i = 0; i < hits.length(); i ++){
                              SearchResultBean resultBean = new SearchResultBean();
                              resultBean.setHtmlPath(hits.doc(i).get("path"));
                              resultBean.setHtmlTitle(hits.doc(i).get("title"));
                              searchResult.add(resultBean);
                          }
                      } catch (IOException e) {
                          e.printStackTrace();
                      }
                  }
                  return searchResult;
              }
          }
           

          清單7中,注意到在這個類里面有三個私有屬性。第一個是 searchWord,代表了來自客戶端的搜索詞。第二個是 indexManager,代表了在索引子系統(tǒng)中定義的類 IndexManager 的一個實例。第三個是 analyzer,代表了用來解析搜索詞的解析器。現(xiàn)在我們把注意力放在方法 search 上面。這個方法首先檢查索引文件是否已經(jīng)存在,如果已經(jīng)存在,那么就在已經(jīng)存在的索引上進行檢索,如果不存在,那么首先調用類 IndexManager 提供的方法來創(chuàng)建索引,然后在新創(chuàng)建的索引上進行檢索。搜索結果返回后,這個方法從搜索結果中提取出需要的屬性并為每個搜索結果生成類 SearchResultBean 的一個實例。最后這些 SearchResultBean 的實例被放到一個列表里面并返回給請求管理器。

          在類 SearchResultBean 中,含有兩個屬性,分別是 htmlPathhtmlTitle,以及這個兩個屬性的 get 和 set 方法。這也意味著我們的搜索結果包含兩個屬性:htmlPathhtmlTitle,其中 htmlPath 代表了 HTML 文件的路徑,htmlTitle 代表了 HTML 文件的標題。

          1. 索引子系統(tǒng): 類 IndexManager 用來實現(xiàn)這個子系統(tǒng)。清單8 給出了這個類的源代碼。

          清單8:索引子系統(tǒng)的實現(xiàn)
          package sample.dw.paper.lucene.index;
          
          import java.io.File;
          import java.io.IOException;
          import java.io.Reader;
          
          import org.apache.lucene.analysis.Analyzer;
          import org.apache.lucene.analysis.standard.StandardAnalyzer;
          import org.apache.lucene.document.Document;
          import org.apache.lucene.document.Field;
          import org.apache.lucene.index.IndexWriter;
          import org.apache.lucene.store.Directory;
          import org.apache.lucene.store.FSDirectory;
          
          import sample.dw.paper.lucene.util.HTMLDocParser;
          
          /**
           * This class is used to create an index for HTML files
           *
           */
          public class IndexManager {
          
              //the directory that stores HTML files 
              private final String dataDir  = "c:\\dataDir";
          
              //the directory that is used to store a Lucene index
              private final String indexDir = "c:\\indexDir";
          
              /**
               * create index
               */
              public boolean createIndex() throws IOException{
                  if(true == ifIndexExist()){
                      return true;	
                  }
                  File dir = new File(dataDir);
                  if(!dir.exists()){
                      return false;
                  }
                  File[] htmls = dir.listFiles();
                  Directory fsDirectory = FSDirectory.getDirectory(indexDir, true);
                  Analyzer  analyzer    = new StandardAnalyzer();
                  IndexWriter indexWriter = new IndexWriter(fsDirectory, analyzer, true);
                  for(int i = 0; i < htmls.length; i++){
                      String htmlPath = htmls[i].getAbsolutePath();
          
                      if(htmlPath.endsWith(".html") || htmlPath.endsWith(".htm")){
                  		addDocument(htmlPath, indexWriter);
                  	}
                  }
                  indexWriter.optimize();
                  indexWriter.close();
                  return true;
          
              }
          
              /**
               * Add one document to the Lucene index
               */
              public void addDocument(String htmlPath, IndexWriter indexWriter){
                  HTMLDocParser htmlParser = new HTMLDocParser(htmlPath);
                  String path    = htmlParser.getPath();
                  String title   = htmlParser.getTitle();
                  Reader content = htmlParser.getContent();
          
                  Document document = new Document();
                  document.add(new Field("path",path,Field.Store.YES,Field.Index.NO));
                  document.add(new Field("title",title,Field.Store.YES,Field.Index.TOKENIZED));
                  document.add(new Field("content",content));
                  try {
                        indexWriter.addDocument(document);
              } catch (IOException e) {
                        e.printStackTrace();
                    }
              }
          
              /**
               * judge if the index exists already
               */
              public boolean ifIndexExist(){
                  File directory = new File(indexDir);
                  if(0 < directory.listFiles().length){
                      return true;
                  }else{
                      return false;
                  }
              }
          
              public String getDataDir(){
                  return this.dataDir;
              }
          
              public String getIndexDir(){
                  return this.indexDir;
              }
          
          }
          

          這個類包含兩個私有屬性,分別是 dataDirindexDirdataDir 代表存放等待進行索引的 HTML 頁面的路徑,indexDir 代表了存放 Lucene 索引文件的路徑。類 IndexManager 提供了三個方法,分別是 createIndex, addDocumentifIndexExist。如果索引不存在的話,你可以使用方法 createIndex 去創(chuàng)建一個新的索引,用方法 addDocument 去向一個索引上添加文檔。在我們的場景中,一個文檔就是一個 HTML 頁面。方法 addDocument 會調用由類 HTMLDocParser 提供的方法對 HTML 文檔進行解析。你可以使用最后一個方法 ifIndexExist 來判斷 Lucene 的索引是否已經(jīng)存在。

          現(xiàn)在我們來看一下放在包 sample.dw.paper.lucene.util 里面的類 HTMLDocParser。這個類用來從 HTML 文件中提取出文本信息。這個類包含三個方法,分別是 getContentgetTitlegetPath。第一個方法返回去除了 HTML 標記的文本內容,第二個方法返回 HTML 文件的標題,最后一個方法返回 HTML 文件的路徑。清單9 給出了這個類的源代碼。


          清單9:HTML 解析器
          package sample.dw.paper.lucene.util;
          
          import java.io.FileInputStream;
          import java.io.FileNotFoundException;
          import java.io.IOException;
          import java.io.InputStream;
          import java.io.InputStreamReader;
          import java.io.Reader;
          import java.io.UnsupportedEncodingException;
          
          import org.apache.lucene.demo.html.HTMLParser;
          
          public class HTMLDocParser {
              private String htmlPath;
          
              private HTMLParser htmlParser;
          
              public HTMLDocParser(String htmlPath){
                  this.htmlPath = htmlPath;
                  initHtmlParser();
              }
          
              private void initHtmlParser(){
                  InputStream inputStream = null;
                  try {
                      inputStream = new FileInputStream(htmlPath);
                  } catch (FileNotFoundException e) {
                      e.printStackTrace();
                  }
                  if(null != inputStream){
          	        try {
                          htmlParser = new HTMLParser(new InputStreamReader(inputStream, "utf-8"));
                      } catch (UnsupportedEncodingException e) {
                          e.printStackTrace();
                      }
                  }
              }
          
              public String getTitle(){
                  if(null != htmlParser){
                      try {
                          return htmlParser.getTitle();
                      } catch (IOException e) {
                          e.printStackTrace();
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
              return "";
              }
          
              public Reader getContent(){
              if(null != htmlParser){
                      try {
                            return htmlParser.getReader();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                  }
                  return null;
              }
          
              public String getPath(){
                  return this.htmlPath;		
              }
          }
          

          5.在 Tomcat 5.0 上運行應用程序

          現(xiàn)在我們可以在 Tomcat 5.0 上運行開發(fā)好的應用程序。

          1. 右鍵單擊 search.jsp,然后選擇 Run as > Run on Server,如圖7所示。

          圖7:配置 Tomcat 5.0
          配置 Tomcat 5.0
          1. 在彈出的窗口中,選擇 Tomcat v5.0 Server 作為目標 Web 應用程序服務器,然后點擊 Next,如圖8 所示:

          圖8:選擇 Tomcat 5.0
          選擇 Tomcat 5.0
          1. 現(xiàn)在需要指定用來運行 Web 應用程序的 Apache Tomcat 5.0 以及 JRE 的路徑。這里你所選擇的 JRE 的版本必須和你用來編譯 Java 文件的 JRE 的版本一致。配置好之后,點擊 Finish。如 圖9 所示。

          圖9:完成Tomcat 5.0的配置
          完成Tomcat 5.0的配置
          1. 配置好之后,Tomcat 會自動運行,并且會對 search.jsp 進行編譯并顯示給用戶。如 圖10 所示。

          圖10:用戶界面
          用戶界面
          1. 在輸入框中輸入關鍵詞 “information” 然后單擊 Search 按鈕。然后這個頁面上會顯示出搜索結果來,如 圖11 所示。

          圖11:搜索結果
          搜索結果
          1. 單擊搜索結果的第一個鏈接,頁面上就會顯示出所鏈接到的頁面的內容。如 圖12 所示.

          圖12:詳細信息
          詳細信息

          現(xiàn)在我們已經(jīng)成功的完成了示例項目的開發(fā),并成功的用Lucene實現(xiàn)了搜索和索引功能。你可以下載這個項目的源代碼(下載)。





          回頁首


          總結

          Lucene 提供了靈活的接口使我們更加方便的設計我們的 Web 搜索應用程序。如果你想在你的應用程序中加入搜索功能,那么 Lucene 是一個很好的選擇。在設計你的下一個帶有搜索功能的應用程序的時候可以考慮使用 Lucene 來提供搜索功能。

          posted @ 2006-10-17 13:05 點滴鑄就輝煌 閱讀(271) | 評論 (0)編輯 收藏

          2006年8月29日

          使用Windows操作系統(tǒng)的朋友對Excel(電子表格)一定不會陌生,但是要使用Java語言來操縱Excel文件并不是一件容易的事。在Web應用日益盛行的今天,通過Web來操作Excel文件的需求越來越強烈,目前較為流行的操作是在JSP或Servlet 中創(chuàng)建一個CSV (comma separated values)文件,并將這個文件以MIME,text/csv類型返回給瀏覽器,接著瀏覽器調用Excel并且顯示CSV文件。這樣只是說可以訪問到Excel文件,但是還不能真正的操縱Excel文件,本文將給大家一個驚喜,向大家介紹一個開放源碼項目,Java Excel API,使用它大家就可以方便地操縱Excel文件了。
          JAVA EXCEL API簡介

          Java Excel是一開放源碼項目,通過它Java開發(fā)人員可以讀取Excel文件的內容、創(chuàng)建新的Excel文件、更新已經(jīng)存在的Excel文件。使用該API非Windows操作系統(tǒng)也可以通過純Java應用來處理Excel數(shù)據(jù)表。因為是使用Java編寫的,所以我們在Web應用中可以通過JSP、Servlet來調用API實現(xiàn)對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)在還不支持以下功能,但不久就會提供了:

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

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

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


          import java.io.*;
          import jxl.*;
          … … … …
          try
          {
          //構建Workbook對象, 只讀Workbook對象
          //直接從本地文件創(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(術語:工作表)。參考下面的代碼片段:


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



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

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


          //獲取第一行,第一列的值
          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值都作為一個字符串返回。示例代碼中Cell(0, 0)是文本型,Cell(1, 0)是數(shù)字型,Cell(1,1)是日期型,通過getContents(),三種類型的返回值都是字符型。

          如果有需要知道Cell內容的確切類型,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對象后,通過getType()方法可以獲得該單元格的類型,然后與API提供的基本類型相匹配,強制轉換成相應的類型,最后調用相應的取值方法getXXX(),就可以得到確定類型的值。API提供了以下基本類型,與Excel的數(shù)據(jù)格式相對應,如下圖所示:





          每種類型的具體意義,請參見Java Excel API Document。

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


          //操作完成時,關閉對象,釋放占用的內存空間
          rwb.close();



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

          Workbook類提供的方法

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


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



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


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



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


          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)
          獲取某一列的所有單元格,返回的是單元格對象數(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)
          獲取某一行的所有單元格,返回的是單元格對象數(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)
          獲取指定單元格的對象引用,需要注意的是它的兩個參數(shù),第一個是列數(shù),第二個是行數(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工作表,在這里單元格的內容是不帶任何修飾的(如:字體,顏色等等),所有的內容都作為字符串寫入。(完整代碼見ExcelWriting.java)

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


          import java.io.*;
          import jxl.*;
          import jxl.write.*;
          … … … …
          try
          {
          //構建Workbook對象, 只讀Workbook對象
          //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提供了兩種方式來處理可寫入的輸出流,一種是直接生成本地文件,如果文件名不帶全路徑的話,缺省的文件會定位在當前目錄,如果文件名帶有全路徑的話,則生成的Excel文件則會定位在相應的目錄;另外一種是將Excel對象直接寫入到輸出流,例如:用戶通過瀏覽器來訪問Web服務器,如果HTTP頭設置正確的話,瀏覽器自動調用客戶端的Excel應用程序,來顯示動態(tài)生成的Excel電子表格。

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


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



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


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

          //添加帶有字型Formatting的對象
          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的對象
          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對象
          jxl.write.Number labelN = new jxl.write.Number(0, 1, 3.1415926);
          ws.addCell(labelN);

          //添加帶有formatting的Number對象
          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對象
          jxl.write.Boolean labelB = new jxl.write.Boolean(0, 2, false);
          ws.addCell(labelB);

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

          //添加帶有formatting的DateFormat對象
          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);



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

          最后,不要忘記關閉打開的Excel工作薄對象,以釋放占用的內存,參見下面的代碼片段:


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

          //關閉Excel工作薄對象
          wwb.close();



          這可能與讀取Excel文件的操作有少少不同,在關閉Excel對象之前,你必須要先調用write()方法,因為先前的操作都是存儲在緩存中的,所以要通過該方法將操作的內容保存在文件中。如果你先關閉了Excel對象,那么只能得到一張空的工作薄了。

          3 拷貝、更新Excel工作薄

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


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

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

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

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

          //關閉可寫入的Excel對象
          wwb.close();

          //關閉只讀的Excel對象
          rw.close();



          之所以使用這種方式構建Excel對象,完全是因為效率的原因,因為上面的示例才是API的主要應用。為了提高性能,在讀取工作表時,與數(shù)據(jù)相關的一些輸出信息,所有的格式信息,如:字體、顏色等等,是不被處理的,因為我們的目的是獲得行數(shù)據(jù)的值,既使沒有了修飾,也不會對行數(shù)據(jù)的值產(chǎn)生什么影響。唯一的不利之處就是,在內存中會同時保存兩個同樣的工作表,這樣當工作表體積比較大時,會占用相當大的內存,但現(xiàn)在好像內存的大小并不是什么關鍵因素了。

          一旦獲得了可寫入的工作表對象,我們就可以對單元格對象進行更新的操作了,在這里我們不必調用API提供的add()方法,因為單元格已經(jīng)于工作表當中,所以我們只需要調用相應的setXXX()方法,就可以完成更新的操作了。

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

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

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

          以上摘自IBM網(wǎng)站
          posted @ 2006-08-29 17:32 點滴鑄就輝煌 閱讀(261) | 評論 (0)編輯 收藏

          2006年6月23日

          [原創(chuàng)文章,轉載請保留或注明出處:http://www.regexlab.com/zh/encoding.htm]

          級別:中級

          摘要:本文介紹了字符與編碼的發(fā)展過程,相關概念的正確理解。舉例說明了一些實際應用中,編碼的實現(xiàn)方法。然后,本文講述了通常對字符與編碼的幾種誤解,由于這些誤解而導致亂碼產(chǎn)生的原因,以及消除亂碼的辦法。本文的內容涵蓋了“中文問題”,“亂碼問題”。

          掌握編碼問題的關鍵是正確地理解相關概念,編碼所涉及的技術其實是很簡單的。因此,閱讀本文時需要慢讀多想,多思考。

          引言

          “字符與編碼”是一個被經(jīng)常討論的話題。即使這樣,時常出現(xiàn)的亂碼仍然困擾著大家。雖然我們有很多的辦法可以用來消除亂碼,但我們并不一定理解這些辦法的內在原理。而有的亂碼產(chǎn)生的原因,實際上由于底層代碼本身有問題所導致的。因此,不僅是初學者會對字符編碼感到模糊,有的底層開發(fā)人員同樣對字符編碼缺乏準確的理解。

          1. 編碼問題的由來,相關概念的理解

          1.1 字符與編碼的發(fā)展

          從計算機對多國語言的支持角度看,大致可以分為三個階段:

            系統(tǒng)內碼 說明 系統(tǒng)
          階段一 ASCII 計算機剛開始只支持英語,其它語言不能夠在計算機上存儲和顯示。 英文 DOS
          階段二 ANSI編碼
          (本地化)
          為使計算機支持更多語言,通常使用 0x80~0xFF 范圍的 2 個字節(jié)來表示 1 個字符。比如:漢字 '中' 在中文操作系統(tǒng)中,使用 [0xD6,0xD0] 這兩個字節(jié)存儲。

          不同的國家和地區(qū)制定了不同的標準,由此產(chǎn)生了 GB2312, BIG5, JIS 等各自的編碼標準。這些使用 2 個字節(jié)來代表一個字符的各種漢字延伸編碼方式,稱為 ANSI 編碼。在簡體中文系統(tǒng)下,ANSI 編碼代表 GB2312 編碼,在日文操作系統(tǒng)下,ANSI 編碼代表 JIS 編碼。

          不同 ANSI 編碼之間互不兼容,當信息在國際間交流時,無法將屬于兩種語言的文字,存儲在同一段 ANSI 編碼的文本中。
          中文 DOS,中文 Windows 95/98,日文 Windows 95/98
          階段三 UNICODE
          (國際化)
          為了使國際間信息交流更加方便,國際組織制定了 UNICODE 字符集,為各種語言中的每一個字符設定了統(tǒng)一并且唯一的數(shù)字編號,以滿足跨語言、跨平臺進行文本轉換、處理的要求。 Windows NT/2000/XP,Linux,Java

          字符串在內存中的存放方法:

          在 ASCII 階段,單字節(jié)字符串使用一個字節(jié)存放一個字符(SBCS)。比如,"Bob123" 在內存中為:

          42 6F 62 31 32 33 00
          B o b 1 2 3 \0

          在使用 ANSI 編碼支持多種語言階段,每個字符使用一個字節(jié)或多個字節(jié)來表示(MBCS),因此,這種方式存放的字符也被稱作多字節(jié)字符。比如,"中文123" 在中文 Windows 95 內存中為7個字節(jié),每個漢字占2個字節(jié),每個英文和數(shù)字字符占1個字節(jié):

          D6 D0 CE C4 31 32 33 00
          1 2 3 \0

          在 UNICODE 被采用之后,計算機存放字符串時,改為存放每個字符在 UNICODE 字符集中的序號。目前計算機一般使用 2 個字節(jié)(16 位)來存放一個序號(DBCS),因此,這種方式存放的字符也被稱作寬字節(jié)字符。比如,字符串 "中文123" 在 Windows 2000 下,內存中實際存放的是 5 個序號:

          2D 4E 87 65 31 00 32 00 33 00 00 00 ???? ← 在 x86 CPU 中,低字節(jié)在前
          1 2 3 \0  

          一共占 10 個字節(jié)。

          1.2 字符,字節(jié),字符串

          理解編碼的關鍵,是要把字符的概念和字節(jié)的概念理解準確。這兩個概念容易混淆,我們在此做一下區(qū)分:

            概念描述 舉例
          字符 人們使用的記號,抽象意義上的一個符號。 '1', '中', 'a', '$', '¥', ……
          字節(jié) 計算機中存儲數(shù)據(jù)的單元,一個8位的二進制數(shù),是一個很具體的存儲空間。 0x01, 0x45, 0xFA, ……
          ANSI
          字符串
          在內存中,如果“字符”是以 ANSI 編碼形式存在的,一個字符可能使用一個字節(jié)或多個字節(jié)來表示,那么我們稱這種字符串為 ANSI 字符串或者多字節(jié)字符串 "中文123"
          (占7字節(jié))
          UNICODE
          字符串
          在內存中,如果“字符”是以在 UNICODE 中的序號存在的,那么我們稱這種字符串為 UNICODE 字符串或者寬字節(jié)字符串 L"中文123"
          (占10字節(jié))

          由于不同 ANSI 編碼所規(guī)定的標準是不相同的,因此,對于一個給定的多字節(jié)字符串,我們必須知道它采用的是哪一種編碼規(guī)則,才能夠知道它包含了哪些“字符”。而對于 UNICODE 字符串來說,不管在什么環(huán)境下,它所代表的“字符”內容總是不變的。

          回頁首

          1.3 字符集與編碼

          各個國家和地區(qū)所制定的不同 ANSI 編碼標準中,都只規(guī)定了各自語言所需的“字符”。比如:漢字標準(GB2312)中沒有規(guī)定韓國語字符怎樣存儲。這些 ANSI 編碼標準所規(guī)定的內容包含兩層含義:

          1. 使用哪些字符。也就是說哪些漢字,字母和符號會被收入標準中。所包含“字符”的集合就叫做“字符集”。
          2. 規(guī)定每個“字符”分別用一個字節(jié)還是多個字節(jié)存儲,用哪些字節(jié)來存儲,這個規(guī)定就叫做“編碼”。

          各個國家和地區(qū)在制定編碼標準的時候,“字符的集合”和“編碼”一般都是同時制定的。因此,平常我們所說的“字符集”,比如:GB2312, GBK, JIS 等,除了有“字符的集合”這層含義外,同時也包含了“編碼”的含義。

          UNICODE 字符集”包含了各種語言中使用到的所有“字符”。用來給 UNICODE 字符集編碼的標準有很多種,比如:UTF-8, UTF-7, UTF-16, UnicodeLittle, UnicodeBig 等。

          回頁首

          1.4 常用的編碼簡介

          簡單介紹一下常用的編碼規(guī)則,為后邊的章節(jié)做一個準備。在這里,我們根據(jù)編碼規(guī)則的特點,把所有的編碼分成三類:

          分類 編碼標準 說明
          單字節(jié)字符編碼 ISO-8859-1 最簡單的編碼規(guī)則,每一個字節(jié)直接作為一個 UNICODE 字符。比如,[0xD6, 0xD0] 這兩個字節(jié),通過 iso-8859-1 轉化為字符串時,將直接得到 [0x00D6, 0x00D0] 兩個 UNICODE 字符,即 "?D"。

          反之,將 UNICODE 字符串通過 iso-8859-1 轉化為字節(jié)串時,只能正常轉化 0~255 范圍的字符。
          ANSI 編碼 GB2312,
          BIG5,
          Shift_JIS,
          ISO-8859-2 ……
          把 UNICODE 字符串通過 ANSI 編碼轉化為“字節(jié)串”時,根據(jù)各自編碼的規(guī)定,一個 UNICODE 字符可能轉化成一個字節(jié)或多個字節(jié)。

          反之,將字節(jié)串轉化成字符串時,也可能多個字節(jié)轉化成一個字符。比如,[0xD6, 0xD0] 這兩個字節(jié),通過 GB2312 轉化為字符串時,將得到 [0x4E2D] 一個字符,即 '中' 字。

          “ANSI 編碼”的特點:
          1. 這些“ANSI 編碼標準”都只能處理各自語言范圍之內的 UNICODE 字符。
          2. “UNICODE 字符”與“轉換出來的字節(jié)”之間的關系是人為規(guī)定的。
          UNICODE 編碼 UTF-8,
          UTF-16, UnicodeBig ……
          與“ANSI 編碼”類似的,把字符串通過 UNICODE 編碼轉化成“字節(jié)串”時,一個 UNICODE 字符可能轉化成一個字節(jié)或多個字節(jié)。

          與“ANSI 編碼”不同的是:
          1. 這些“UNICODE 編碼”能夠處理所有的 UNICODE 字符。
          2. “UNICODE 字符”與“轉換出來的字節(jié)”之間是可以通過計算得到的。

          我們實際上沒有必要去深究每一種編碼具體把某一個字符編碼成了哪幾個字節(jié),我們只需要知道“編碼”的概念就是把“字符”轉化成“字節(jié)”就可以了。對于“UNICODE 編碼”,由于它們是可以通過計算得到的,因此,在特殊的場合,我們可以去了解某一種“UNICODE 編碼”是怎樣的規(guī)則。

          回頁首

          2. 字符與編碼在程序中的實現(xiàn)

          2.1 程序中的字符與字節(jié)

          在 C++ 和 Java 中,用來代表“字符”和“字節(jié)”的數(shù)據(jù)類型,以及進行編碼的方法:

          類型或操作 C++ Java
          字符 wchar_t char
          字節(jié) char byte
          ANSI 字符串 char[] byte[]
          UNICODE 字符串 wchar_t[] String
          字節(jié)串→字符串 mbstowcs(), MultiByteToWideChar() string = new String(bytes, "encoding")
          字符串→字節(jié)串 wcstombs(), WideCharToMultiByte() bytes = string.getBytes("encoding")

          以上需要注意幾點:

          1. Java 中的 char 代表一個“UNICODE 字符(寬字節(jié)字符)”,而 C++ 中的 char 代表一個字節(jié)。
          2. MultiByteToWideChar() 和 WideCharToMultiByte() 是 Windows API 函數(shù)。

          回頁首

          2.2 C++ 中相關實現(xiàn)方法

          聲明一段字符串常量:

          // ANSI 字符串,內容長度 7 字節(jié)
          char
          ???? sz[20] = "中文123";

          // UNICODE 字符串,內容長度 5 個 wchar_t(10 字節(jié))
          wchar_t wsz[20] = L"\x4E2D\x6587\x0031\x0032\x0033";

          UNICODE 字符串的 I/O 操作,字符與字節(jié)的轉換操作:

          // 運行時設定當前 ANSI 編碼,VC 格式
          setlocale(LC_ALL, ".936");

          // GCC 中格式
          setlocale(LC_ALL, "zh_CN.GBK");

          // Visual C++ 中使用小寫 %s,按照 setlocale 指定編碼輸出到文件
          // GCC 中使用大寫 %S

          fwprintf(fp, L"%s\n", wsz);

          // 把 UNICODE 字符串按照 setlocale 指定的編碼轉換成字節(jié)
          wcstombs(sz, wsz, 20);
          // 把字節(jié)串按照 setlocale 指定的編碼轉換成 UNICODE 字符串
          mbstowcs(wsz, sz, 20);

          在 Visual C++ 中,UNICODE 字符串常量有更簡單的表示方法。如果源程序的編碼與當前默認 ANSI 編碼不符,則需要使用 #pragma setlocale,告訴編譯器源程序使用的編碼:

          // 如果源程序的編碼與當前默認 ANSI 編碼不一致,
          // 則需要此行,編譯時用來指明當前源程序使用的編碼

          #pragma setlocale
          (".936")

          // UNICODE 字符串常量,內容長度 10 字節(jié)
          wchar_t wsz[20] = L"中文123";

          以上需要注意 #pragma setlocale 與 setlocale(LC_ALL, "") 的作用是不同的,#pragma setlocale 在編譯時起作用,setlocale() 在運行時起作用。

          回頁首

          2.3 Java 中相關實現(xiàn)方法

          字符串類 String 中的內容是 UNICODE 字符串:

          // Java 代碼,直接寫中文
          String
          string = "中文123";

          // 得到長度為 5,因為是 5 個字符
          System.out.println(string.length());

          字符串 I/O 操作,字符與字節(jié)轉換操作。在 Java 包 java.io.* 中,以“Stream”結尾的類一般是用來操作“字節(jié)串”的類,以“Reader”,“Writer”結尾的類一般是用來操作“字符串”的類。

          // 字符串與字節(jié)串間相互轉化

          // 按照 GB2312 得到字節(jié)(得到多字節(jié)字符串)

          byte
          [] bytes = string.getBytes("GB2312");

          // 從字節(jié)按照 GB2312 得到 UNICODE 字符串
          string = newString(bytes, "GB2312");

          // 要將 String 按照某種編碼寫入文本文件,有兩種方法:

          // 第一種辦法:用 Stream 類寫入已經(jīng)按照指定編碼轉化好的字節(jié)串

          OutputStream os = new FileOutputStream("1.txt");
          os.write(bytes);
          os.close();

          // 第二種辦法:構造指定編碼的 Writer 來寫入字符串
          Writer ow = new OutputStreamWriter(new FileOutputStream("2.txt"), "GB2312");
          ow.write(string);
          ow.close();

          /* 最后得到的 1.txt 和 2.txt 都是 7 個字節(jié) */

          如果 java 的源程序編碼與當前默認 ANSI 編碼不符,則在編譯的時候,需要指明一下源程序的編碼。比如:

          E:\>javac -encoding BIG5 Hello.java

          以上需要注意區(qū)分源程序的編碼與 I/O 操作的編碼,前者是在編譯時起作用,后者是在運行時起作用。

          回頁首

          3. 幾種誤解,以及亂碼產(chǎn)生的原因和解決辦法

          3.1 容易產(chǎn)生的誤解
            對編碼的誤解
          誤解一 在將“字節(jié)串”轉化成“UNICODE 字符串”時,比如在讀取文本文件時,或者通過網(wǎng)絡傳輸文本時,容易將“字節(jié)串”簡單地作為單字節(jié)字符串,采用每“一個字節(jié)”就是“一個字符”的方法進行轉化。

          而實際上,在非英文的環(huán)境中,應該將“字節(jié)串”作為 ANSI 字符串,采用適當?shù)木幋a來得到 UNICODE 字符串,有可能“多個字節(jié)”才能得到“一個字符”。

          通常,一直在英文環(huán)境下做開發(fā)的程序員們,容易有這種誤解。
          誤解二 在 DOS,Windows 98 等非 UNICODE 環(huán)境下,字符串都是以 ANSI 編碼的字節(jié)形式存在的。這種以字節(jié)形式存在的字符串,必須知道是哪種編碼才能被正確地使用。這使我們形成了一個慣性思維:“字符串的編碼”。

          當 UNICODE 被支持后,Java 中的 String 是以字符的“序號”來存儲的,不是以“某種編碼的字節(jié)”來存儲的,因此已經(jīng)不存在“字符串的編碼”這個概念了。只有在“字符串”與“字節(jié)串”轉化時,或者,將一個“字節(jié)串”當成一個 ANSI 字符串時,才有編碼的概念。

          不少的人都有這個誤解。

          第一種誤解,往往是導致亂碼產(chǎn)生的原因。第二種誤解,往往導致本來容易糾正的亂碼問題變得更復雜。

          在這里,我們可以看到,其中所講的“誤解一”,即采用每“一個字節(jié)”就是“一個字符”的轉化方法,實際上也就等同于采用 iso-8859-1 進行轉化。因此,我們常常使用 bytes = string.getBytes("iso-8859-1") 來進行逆向操作,得到原始的“字節(jié)串”。然后再使用正確的 ANSI 編碼,比如 string = new String(bytes, "GB2312"),來得到正確的“UNICODE 字符串”。

          回頁首

          3.2 非 UNICODE 程序在不同語言環(huán)境間移植時的亂碼

          非 UNICODE 程序中的字符串,都是以某種 ANSI 編碼形式存在的。如果程序運行時的語言環(huán)境與開發(fā)時的語言環(huán)境不同,將會導致 ANSI 字符串的顯示失敗。

          比如,在日文環(huán)境下開發(fā)的非 UNICODE 的日文程序界面,拿到中文環(huán)境下運行時,界面上將顯示亂碼。如果這個日文程序界面改為采用 UNICODE 來記錄字符串,那么當在中文環(huán)境下運行時,界面上將可以顯示正常的日文。

          由于客觀原因,有時候我們必須在中文操作系統(tǒng)下運行非 UNICODE 的日文軟件,這時我們可以采用一些工具,比如,南極星,AppLocale 等,暫時的模擬不同的語言環(huán)境。

          回頁首

          3.3 網(wǎng)頁提交字符串

          當頁面中的表單提交字符串時,首先把字符串按照當前頁面的編碼,轉化成字節(jié)串。然后再將每個字節(jié)轉化成 "%XX" 的格式提交到 Web 服務器。比如,一個編碼為 GB2312 的頁面,提交 "中" 這個字符串時,提交給服務器的內容為 "%D6%D0"。

          在服務器端,Web 服務器把收到的 "%D6%D0" 轉化成 [0xD6, 0xD0] 兩個字節(jié),然后再根據(jù) GB2312 編碼規(guī)則得到 "中" 字。

          在 Tomcat 服務器中,request.getParameter() 得到亂碼時,常常是因為前面提到的“誤解一”造成的。默認情況下,當提交 "%D6%D0" 給 Tomcat 服務器時,request.getParameter() 將返回 [0x00D6, 0x00D0] 兩個 UNICODE 字符,而不是返回一個 "中" 字符。因此,我們需要使用 bytes = string.getBytes("iso-8859-1") 得到原始的字節(jié)串,再用 string = new String(bytes, "GB2312") 重新得到正確的字符串 "中"。

          回頁首

          3.4 從數(shù)據(jù)庫讀取字符串

          通過數(shù)據(jù)庫客戶端(比如 ODBC 或 JDBC)從數(shù)據(jù)庫服務器中讀取字符串時,客戶端需要從服務器獲知所使用的 ANSI 編碼。當數(shù)據(jù)庫服務器發(fā)送字節(jié)流給客戶端時,客戶端負責將字節(jié)流按照正確的編碼轉化成 UNICODE 字符串。

          如果從數(shù)據(jù)庫讀取字符串時得到亂碼,而數(shù)據(jù)庫中存放的數(shù)據(jù)又是正確的,那么往往還是因為前面提到的“誤解一”造成的。解決的辦法還是通過 string = new String( string.getBytes("iso-8859-1"), "GB2312") 的方法,重新得到原始的字節(jié)串,再重新使用正確的編碼轉化成字符串。

          回頁首

          3.5 電子郵件中的字符串

          當一段 Text 或者 HTML 通過電子郵件傳送時,發(fā)送的內容首先通過一種指定的字符編碼轉化成“字節(jié)串”,然后再把“字節(jié)串”通過一種指定的傳輸編碼(Content-Transfer-Encoding)進行轉化得到另一串“字節(jié)串”。比如,打開一封電子郵件源代碼,可以看到類似的內容:

          Content-Type: text/plain;
          ??????? charset="gb2312"
          Content-Transfer-Encoding: base64

          sbG+qcrQuqO17cf4yee74bGjz9W7+b3wudzA7dbQ0MQNCg0KvPKzxqO6uqO17cnnsaPW0NDEDQoNCg==

          最常用的 Content-Transfer-Encoding 有 Base64 和 Quoted-Printable 兩種。在對二進制文件或者中文文本進行轉化時,Base64 得到的“字節(jié)串”比 Quoted-Printable 更短。在對英文文本進行轉化時,Quoted-Printable 得到的“字節(jié)串”比 Base64 更短。

          郵件的標題,用了一種更簡短的格式來標注“字符編碼”和“傳輸編碼”。比如,標題內容為 "中",則在郵件源代碼中表示為:

          // 正確的標題格式
          Subject: =?GB2312?B?1tA=?=

          其中,

          • 第一個“=?”與“?”中間的部分指定了字符編碼,在這個例子中指定的是 GB2312。
          • “?”與“?”中間的“B”代表 Base64。如果是“Q”則代表 Quoted-Printable。
          • 最后“?”與“?=”之間的部分,就是經(jīng)過 GB2312 轉化成字節(jié)串,再經(jīng)過 Base64 轉化后的標題內容。

          如果“傳輸編碼”改為 Quoted-Printable,同樣,如果標題內容為 "中":

          // 正確的標題格式
          Subject: =?GB2312?Q?=D6=D0?=

          如果閱讀郵件時出現(xiàn)亂碼,一般是因為“字符編碼”或“傳輸編碼”指定有誤,或者是沒有指定。比如,有的發(fā)郵件組件在發(fā)送郵件時,標題 "中":

          // 錯誤的標題格式
          Subject: =?ISO-8859-1?Q?=D6=D0?=

          這樣的表示,實際上是明確指明了標題為 [0x00D6, 0x00D0],即 "?D",而不是 "中"。

          回頁首

          4. 幾種錯誤理解的糾正

          誤解:“ISO-8859-1 是國際編碼?”

          非也。iso-8859-1 只是單字節(jié)字符集中最簡單的一種,也就是“字節(jié)編號”與“UNICODE 字符編號”一致的那種編碼規(guī)則。當我們要把一個“字節(jié)串”轉化成“字符串”,而又不知道它是哪一種 ANSI 編碼時,先暫時地把“每一個字節(jié)”作為“一個字符”進行轉化,不會造成信息丟失。然后再使用 bytes = string.getBytes("iso-8859-1") 的方法可恢復到原始的字節(jié)串。

          誤解:“Java 中,怎樣知道某個字符串的內碼?”

          Java 中,字符串類 java.lang.String 處理的是 UNICODE 字符串,不是 ANSI 字符串。我們只需要把字符串作為“抽象的符號的串”來看待。因此不存在字符串的內碼的問題。

          posted @ 2006-06-23 11:30 點滴鑄就輝煌 閱讀(296) | 評論 (0)編輯 收藏

          2006年6月2日

               摘要: 1 、重要的語言變化 l???????? 泛型( Generics ) l???????? ...  閱讀全文
          posted @ 2006-06-02 17:21 點滴鑄就輝煌 閱讀(450) | 評論 (0)編輯 收藏
          僅列出標題  下一頁
           
          主站蜘蛛池模板: 犍为县| 阳信县| 迁安市| 长沙县| 兰坪| 石门县| 鹤庆县| 普宁市| 丹凤县| 安义县| 新晃| 南城县| 宣汉县| 建平县| 延安市| 封开县| 苍溪县| 柳州市| 娄底市| 衡阳县| 芒康县| 怀集县| 余干县| 桓台县| 广灵县| 乌拉特前旗| 宁陵县| 崇义县| 台南市| 岗巴县| 衢州市| 和田县| 宁乡县| 文水县| 泾川县| 鹰潭市| 资源县| 会理县| 敦煌市| 武城县| 石河子市|