每日一得

          不求多得,只求一得 about java,hibernate,spring,design,database,Ror,ruby,快速開發(fā)
          最近關(guān)心的內(nèi)容:SSH,seam,flex,敏捷,TDD
          本站的官方站點是:顛覆軟件

            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            220 隨筆 :: 9 文章 :: 421 評論 :: 0 Trackbacks

          5? Jive 的其他組件技術(shù)

          Jive 是一個比較豐富的知識寶藏,從中可以學(xué)習(xí)到很多新的實戰(zhàn)技巧和具體功能實現(xiàn)方式。前面基本介紹了 Jive 中的一些主要架構(gòu)技術(shù),通過這些技術(shù)可以基本上掌握 Jive 論壇系統(tǒng)。

          Jive 中還有很多非常實用的組件技術(shù)和工具庫,分析學(xué)習(xí)可重用技術(shù),可以在自己具體的項目重復(fù)使用,大大提高了新系統(tǒng)的開發(fā)速度和效率。

          5.1? Jive 的樹形結(jié)構(gòu)

          Jive 的管理功能中提供了將 Jive 數(shù)據(jù)庫數(shù)據(jù)導(dǎo)出到 XML 文件的管理工具,在這個工具功能實現(xiàn)中,使用了樹形結(jié)構(gòu)的遍歷技術(shù)。

          Jive ForumThread 中的第一個 ForumMessage 作為 root ForumMessage ,以這個 ForumMessage 為根節(jié)點,每個 ForumThread 中包含了一套樹形結(jié)構(gòu)。

          TreeWalker 是樹形結(jié)構(gòu)的一個抽象接口,代碼如下:

          public interface TreeWalker {

          ??? // 根節(jié)點

          ??? public ForumMessage getRoot();

          ??? // 獲得父節(jié)點

          ??? public ForumMessage getParent(ForumMessage child)

          ?????????? ?throws ForumMessageNotFoundException;

          ??? // 獲得子節(jié)點

          ??? public ForumMessage getChild(ForumMessage parent, int index)

          ??????????? throws ForumMessageNotFoundException;

          ??? // 獲得所有子節(jié)點

          ??? public Iterator children(ForumMessage parent);

          ??? // 獲得所有的子節(jié)點,包括子節(jié)點的子節(jié)點

          ??? public Iterator recursiveChildren(ForumMessage parent);

          ??? // 獲得一個節(jié)點的深度,相對根節(jié)點而言

          ??? public int getMessageDepth(ForumMessage message);

          ???

          ??? public int getChildCount(ForumMessage parent);

          ??? public int getRecursiveChildCount(ForumMessage parent);

          ?

          ??? /**

          ???? * 返回相對父節(jié)點的子節(jié)點索引。例如

          ???? * <pre>

          ???? *?? 4

          ???? *?? |-- 2

          ???? *?? |-- |-- 1

          ???? *?? |-- |-- 6

          ???? *?? |-- |-- 8

          ???? *?? |-- 5

          ???? * </pre>

          ???? * getIndexOfChild(4, 2) 將返回 0

          ???? * getIndexOfChild(4, 5) 將返回 1

          ???? * getIndexOfChild(2, 1) 將返回 0

          ???? * getIndexOfChild(2, 6) 將返回 1

          ???? * getIndexOfChild(2, 8) 將返回 2

          ???? */

          ??? public int getIndexOfChild(ForumMessage parent, ForumMessage child);

          ??? // 一個節(jié)點是否是葉,葉相對枝來說,葉沒有子節(jié)點了

          ??? public boolean isLeaf(ForumMessage node);

          }

          DbTreeWalker TreeWalker 的一個實現(xiàn),它是將一個 ForumThread 下所有帖子的 ID 從數(shù)據(jù)庫中裝入 LongTree 中。一句 LongTree 的樹形結(jié)構(gòu)遍歷核心技術(shù)實現(xiàn) ForumThread 中的帖子結(jié)構(gòu)的遍歷。

          LongTree 類似之前的 Cache 類,封裝了樹形結(jié)構(gòu)遍歷的核心算法,在 LongTree 中建立了 3 個數(shù)組 long [] keys char [] leftChildren char [] rightSiblings

          一個節(jié)點有兩個特性:它有子節(jié)點;它有兄弟節(jié)點。 leftChildren 保存的是這個節(jié)點的子節(jié)點的索引;而 rightSiblings 保存的是這個節(jié)點兄弟節(jié)點的索引。例如:

          ??1000

          ?? |-- 3000

          ?? |-- |--4000

          ?? |-- |--6000

          ?? |-- |--7000

          ?? |-- 5000

          1000 是個根節(jié)點, 1000 下有兩個子節(jié)點 3000 5000 ,而 3000 則有 3 個子節(jié)點 4000 6000 7000 3000 還有一個兄弟節(jié)點 5000 ,使用上述 3 個數(shù)組是這樣保持信息的:

          keys[0] = 1000

          keys[1] = 3000

          keys[2] = 4000

          keys[3] = 5000

          keys[4] = 6000

          keys[5] = 7000

          keys 數(shù)組中保存的是各個節(jié)點的數(shù)值,而 leftChildren rightSiblings 數(shù)組保存的是 keys 數(shù)組的 index ,即 0 1 2 3 4 等數(shù)字。

          1000 節(jié)點有兩個子節(jié)點,那么其對應(yīng)的 leftChildren rightSiblings 分別是:

          leftChildren[0] = 1

          leftChildren[0] 中的索引 0 表示當前索引, keus[0] 1000 ,說明現(xiàn)在節(jié)點是 1000 1 也表示 keys 數(shù)組的索引, keys[1] 的值是 3000 ,所以上一句表示 1000 的子節(jié)點是 3000

          1000 節(jié)點沒有兄弟節(jié)點:

          rightSiblings[0] = - 1

          再看看 3000 節(jié)點,其 keys 的索引 Index 1 ,其子節(jié)點是 4000 6000 7000 ,取最近一個 4000 的索引 index 放入數(shù)組:

          leftChildren[1] = 2

          這表示 1000 節(jié)點的子節(jié)點是 4000 ,那么另外一個 6000 節(jié)點如何表示?這是以 4000 節(jié)點的兄弟節(jié)點表現(xiàn)出來的。 4000 節(jié)點的 keys 的索引 index 2 ,通過下列表示:

          rightSiblings[2] = 4

          其中, 4 表示 6000 keys 中的索引 Index 。同樣,第 3 個子節(jié)點 7000 表示如下:

          rightSiblings[4] = 5

          這樣, 3000 節(jié)點有 3 個子節(jié)點 4000 6000 7000 4000 6000 7000 是兄弟節(jié)點)通過上述簡單兩句就表現(xiàn)出來了。

          總結(jié)一個父子關(guān)系樹的表示方法:在父節(jié)點中,使用 leftChildren 保存最靠近父節(jié)點的一個子節(jié)點(父節(jié)點的第一個兒子,叫長子)的索引,其他子節(jié)點則是通過 rightSiblings 表明與長子是兄弟關(guān)系。

          看看 LongTress 的初始化構(gòu)造方法, keys 數(shù)組的值保存的是 ForumMessage ID ,如下:

          public LongTree(long rootKey, int size) {

          ??? keys = new long[size+1];?? ???????????????????? // 初始化

          ??? leftChildren = new char[size+1]; ????????????? // 初始化

          ??? rightSiblings = new char[size+1]; ???????????? // 初始化

          ?

          ??? // keys[1] 中保存的是 rootMessage ID

          ??? keys[1] = rootKey;

          ??? leftChildren[1] = 0;?? ????????????????????????????? // 無子節(jié)點

          ??? rightSiblings[1] = 0;? ?????????????????????????????? // 無兄弟姐妹

          }

          當加入一個節(jié)點時,其方法如下:

          public void addChild(long parentKey, long newKey) {

          ??? // 根據(jù) parentKey 找出其對應(yīng)的 keys 索引 index

          ??? char parentIndex = findKey(parentKey, (char)1);

          ??? if (parentIndex == 0) {

          ??????????? throw new IllegalArgumentException("Parent key " + parentKey +

          ??????????????????? " not found when adding child " + newKey + ".");

          ??? }

          ?

          ??? // newKey 創(chuàng)建節(jié)點

          ??? keys[nextIndex] = newKey;

          ??? leftChildren[nextIndex] = 0;

          ??? rightSiblings[nextIndex] = 0;

          ?

          ??? // 將新建節(jié)點標志為父節(jié)點的子節(jié)點

          ??? if (leftChildren[parentIndex] == 0) {

          ??????? // 如果父節(jié)點原來沒有子節(jié)點,那就將新建節(jié)點作為其子節(jié)點

          ??????? leftChildren[parentIndex] = nextIndex;

          ??? }else {

          ???? ???// 如果父節(jié)點有子節(jié)點,尋找最后一個子節(jié)點

          ??????? long siblingIndex = leftChildren[parentIndex];

          ?????? // siblingIndex 中循環(huán)查找,直至值為 0

          ??????? while (rightSiblings[new Long(siblingIndex).intValue()] != 0) {

          ???????????? siblingIndex = rightSiblings[new Long(siblingIndex).intValue()];

          ??????? }

          ??????? // 將新建節(jié)點作為最后一個字節(jié)點加入

          ??????? rightSiblings[new Long(siblingIndex).intValue()] = nextIndex;

          ??? }

          ??? // 最后,自動增加 nextIndex 以便下一個等待插入

          ??? nextIndex++;

          }

          Jive 將數(shù)據(jù)導(dǎo)出到 XML 文件時,就是根據(jù)某個 ForumMessage ID ,通過 TreeWalker 找出它的所有子節(jié)點 ForumMessage ID ,然后將其內(nèi)容導(dǎo)出。

          5.2? XML JDOM

          XML 稱為可擴充標記語言,是類似 HTML 定義文檔標記語言的一個框架。 XML 以結(jié)構(gòu)嚴謹著稱,因此用來保存數(shù)據(jù)是非常適合的,這樣在數(shù)據(jù)庫之外,又多了一個持久化保存數(shù)據(jù)的方式。

          Java 中, XML 更多時是作為配置文件數(shù)據(jù)存儲體形式出現(xiàn),在之前一般是使用 properties 來保存系統(tǒng)的配置文件,如下:

          cache.maxsize=1024

          cache.minsize=2

          這兩句分別設(shè)置 cache 的最大值和最小值,那么在 Java 中通過下列語句讀取:

          Properties p = new Properties();

          InputStream fin = new FileInputStream("Config.properties");

          p.load(fin);

          String maxSize = p.getProperty("cache.maxsize ");

          String minSize = p.getProperty("cache.minsize ");

          這樣就可以獲得配置文件中的兩個值。

          這種配置文件使用方法簡單直接,但是只適合配置文件不很復(fù)雜的情況。在復(fù)雜的配置情況下, properties 就不是很合適,例如設(shè)置系統(tǒng)的可選屬性,一個系統(tǒng)安裝在不同應(yīng)用場合,客戶的要求總有些不一樣,有些功能是可選的,那么需要在配置文件中配置一些可選的功能,以 Tomcat server.xml 為例,如下:

          <Context path="/register" docBase="D:/javasource/SimpleRegister/defaultroot" debug="1"

          ???????????????? reloadable="true" crossContext="true">

          </Context>

          <Context path="/examples" docBase="examples" debug="0"

          ???? ????????????reloadable="true" crossContext="true">

          ????????? <Logger className="org.apache.catalina.logger.FileLogger"

          ???????????????????? prefix="localhost_examples_log." suffix=".txt"

          ??????? ??? ??timestamp="true"/>

          </Context>

          在一個配置中有很多 Context ,每個 Contexr 都包含 Logger 等具體配置, XML 格式本身是一種樹形結(jié)構(gòu)的數(shù)據(jù)格式。在實際應(yīng)用中,很多復(fù)雜的表示都可以使用樹形結(jié)構(gòu)來分解代表。因此,使用 XML 來表示這種樹形結(jié)構(gòu)的數(shù)據(jù)無疑是非常合適的。

          Jive 中, jive_config.xml Jive 系統(tǒng)的配置文件。這個配置文件是在 Jive 系統(tǒng)安裝時,按照用戶的選擇動態(tài)生成的,其中包含數(shù)據(jù)庫連接參數(shù)、界面顯示顏色、電子郵件配置以及緩沖配置、搜索配置和文件或圖片上傳配置。

          分析讀取 XML 數(shù)據(jù)有很多工具,如 DOM http://www.worg/DOM/ )和 SAX http://www.saxproject.org/ )。這兩種是標準的 XML 分析器,可以使用任何語言來實現(xiàn), DOM 分析 XML 數(shù)據(jù)時,是將整個文檔一下子讀入內(nèi)存,如果文檔很大,性能就發(fā)生影響,而 SAX 則是動態(tài)地對每一行分析,無需全部讀入,因此在分析大文檔時速度比較快。

          但是這兩種分析方法都是圍繞 XML 樹形結(jié)構(gòu)展開的,在編制這兩種分析器時,會涉及到大量 XML 概念的 API ,需要一定的 XML 基礎(chǔ)和知識,使用起來有一定難度。

          JDOM http://www.jdom.org )封裝了 DOM/SAX 的具體使用技術(shù),以非常符合 Java 編程方式的形式來分析 XML ,因此使用起來非常方便。

          在分析速度方面, JDOM DOM 要快,比 SAX 慢一點。但用在分析配置文件上,速度不是主要的,因為可以使用 lazy initialization 。這類似緩存機制,在第一次讀取后就保存在內(nèi)存中,以后每次直接從內(nèi)存中獲取。

          Jive 中, JDOM 操作基本是由 JiveGlobals 完成的。

          public class JiveGlobals {

            private static final String JIVE_CONFIG_FILENAME = "jive_config.xml";

          private static XMLProperties properties = null;

          ?

          ...

          // 從配置文件獲取配置

            public static String getJiveProperty(String name) {

             loadProperties();

             return properties.getProperty(name);

            }

          ? // JDOM 載入配置文件

            private synchronized static void loadProperties() {

             if (properties == null) {

              properties = new XMLProperties(jiveHome + File.separator +

                      JIVE_CONFIG_FILENAME);

             }

            }

          ?// 將配置保存到配置文件中

          ?public static void setJiveProperty(String name, String value) {

          ?? loadProperties();

          ?? properties.setProperty(name, value);

          ?}

          }

          從上面代碼看出,對 XML 文件讀寫非常方便,使用 properties.getProperty(name) 就可以獲得 name 的配置值,而 properties.setProperty(name, value) 一句就可以將 name 和其值 value 保存到 XML 文件中,非常類似 Hashtable 的讀取和存入。

          XMLProperties JDOM 的一個屬性文件輔助包,它主要是對屬性名進行分解和合成,例如 XML 如下:

          <jive>

            <email>

             <fromName>Jive_Administrator</fromName>

             <fromEmail>webmaster@example.com</fromEmail>

             <subject>Your thread was updated!</subject>

             <body>Hello {name}! The thread {threadName} was updated!</body>

            </email>

          <jive>

          jive/email/fromName 的值是 Jive_Administrator ,那么如何讀取 Jive_Administrator ?使用 properties.getProperty("email.fromName") 就可以。注意到,這里 Key 的名字組合是 email.fromName ,這種特定的寫法就是 XMLProperties 可以支持的,在對 XML 文件保存細節(jié)中,由 XMLProperties 將這種屬性名稱寫法具體轉(zhuǎn)換成 XML 文檔操作。具體內(nèi)部代碼如下:

          ? public String getProperty(String name) {

          ??????? if (propertyCache.containsKey(name)) {? // 從緩存中獲取

          ??????????? return (String)propertyCache.get(name);

          ??????? }

          ???????? // email.fromName 轉(zhuǎn)變?yōu)?/span> String 數(shù)組

          ???????? // 例如 propName[0] = jive; propName[1] = email …

          ??????? String[] propName = parsePropertyName(name);

          ??????? // 通過 propName 數(shù)組循環(huán),遍歷 XML 的樹形結(jié)構(gòu)層次,尋找出對應(yīng)的屬性值

          ??????? Element element = doc.getRootElement();

          ??????? for (int i = 0; i < propName.length; i++) {

          ??????????? element = element.getChild(propName[i]);

          ??????????? if (element == null) {

          ??????????????? return null;

          ?????? ?????}

          ??????? }

          ??????? // 尋找到 element 后,獲得其內(nèi)容

          ??????? String value = element.getText();

          ??????? if ("".equals(value)) {??????????? return null;??????? }

          ??????? else {

          ??????????? // 保存到緩存中

          ??????????? value = value.trim();

          ??????????? propertyCache.put(name, value);

          ??????????? return value;

          ??????? }

          ??? }

          以上只是分析了 JDOM XMLProperties 包是如何做屬性配置提取的,正是因為 JDOM 內(nèi)部做了很多基礎(chǔ)支持性的細節(jié)工作,才使得使用 JDOM 變得非常方便。

          總結(jié)使用 JDOM 對配置文件讀寫操作語法如下:

          ·????????? 獲得配置(查詢): getProperty(name)

          ·????????? 新增和修改: properties.setProperty(name, value)

          ·????????? 刪除: properties.deleteProperty(name)

          name 的格式是 xxx.xxx.xxx ,例如:

          <jive>

          ?? …

          <upload>

          ??????? <dir>/home/jdon/jive/upload/</dir>

          ??????? <relurl>upload/</relurl>

          ??? </upload>

          ??? …

          </jive>

          要獲得 /home/jdon/jive/upload/ name 的格式是 upload.dir ;要獲得 upload/ name 的格式是 upload.relurl

          注意,如果要在系統(tǒng)中支持上述功能,必須下載 JDOM 包,還要有 DataFormatFilter. java DataUnformatFilter.java XMLFilterBase.java XMLProperties.java 支持。這幾個類不包含在 JDOM 標準包中,作為一個應(yīng)用包含在其 Sample 中。當然也可以直接從 Jive 中復(fù)制出來使用。

          5.3? 全文檢索和 Lucene

          Jive 中支持全文檢索,這個功能主要核心依賴另外一個開放源代碼項目 Lucene http://jakarta.apache.org/lucene/docs/index.html )。 Jakarta Lucene 是一個高性能全文搜索引擎,可以跨平臺應(yīng)用于任何搜索應(yīng)用。

          使用 Lucene 作為搜索引擎,應(yīng)用系統(tǒng)需要做兩件事情:

          1 )建立索引文件。將 Jive 數(shù)據(jù)庫中的數(shù)據(jù)內(nèi)容建立索引文件,這是通過 SearchManager 來完成。 SearchManager 代碼如下:

          public interface SearchManager {

          ??? public boolean isSearchEnabled();

          ??? public void setSearchEnabled(boolean searchEnabled);

          ??? /**

          ??? // 如果 SearchManage 正在工作,返回真

          ??? public boolean isBusy();

          ??? // 返回索引完成率

          ??? public int getPercentComplete();

          ??? // 是否自動建立索引

          ??? // 通過 TaskEngine.scheduleTask 方法實現(xiàn)定期自動索引

          ??? public boolean isAutoIndexEnabled();

          ??? public void setAutoIndexEnabled(boolean value);

          ??? // 自動索引間隔的分鐘數(shù)

          ??? public int getAutoIndexInterval();

          ??? public void setAutoIndexInterval(int minutes);

          ??? // 獲得上次建立索引的時間

          ??? public Date getLastIndexedDate();

          // 在實時建立索引時,將當前帖子加入索引

          ??? public void addToIndex(ForumMessage message);

          ??? public void removeFromIndex(ForumMessage message);

          ??? // 手動更新自上次建立索引后的新內(nèi)容

          ??? public void updateIndex();

          ??? // 手動重新建立全部的索引

          ??? public void rebuildIndex();

          ??? // 優(yōu)化

          ??? public void optimize();

          }

          ·????????? SearchManager 定義了建立索引的一些屬性,建立索引有兩種方式:當有新帖子加入時,通過調(diào)用 indexMessage() 方法實時索引;或者通過 TaskEngine.scheduleTask 方法每隔一定時間建立索引。

          ·????????? DbSearchManager 作為 SearchManager 的子類實現(xiàn),又是一個線程類,它是建立索引的主要功能類。在 DbSearchManager 中主要使用了 Lucene IndexWriter Analyzer Document Field 等功能類來建立索引。

          ·????????? IndexWriter 用戶建立新的索引,當然也可以將文檔加入已經(jīng)存在的索引。

          在文本被索引之前,它必須通過一個分析器 Analyzer 。分析器 Analyzer 負責從文本中分離出索引關(guān)鍵字。 Lucene 有幾種不同類型的分析器:

          ·????????? SimpleAnalyzer 是將英文轉(zhuǎn)換為小寫字母,按空格和標點符號切分出英文單詞,

          I am Java 這一句,使用 SimpleAnalyzer 切詞就會切分出下列詞語:

          token1=I

          token2=am

          token3=Java

          ·????????? StandardAnalyzer 是對英文進行了較為復(fù)雜的處理。除了按詞語建立索引關(guān)鍵字( token )外,還能夠為特殊名稱、郵件地址、縮寫格式等建立索引單元,而且對“ and ”、“ the ”等詞語做了過濾。

          ·????????? ChineseAnalyzer 是專門用來分析中文的索引的。關(guān)于中文分析器,有很多嘗試,如車東的 http://sourceforge.net/projects/weblucene/ zhoujun http://www. jdon.com/jive/thread.jsp? forum=61&thread=8400 等,該問題將在后面章節(jié)繼續(xù)討論。

          一個索引是由一系列 Document 組成,每個 Document 是由一個或多個 Field 組成,每個 Field 都有一個名字和值,可以把 Document 作為關(guān)系數(shù)據(jù)庫中一條記錄,而 Field 則是記錄中某列字段。一般建立索引如下:

          // 指定將在哪個目錄建立索引

          String indexDir = "/home/jdon/jive/WEB-INF/jiveHome";?

          // 指定將要建立索引的文本

          String text = "welcom here, I am Java,";?????

          Analyzer analyzer = new StandardAnalyzer();?? // 使用 StandardAnalyzer

          // 建立一個 IndexWriter

          IndexWriter writer = new IndexWriter(indexDir, analyzer, true);

          // 建立 Document

          Document document? = new Document();

          // 進行切詞、索引

          document.add(Field.Text("fieldname", text));

          // 加入索引中

          writer.addDocument(document);

          writer.close();

          其中, Field 根據(jù)具體要求有不同用法, Lucene 提供 4 種類型的 Field: Keyword UnIndexed UnStored Text

          ·????????? Keyword 不實現(xiàn)切詞,逐字地保存在索引中,這種類型適合一些如 URL 、日期、個人姓名、社會安全號碼、電話號碼等需要原封不動保留的詞語。

          ·????????? UnIndexed 既不實現(xiàn)切詞也不索引,但是其值是一個詞一個詞地保存在索引中,這不適合很大很長的詞語,適合于顯示一些不經(jīng)過直接搜索的結(jié)果值。

          ·????????? UnStored UnIndexed 正好相反,將被切詞和索引,但是不保存在索引中,這適合巨大文本,如帖子內(nèi)容、頁面內(nèi)容等。

          ·????????? Text 是實現(xiàn)切詞、索引,并且保存在索引中。

          Jive 中,索引的建立以 DbSearchManager 中加入帖子索引方法為例:

          protected final void addMessageToIndex(long messageID, long userID,

          ??????????? long threadID, long forumID, String subject, String body,

          ??????????? java.util.Date creationDate, IndexWriter writer) throws IOException

          {

          ??? // 建立一個 ? Document

          ??? Document doc = new Document();

          ??? doc.add(Field.Keyword("messageID",Long.toString(messageID)));

          ??? doc.add(new Field("userID", Long.toString(userID), false, true, false));

          ?? doc.add(new Field("threadID", Long.toString(threadID), false, true, false));

          ??? doc.add(new Field("forumID", Long.toString(forumID), false, true, false));

          ??? doc.add(Field.UnStored("subject", subject));

          ??? doc.add(Field.UnStored("body", body));

          ??? doc.add(new Field("creationDate", DateField.dateToString(creationDate),

          ??????????????? false, true, false));

          ??? // 將該 Document 加入當前索引中

          ??? writer.addDocument(doc);

          }

          DbSearchManager 中同時也實現(xiàn)了自動建立索引的過程,通過在構(gòu)造方法中生成 TimeTask 實例:

          timerTask = TaskEngine.scheduleTask(

          ??????????????????? this,autoIndexInterval*JiveGlobals.MINUTE,

          ??????????????????? autoIndexInterval*JiveGlobals.MINUTE);

          因為 DbSearchManager 是線程類,它在 run 方法中實現(xiàn)索引任務(wù)自動運行:

          TaskEngine.addTask(new IndexTask(false));

          2 )建立完成后,就可以直接搜索特定的詞語了。搜索語句一般代碼如下:

          Searcher searcher = new IndexSearcher((indexDir);? // 創(chuàng)建一個搜索器

          // 使用和索引同樣的語言分析器

          Query query = QueryParser.parse(queryString, "body", new StandardAnalyzer());

          // 搜索結(jié)果使用 Hits 存儲

          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("fieldname "));

          };

          Jive 實現(xiàn)搜索就復(fù)雜得多,它為搜索專門建立了一個 Query 接口:

          public interface Query {

          ??? // 需要搜索的字符串

          ??? public String getQueryString();

          ??? public void setQueryString(String queryString);

          ?

          ??? public Date getBeforeDate();

          ??? public void setBeforeDate(Date beforeDate);

          ?

          ??? public Date getAfterDate();

          ??? public void setAfterDate(Date afterDate);

          ?

          ??? public User getFilteredUser();

          ??? public void filterOnUser(User user);

          ?

          ??? public ForumThread getFilteredThread();

          ??? public void filterOnThread(ForumThread thread);

          ?

          ??? public int resultCount();

          ??? public Iterator results();

          ??? public Iterator results(int startIndex, int numResults);

          }

          Query 接口中主要定義了和搜索相關(guān)的一些參數(shù),可以根據(jù)具體要求定制,直接使用 Query 就可以達到搜索的目的,如需要搜索 Java is cool ,那么使用下列代碼:

          ForumFactory forumFactory = ForumFactory.getInstance();

          Query query = forumFactory.createQuery(forums);

          query.setQueryString("Jive is cool");

          Iterator iter = query.results();

          while (iter.hasNext()) {

          ???? ForumMessage message = (ForumMessage)iter.nextElement();

          ???? // 輸出結(jié)果

          }

          追查代碼會發(fā)現(xiàn),上面 forumFactory.createQuery(forums) 方法實際內(nèi)容是 new DbQuery(forums, this) DbQuery 作為 Query 的一個子類,它的搜索語句通過 executeQuery() 方法中下列語句實現(xiàn):

          private void executeQuery() {

          ??? try {

          ?????? Searcher searcher = getSearcher();? // 創(chuàng)建一個搜索器

          ?????? …

          ?????? // 使用分析器獲得 Query 對象

          ?????? org.apache.lucene.search.Query bodyQuery =

          ??????????????? QueryParser.parse(queryString, "body", DbSearchManager.analyzer);

          ????? org.apache.lucene.search.Query subjectQuery =

          ??????????????? QueryParser.parse(queryString, "subject", DbSearchManager.analyzer);

          ??????? // 將兩個 Query 對象加入 BooleanQuery

          ??????? BooleanQuery comboQuery = new BooleanQuery();

          ??????? comboQuery.add(subjectQuery,false,false);

          ??????? comboQuery.add(bodyQuery,false,false);

          ??????? //Jive 自己的搜索結(jié)果過濾器

          ??????? MultiFilter multiFilter = new MultiFilter(3);

          ??????? int filterCount = 0;

          ????

          ??????? if (factory.getForumCount() != forums.length) {

          ???????????? // 將其他論壇內(nèi)容搜索結(jié)果過濾掉

          ??????????? String[] forumIDs = new String[forums.length];

          ???????????? for (int i=0; i<forumIDs.length; i++) {

          ??????????????? forumIDs[i] = Long.toString(forums[i].getID());

          ???????????? }

          ???????????? multiFilter.add(new FieldFilter("forumID", forumIDs));

          ???????????? filterCount++;

          ??????? }

          ?

          ???????? // 日期過濾器 ? 如只查詢某日期以后的內(nèi)容

          ??????? if (beforeDate != null || afterDate != null) {

          ??????????? if (beforeDate != null && afterDate != null) {

          ??????????????? multiFilter.add(new DateFilter("creationDate", beforeDate, afterDate));

          ??????????????? filterCount++;

          ?? ??????????}else if (beforeDate == null) {

          ??????????????? multiFilter.add(DateFilter.After("creationDate", afterDate));

          ???????????????? filterCount++;

          ???????????? }else {

          ???????????????? multiFilter.add(DateFilter.Before("creationDate", beforeDate));

          ? ???????????????filterCount++;

          ???????????? }

          ??????? }

          ??????? // 過濾用戶

          ??????? if (user != null) {

          ???????????? String userID = Long.toString(user.getID());

          ???????????? multiFilter.add(new FieldFilter("userID", userID));

          ???????????? filterCount++;

          ??????? }

          ??????? // 主題過濾

          ??????? if (thread != null) {

          ???????????? String threadID = Long.toString(thread.getID());

          ????????? ???multiFilter.add(new FieldFilter("threadID", threadID));

          ???????????? filterCount++;

          ??????? }

          ??????? if (filterCount > 0) {// 實現(xiàn)搜索

          ???????????? hits = searcher.search(comboQuery, multiFilter);

          ??? ????} else {

          ???????????? hits = searcher.search(comboQuery);

          ??????? }

          ??????? // 搜索結(jié)果不要超過最大大小

          ??????? int numResults = hits.length() < MAX_RESULTS_SIZE ?

          ??????????????????? hits.length() : MAX_RESULTS_SIZE;

          ??????? long [] messages = new long[numResults];

          ??????? for (int i=0; i<numResults; i++) {

          ?????????? messages[i]= Long.parseLong( ((Document)hits.doc(i)).get("messageID") );

          ?????? }

          ??????? results = messages;

          ????? } catch (Exception e) {

          ??????? e.printStackTrace();

          ??????? results = new long[0];

          ????? }

          }

          Jive 的搜索使用了過濾器,以便過濾掉不想出現(xiàn)的結(jié)果,然后還對搜索結(jié)果進行了限制轉(zhuǎn)換,這些在實際使用中都是必需的。

          5.4? Jive 的中文問題

          Jive 默認的字符集編碼方式是 ISO8859_1 ,即 Latin-1 字符集,這是國際標準化組織用來表示 Latin 等西方語言使用的字符集。

          ISO8859_1 字符集非常類似常見的 ASCII 字符集。由于 ISO8859_1 是使用單字節(jié)來表示,而漢字是采取雙字節(jié)來表示一個漢字,我國制定了一套專門用來表示漢字 GB2312 GBK 編碼字符集。

          Java 內(nèi)部運算中,涉及到的所有字符串都會被轉(zhuǎn)化為 UTF-8 編碼來進行運算。那么,在被 Java 轉(zhuǎn)化之前,字符串是什么樣的字符集? Java 總是根據(jù)操作系統(tǒng)的默認編碼字符集來決定字符串的初始編碼,而且 Java 系統(tǒng)的輸入和輸出的都是采取操作系統(tǒng)的默認編碼。

          因此,如果能統(tǒng)一 Java 系統(tǒng)的輸入、輸出和操作系統(tǒng) 3 者的編碼字符集合,將能夠使 Java 系統(tǒng)正確處理和顯示漢字。這是處理 Java 系統(tǒng)漢字的一個原則,但是在實際項目中,能夠正確抓住和控制住 Java 系統(tǒng)的輸入和輸出部分是比較難的。

          Jive 是運行在 Web 容器中的一個 Servlet/JSP 系統(tǒng)。在這個系統(tǒng)中,輸入途徑有很多種:一種是通過頁面表單打包成請求( request )發(fā)往服務(wù)器的;第二種是通過數(shù)據(jù)庫讀入;還有第 3 種輸入比較復(fù)雜, JSP 在第一次運行時總是被編譯成 Servlet JSP 中常常包含中文字符,那么編譯使用 javac 時, Java 將根據(jù)默認的操作系統(tǒng)編碼作為初始編碼。除非特別指定,如在 Jbuilder 中可以指定默認的字符集。

          輸出途徑也有幾種:第一種是 JSP 頁面的輸出。由于 JSP 頁面已經(jīng)被編譯成 Servlet ,那么在輸出時,也將根據(jù)操作系統(tǒng)的默認編碼來選擇輸出編碼,除非指定輸出編碼方式;還有輸出途徑是數(shù)據(jù)庫,將字符串輸出到數(shù)據(jù)庫。

          由此看來,一個 J2EE 系統(tǒng)的輸入輸出是非常復(fù)雜,而且是動態(tài)變化的,而 Java 是跨平臺運行的,在實際編譯和運行中,都可能涉及到不同的操作系統(tǒng),如果任由 Java 自由根據(jù)操作系統(tǒng)來決定輸入輸出的編碼字符集,這將不可控制地出現(xiàn)亂碼。

          正是由于 Java 的跨平臺特性,使得字符集問題必須由具體系統(tǒng)來統(tǒng)一解決,所以在一個 Java 應(yīng)用系統(tǒng)中,解決中文亂碼的根本辦法是明確指定整個應(yīng)用系統(tǒng)統(tǒng)一字符集。

          Jive 中如果指定默認字符集為某個字符集,那么就要在所有的輸入輸出環(huán)節(jié)都要標識為這個字符集。但是,前面已經(jīng)提到,要完全在編碼時做到還是有一定難度,必須對 Web 程序有相當?shù)卣莆蘸屠斫猓也襟E較繁瑣。

          有一種相對省事的做法,例如統(tǒng)一指定為 ISO8859_1 ,因為目前大多數(shù)軟件都是西方人編制的,他們默認的字符集就是 ISO8859_1 ,包括操作系統(tǒng) Linux 和數(shù)據(jù)庫 MySQL 等。這樣,如果指定 Jive 統(tǒng)一編碼為 ISO8859_1 ,那么就有下面 3 個環(huán)節(jié)必須把握:

          ·????????? 開發(fā)和編譯代碼時指定字符集為 ISO8859_1

          ·????????? 運行操作系統(tǒng)的默認編碼必須是 ISO8859_1 ,如 Linux

          ·????????? JSP 頭部聲明: <%@ page contentType="text/html;charset=ISO8859_1" %>

          如果統(tǒng)一指定為 GBK 中文字符集,上述 3 個環(huán)節(jié)同樣需要做到,不同的是只能運行在默認編碼為 GBK 的操作系統(tǒng),如中文 Windows

          所以統(tǒng)一編碼為 ISO8859_1 GBK 雖然帶來編制代碼的方便,但是也破壞了 Java 跨平臺運行的優(yōu)越性,只在一定范圍內(nèi)行得通。

          很多情況下,程序員大都是在中文 Windows 下開發(fā)調(diào)試 Java 系統(tǒng),然后直接部署到 Linux 等系統(tǒng)上真正運行。而且其中可能涉及到 XML 文件讀寫。 XML 是對編碼方式要求嚴格的數(shù)據(jù)存儲體, XML 又可以隨著代碼移動。因此,在進行真正大規(guī)模 Java 系統(tǒng)開發(fā)運行時,上述臨時簡單的變通方式就沒有效果了。

          要從根本上解決 Java 的中文問題,只要將 Java 系統(tǒng)的統(tǒng)一編碼定義為 UTF-8 UTF-8 編碼是一種兼容所有語言的編碼方式,惟一比較麻煩的就是要找到應(yīng)用系統(tǒng)的所有出入口,然后使用 UTF-8 去“結(jié)扎”它。

          Jive 默認的字符集編碼方式是 ISO8859_1 ,如果都統(tǒng)一為 UTF-8 ,那么也需要做下列幾步工作:

          ·????????? 開發(fā)和編譯代碼時指定字符集為 UTF-8

          ·????????? 使用過濾器,將所有請求( request )轉(zhuǎn)換為 UTF-8 ;針對不同應(yīng)用過濾器有兩種。

          ·????????? 如果所有請求都經(jīng)過一個 Servlet 控制分配器,那么使用 Servlet filter 執(zhí)行語句。

          ·????????? request.setCharacterEncoding("UTF-8")

          ·????????? 如果不經(jīng)過 Servlet ,而直接是 JSP ,那么每個 JSP 頭部設(shè)置上述語句。

          ·????????? JSP 頭部聲明: <%@ page contentType="text/html;charset= UTF-8" %>

          ·????????? 設(shè)定數(shù)據(jù)庫連接方式是 UTF-8

          以上討論了 Jive 以及通用 Java 的中文問題。如果整個應(yīng)用系統(tǒng)是從開始進行開發(fā),那么統(tǒng)一指定編碼為 UTF-8 就非常容易做到。如果是在英文源代碼基礎(chǔ)上二次開發(fā),那么首先要將原來的源代碼轉(zhuǎn)換為統(tǒng)一編碼 UTF-8 ,那么這種轉(zhuǎn)換工作會帶來一定的麻煩。

          posted on 2006-08-31 12:27 Alex 閱讀(659) 評論(0)  編輯  收藏 所屬分類: java
          主站蜘蛛池模板: 威海市| 华蓥市| 夹江县| 寻乌县| 新和县| 如东县| 大渡口区| 土默特左旗| 兰西县| 长宁县| 平利县| 盐山县| 辉南县| 土默特左旗| 姜堰市| 偃师市| 莲花县| 镇江市| 阿图什市| 杨浦区| 崇礼县| 理塘县| 莒南县| 大关县| 柘荣县| 兰坪| 武威市| 高要市| 玛纳斯县| 津市市| 宝应县| 峨眉山市| 治县。| 饶河县| 丰都县| 汉源县| 邹城市| 嘉义市| 临安市| 依安县| 阿拉善左旗|