Lucene 是 Apache Jakarta 的一個(gè)子項目,是一個(gè)全文檢索的搜索引擎庫。其提供了簡(jiǎn)單實(shí)用的 API,通過(guò)這些 API,可以自行編寫(xiě)對文件(TEXT/XML/HTML等)、目錄、數據庫的全文檢索程序。
Features:
* Very fast indexing, minimal RAM required
* Index compression to 30% of original text
* Indexes text and HTML, document classes available for XML, PDF and RTF
* Search supports phrase and Boolean queries, plus, minus and quote marks, and parentheses
* Allows single and multiple character wildcards anywhere in the search words, fuzzy search, proximity
* Will search for punctuation such as + or ?
* Field searches for title, author, etc., and date-range searching
* Supports most European languages
* Option to store and display full text of indexed documents
* Search results in relevance order
* APIs for file format conversion, languages and user interfaces
Java 的程序基本編寫(xiě)完成,實(shí)現了對中文的支持。下一步是將其放到 WEB 上運行,首先想到的是使用 JSP,安裝了Apache Tomcat/4.1.24,默認的發(fā)布端口是 8080?,F在面臨的一個(gè)問(wèn)題是:Apache httpd 的端口是 80,并且我的機器對外只能通過(guò) 80 端口進(jìn)行訪(fǎng)問(wèn),如果將 Tomcat 的發(fā)布端口改成 80 的話(huà),httpd 就沒(méi)法對外了,而其上的 PHP 程序也將無(wú)法在 80 端口運行。 cd php-4.3.6 ./configure --with-java=/usr/local/jdk make make install PHP Fatal error: Unable to load Java Library /usr/local/jdk/jre/lib/i386/libjava.so, error: libjvm.so: cannot open shared object file: No such file or directory in /home/nio/public_html/java.php on line 2 ln -s java.so libphp_java.so export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/jdk/jre/lib/i386/server:/usr/local/jdk/jre/lib/i386 [Java] java.class.path = /usr/lib/php/php_java.jar java.home = /usr/local/jdk ;java.library = ;java.library.path = extension_dir=/usr/lib/php/20020429/ extension=java.so service httpd restart getProperty(‘java.version‘).‘<br />‘; print ‘Java vendor=‘ . $system->getProperty(‘java.vendor‘).‘<br />‘; print ‘OS=‘ . $system->getProperty(‘os.name‘) . ‘ ‘ . $system->getProperty(‘os.version‘) . ‘ on ‘ . $system->getProperty(‘os.arch‘) . ‘<br />‘; ?> 今天總算有些空閑時(shí)間,正好說(shuō)說(shuō)第二種方案:使用 mod_jk 做橋接的方式,將 servlet 引擎結合到 httpd 中。 tar xzf jakarta-tomcat-connectors-jk-1.2.5-src.tar.gz cd jakarta-tomcat-connectors-jk-1.2.5-src/jk/native ./configure --with-apxs=/usr/local/apache/bin/apxs make cp apache-1.3/mod_jk.so /usr/local/apache/libexec LoadModule jk_module libexec/mod_jk.so AddModule mod_jk.c # workers.properties 文件所在路徑,后邊將對此文件進(jìn)行講解 JkWorkersFile /usr/local/apache/conf/workers.properties # jk 的日志文件存放路徑 JkLogFile /usr/local/apache/log/mod_jk.log # 設置 jk 的日志級別 [debug/error/info] JkLogLevel info # 選擇日志時(shí)間格式 JkLogStampFormat "[%a %b %d %H:%M:%S %Y] " # JkOptions 選項設置 JkOptions +ForwardKeySize +ForwardURICompat -ForwardDirectories # JkRequestLogFormat 設置日志的請求格式 JkRequestLogFormat "%w %V %T" # 映射 /examples/* 到 worker1,worker1 在 workers.properties 文件中定義 JkMount /examples/* worker1 # 定義使用 ajp13 的 worker1 worker.list=worker1 # 設置 worker1 的屬性(ajp13) worker.worker1.type=ajp13 worker.worker1.host=localhost worker.worker1.port=8009 worker.worker1.lbfactor=50 worker.worker1.cachesize=10 worker.worker1.cache_timeout=600 worker.worker1.socket_keepalive=1 worker.worker1.socket_timeout=300 <!-- <Connector className="org.apache.coyote.tomcat4.CoyoteConnector" port="8080" minProcessors="5" maxProcessors="75" enableLookups="false" redirectPort="8443" acceptCount="100" debug="0" connectionTimeout="20000" useURIValidationHack="false" disableUploadTimeout="true" /> --> 對于 Lucene 的初步研究已經(jīng)過(guò)去一段時(shí)間,自己感覺(jué)還不是很深入,但由于時(shí)間的關(guān)系,一直也沒(méi)再拿起。應網(wǎng)友的要求,將自己實(shí)踐中寫(xiě)的一些代碼貼出來(lái),希望能對大家有用。程序沒(méi)有做進(jìn)一步的優(yōu)化,只是很簡(jiǎn)單的實(shí)現功能而已,僅供參考。 /** * PHPDocIndexer.java * 用于對 PHPDoc 的 HTML 頁(yè)面生成索引文件。 */ import java.io.File; import java.io.FileReader; import java.io.BufferedReader; import java.io.IOException; import java.util.Date; import java.text.DateFormat; import java.lang.*; import org.apache.lucene.analysis.cjk.CJKAnalyzer; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.DateField; class PHPDocIndexer { public static void main(String[] args) throws ClassNotFoundException, IOException { try { Date start = new Date(); IndexWriter writer = new IndexWriter("/home/nio/indexes-phpdoc", new CJKAnalyzer(), true); //索引保存目錄,必須存在 indexDocs(writer, new File("/home/nio/phpdoc-zh")); //HTML 文件保存目錄 System.out.println("Optimizing ...."); writer.optimize(); writer.close(); Date end = new Date(); System.out.print("Total time: "); System.out.println(end.getTime() - start.getTime()); } catch (Exception e) { System.out.println("Class " + e.getClass() + " throws error!\n errmsg: " + e.getMessage()); } //end try } //end main public static void indexDocs(IndexWriter writer, File file) throws Exception { if (file.isDirectory()) { String[] files = file.list(); for (int i = 0; i < files.length; i++) { indexDocs(writer, new File(file, files[i])); } //end for } else if (file.getPath().endsWith(".html")) { //只對 HTML 文件做索引 System.out.print("Add file:" + file + " ...."); // Add html file .... Document doc = new Document(); doc.add(Field.UnIndexed("file", file.getName())); //索引文件名 doc.add(Field.UnIndexed("modified", DateFormat.getDateTimeInstance().format(new Date(file.lastModified())))); //索引最后修改時(shí)間 String title = ""; String content = ""; String status = "start"; FileReader fReader = new FileReader(file); BufferedReader bReader = new BufferedReader(fReader); String line = bReader.readLine(); while (line != null) { content += line; //截取 HTML 標題 <title> if ("start" == status && line.equalsIgnoreCase("><TITLE")) { status = "match"; } else if ("match" == status) { title = line.substring(1, line.length() - 7); doc.add(Field.Text("title", title)); //索引標題 status = "end"; } //end if line = bReader.readLine(); } //end while bReader.close(); fReader.close(); doc.add(Field.Text("content", content.replaceAll("<[^<>]+>", ""))); //索引內容 writer.addDocument(doc); System.out.println(" [OK]"); } //end if } } //end class <%@ page language="java" import="javax.servlet.*, javax.servlet.http.*, java.io.*, java.util.Date, java.util.ArrayList, java.util.regex.*, org.apache.lucene.analysis.*, org.apache.lucene.document.*, org.apache.lucene.index.*, org.apache.lucene.search.*, org.apache.lucene.queryParser.*, org.apache.lucene.analysis.Token, org.apache.lucene.analysis.TokenStream, org.apache.lucene.analysis.cjk.CJKAnalyzer, org.apache.lucene.analysis.cjk.CJKTokenizer, com.chedong.weblucene.search.WebLuceneHighlighter" %> <%@ page contentType="text/html;charset=GB2312" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=gb2312" /> <title>PHPDoc - PHP 簡(jiǎn)體中文手冊全文檢索</title> <base target="main"><!-- 由于使用了 Frame,所以指定 target 到 main 窗口顯示 --> <style> body {background-color: white; margin: 4px} body, input, div {font-family: Tahoma; font-size: 9pt} body, div {line-height: 18px} u {color: red} b {color: navy} form {padding: 0px; margin: 0px} .txt {border: 1px solid black} .f {padding: 4px; margin-bottom: 16px; background-color: #E5ECF9; border-top: 1px solid #3366CC; border-bottom: 1px solid #3366CC; text-align: center;} .d, .o {padding-left: 16px} .d {color: gray} .o {color: green} .o a {color: #7777CC} </style> <script language="JavaScript"> function gotoPage(i) { document.frm.page.value = i; document.frm.submit(); } //end function </script> </head> <body> <% String keyVal = null; String pageVal = null; int offset = 0; int curPage = 0; int pages; final int ROWS = 50; //獲取 GET 參數 try { byte[] keyValByte = request.getParameter("key").getBytes("ISO8859_1"); //查找關(guān)鍵字 keyVal = new String(keyValByte); pageVal = request.getParameter("page"); //頁(yè)碼 } catch (Exception e) { //do nothing; } if (keyVal == null) keyVal = new String(""); %> <div class="f"> <form name="frm" action="./index.jsp" method="GET" onsubmit="this.page.value=‘0‘;return true;" target="_self"> <input type="text" name="key" class="txt" size="40" value="<%=keyVal%>" /> <input type="hidden" name="page" value="<%=pageVal%>" /> <input type="submit" value="搜 索" /><br /> <font color="green">提示:可使用多個(gè)關(guān)鍵字(使用空格隔開(kāi))提高搜索的準確率。</font> </form> <script language="JavaScript"> document.frm.key.focus(); </script> </div> <% if (keyVal != null && keyVal.length() > 0) { try { curPage = Integer.parseInt(pageVal); //將當前頁(yè)轉換成整數 } catch (Exception e) { //do nothing; } //end try try { Date startTime = new Date(); keyVal = keyVal.toLowerCase().replaceAll("(or|and)", "").trim().replaceAll("\\s+", " AND "); Searcher searcher = new IndexSearcher("/home/nio/indexes-phpdoc"); //索引目錄 Analyzer analyzer = new CJKAnalyzer(); String[] fields = {"title", "content"}; Query query = MultiFieldQueryParser.parse(keyVal, fields, analyzer); Hits hits = searcher.search(query); StringReader in = new StringReader(keyVal); TokenStream tokenStream = analyzer.tokenStream("", in); ArrayList al = new ArrayList(); for (Token token = tokenStream.next(); token != null; token = tokenStream.next()) { al.add(token.termText()); } //end for //總頁(yè)數 pages = (new Integer(hits.length()).doubleValue() % ROWS != 0) ? (hits.length() / ROWS) + 1 : (hits.length() / ROWS); //當前頁(yè)碼 if (curPage < 1) curPage = 1; else if (curPage > pages) curPage = pages; //起始、終止下標 offset = (curPage - 1) * ROWS; int end = Math.min(hits.length(), offset + ROWS); //循環(huán)輸出查詢(xún)結果 WebLuceneHighlighter hl = new WebLuceneHighlighter(al); for (int i = offset; i < end; i++) { Document doc = hits.doc(i); %> <div class="t"><a href="/~nio/phpdoc-zh/<%=doc.get("file")%>"><%=hl.highLight(doc.get("title"))%></a></div> <div class="d"><%=hl.highLight(doc.get("content").replaceAll("\n", " "), 100)%> ……</div> <div class="o"> /~nio/phpdoc-zh/<%=doc.get("file")%> - <%=doc.get("modified")%> </div> <br /> <% } //end for searcher.close(); Date endTime = new Date(); %> <div class="f"> 檢索總共耗時(shí) <b><%=((endTime.getTime() - startTime.getTime()) / 1000.0)%></b> 秒,約有 <b><%=hits.length()%></b> 項符合條件的記錄,共 <b><%=pages%></b> 頁(yè) <% if (curPage > 1 && pages > 1) { %> | <a href="javascript:gotoPage(<%=(curPage-1)%>);" target="_self">上一頁(yè)</a> <% } //end if if (curPage < pages && pages > 1) { %> | <a href="javascript:gotoPage(<%=(curPage+1)%>)" target="_self">下一頁(yè)</a> <% } //end if } catch (Exception e) { %> <!-- <%=e.getClass()%> 導致錯誤:<%=e.getMessage()%> --> <% } //end if } //end if %> </body> </html>
對于這個(gè)問(wèn)題,我想到兩種方案:
1、使用 PHP 直接調用 Java。需要做的工作是使用 --with-java 重新編譯 PHP;
2、使用 mod_jk 做橋接的方式,將 servlet 引擎結合到 httpd 中。需要做的工作是編譯 jakarta-tomcat-connectors-jk-1.2.5-src,生成 mod_jk.so 給 httpd 使用,然后按照 Howto 文檔 進(jìn)行 Tomcat、httpd 的配置。
對于第一個(gè)方案的嘗試:使用 PHP 直接調用 Java
環(huán)境
* PHP 4.3.6 prefix=/usr
* Apache 1.3.27 prefix=/usr/local/apache
* j2sdk1.4.1_01 prefix=/usr/local/jdk
配置步驟
1) 安裝 JDK,這個(gè)就不多說(shuō)了,到 GOOGLE 可以搜索出這方面的大量文章。
2) 重新編譯 PHP,我的 PHP 版本是 4.3.6:
完成之后,會(huì )在 PHP 的 lib 下(我的是在 /usr/lib/php)有個(gè) php_java.jar,同時(shí)在擴展動(dòng)態(tài)庫存放的目錄下(我的是在 /usr/lib/php/20020429)有個(gè) java.so 文件。到這一步需要注意一個(gè)問(wèn)題,有些 PHP 版本生成的是 libphp_java.so 文件,extension 的加載只認 libphp_java.so,直接加載 java.so 可能會(huì )出現如下錯誤:
所以如果生成的是 java.so,需要創(chuàng )建一個(gè)符號連接:
3) 修改 Apache Service 啟動(dòng)文件(我的這個(gè)文件為 /etc/init.d/httpd),在這個(gè)文件中加入:
正如你所看到的,我的 JDK 裝在 /usr/local/jdk 目錄下,如果你的不是在此目錄,請做相應改動(dòng)(下同)。
4) 修改 PHP 配置文件 php.ini,找到 [Java] 部分進(jìn)行修改:
我將 java.library 及 java.library.path 都注釋掉了,PHP 會(huì )自動(dòng)認為 java.library=/usr/local/jdk/jre/lib/i386/libjava.so。
5) 重新啟動(dòng) Apache httpd 服務(wù):
測試
測試腳本 java.php 源代碼:
安裝配置還算簡(jiǎn)單,但是在 PHP 運行 Java 的速度感覺(jué)較慢,所以下定決心開(kāi)始實(shí)踐第二個(gè)方案。
環(huán)境
* PHP 4.3.6 prefix=/usr
* Apache 1.3.27 prefix=/usr/local/apache
* j2sdk1.4.1_01 prefix=/usr/local/jdk
* jakarta-tomcat-4.1.24 prefix=/usr/local/tomcat
* 另外需要下載 jakarta-tomcat-connectors-jk-1.2.5-src.tar.gz
配置步驟
1) 安裝 JDK 與 Tomcat,這些安裝步驟就不多說(shuō)了。
2) 編譯 jakarta-tomcat-connectors-jk-1.2.5-src,生成 mod_jk.so,并將其復制到 apache 的 modules 存放目錄:
3) 編輯 Apache 配置文件 /usr/local/apache/conf/httpd.conf,加入:
這個(gè) LoadModule 語(yǔ)句最好放在其他 LoadModule 語(yǔ)句后邊。
同時(shí)在配置文件后邊加入:
4) 在 /usr/local/apache/conf/ 目錄下創(chuàng )建 workers.properties 文件,其內容如下:
5) 好了,啟動(dòng) Tomcat,重啟一下 Apache HTTPD Server,訪(fǎng)問(wèn):http://localhost/examples/index.jsp,看看結果如何,和 http://localhost:8080/examples/index.jsp 是一樣的。
提示:如果不想讓別人通過(guò) 8080 端口訪(fǎng)問(wèn)到你的 Tomcat,可以將 /usr/lcoal/tomcat/conf/server.xml 配置文件中的如下代碼加上注釋?zhuān)?/span>
總結
此方案安裝配置稍微復雜些,但執行效率要比第一種方案要好很多。所以決定使用這種方案來(lái)完成我的 Lucene 全文檢索實(shí)踐任務(wù)。
在實(shí)踐中,我以將 PHP 中文手冊中的 HTML 文件生成索引,然后通過(guò)一個(gè) JSP 對其進(jìn)行全文檢索。
生成索引的 Java 代碼:
索引生成完之后,就需要一個(gè)檢索頁(yè)面,下邊是搜索頁(yè)面(search.jsp)的代碼:
聯(lián)系客服