Jive是一個(gè)比較豐富的知識寶藏,從中可以學(xué)習到很多新的實(shí)戰技巧和具體功能實(shí)現方式。前面基本介紹了Jive中的一些主要架構技術(shù),通過(guò)這些技術(shù)可以基本上掌握Jive論壇系統。
Jive中還有很多非常實(shí)用的組件技術(shù)和工具庫,分析學(xué)習可重用技術(shù),可以在自己具體的項目重復使用,大大提高了新系統的開(kāi)發(fā)速度和效率。
Jive的管理功能中提供了將Jive數據庫數據導出到XML文件的管理工具,在這個(gè)工具功能實(shí)現中,使用了樹(shù)形結構的遍歷技術(shù)。
Jive將ForumThread中的第一個(gè)ForumMessage作為root ForumMessage,以這個(gè)ForumMessage為根節點(diǎn),每個(gè)ForumThread中包含了一套樹(shù)形結構。
TreeWalker是樹(shù)形結構的一個(gè)抽象接口,代碼如下:
public interface TreeWalker {
//根節點(diǎn)
public ForumMessagegetRoot();
//獲得父節點(diǎn)
public ForumMessagegetParent(ForumMessage child)
throwsForumMessageNotFoundException;
//獲得子節點(diǎn)
public ForumMessagegetChild(ForumMessage parent, int index)
throwsForumMessageNotFoundException;
//獲得所有子節點(diǎn)
public Iteratorchildren(ForumMessage parent);
//獲得所有的子節點(diǎn),包括子節點(diǎn)的子節點(diǎn)…
public IteratorrecursiveChildren(ForumMessage parent);
//獲得一個(gè)節點(diǎn)的深度,相對根節點(diǎn)而言
public int getMessageDepth(ForumMessagemessage);
public int getChildCount(ForumMessageparent);
public int getRecursiveChildCount(ForumMessageparent);
/**
* 返回相對父節點(diǎn)的子節點(diǎn)索引。例如
* <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(ForumMessageparent, ForumMessage child);
//一個(gè)節點(diǎn)是否是葉,葉相對枝來(lái)說(shuō),葉沒(méi)有子節點(diǎn)了
public booleanisLeaf(ForumMessage node);
}
DbTreeWalker 是TreeWalker的一個(gè)實(shí)現,它是將一個(gè)ForumThread下所有帖子的ID從數據庫中裝入LongTree中。一句LongTree的樹(shù)形結構遍歷核心技術(shù)實(shí)現ForumThread中的帖子結構的遍歷。
LongTree類(lèi)似之前的Cache類(lèi),封裝了樹(shù)形結構遍歷的核心算法,在LongTree中建立了3個(gè)數組long[] keys、char[] leftChildren和char[] rightSiblings。
一個(gè)節點(diǎn)有兩個(gè)特性:它有子節點(diǎn);它有兄弟節點(diǎn)。leftChildren保存的是這個(gè)節點(diǎn)的子節點(diǎn)的索引;而rightSiblings保存的是這個(gè)節點(diǎn)兄弟節點(diǎn)的索引。例如:
1000
|-- 3000
|-- |--4000
|-- |--6000
|-- |--7000
|-- 5000
1000是個(gè)根節點(diǎn),1000下有兩個(gè)子節點(diǎn)3000和5000,而3000則有3個(gè)子節點(diǎn)4000、6000和7000,3000還有一個(gè)兄弟節點(diǎn)5000,使用上述3個(gè)數組是這樣保持信息的:
keys[0] = 1000
keys[1] = 3000
keys[2] = 4000
keys[3] = 5000
keys[4] = 6000
keys[5] = 7000
keys數組中保存的是各個(gè)節點(diǎn)的數值,而leftChildren和rightSiblings數組保存的是keys數組的index,即0、1、2、3、4等數字。
1000節點(diǎn)有兩個(gè)子節點(diǎn),那么其對應的leftChildren和rightSiblings分別是:
leftChildren[0] = 1
leftChildren[0]中的索引0表示當前索引,keus[0]是1000,說(shuō)明現在節點(diǎn)是1000;1也表示keys數組的索引,keys[1]的值是3000,所以上一句表示1000的子節點(diǎn)是3000。
1000節點(diǎn)沒(méi)有兄弟節點(diǎn):
rightSiblings[0] = -1
再看看3000節點(diǎn),其keys的索引Index是1,其子節點(diǎn)是4000、6000和7000,取最近一個(gè)4000的索引index放入數組:
leftChildren[1] = 2
這表示1000節點(diǎn)的子節點(diǎn)是4000,那么另外一個(gè)6000節點(diǎn)如何表示?這是以4000節點(diǎn)的兄弟節點(diǎn)表現出來(lái)的。4000節點(diǎn)的keys的索引index是2,通過(guò)下列表示:
rightSiblings[2] = 4
其中,4表示6000在keys中的索引Index。同樣,第3個(gè)子節點(diǎn)7000表示如下:
rightSiblings[4] = 5
這樣,3000節點(diǎn)有3個(gè)子節點(diǎn)4000、6000和7000(4000、6000和7000是兄弟節點(diǎn))通過(guò)上述簡(jiǎn)單兩句就表現出來(lái)了。
總結一個(gè)父子關(guān)系樹(shù)的表示方法:在父節點(diǎn)中,使用leftChildren保存最靠近父節點(diǎn)的一個(gè)子節點(diǎn)(父節點(diǎn)的第一個(gè)兒子,叫長(cháng)子)的索引,其他子節點(diǎn)則是通過(guò)rightSiblings表明與長(cháng)子是兄弟關(guān)系。
看看LongTress的初始化構造方法,keys數組的值保存的是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; //無(wú)子節點(diǎn)
rightSiblings[1]= 0; //無(wú)兄弟姐妹
}
當加入一個(gè)節點(diǎn)時(shí),其方法如下:
public void addChild(long parentKey,long newKey) {
// 根據parentKey找出其對應的keys索引index
char parentIndex= findKey(parentKey, (char)1);
if (parentIndex== 0) {
thrownew IllegalArgumentException("Parent key " +parentKey +
" notfound when adding child " + newKey + ".");
}
// 為newKey創(chuàng )建節點(diǎn)
keys[nextIndex]= newKey;
leftChildren[nextIndex]= 0;
rightSiblings[nextIndex]= 0;
//將新建節點(diǎn)標志為父節點(diǎn)的子節點(diǎn)
if (leftChildren[parentIndex]== 0) {
// 如果父節點(diǎn)原來(lái)沒(méi)有子節點(diǎn),那就將新建節點(diǎn)作為其子節點(diǎn)
leftChildren[parentIndex]= nextIndex;
}else {
// 如果父節點(diǎn)有子節點(diǎn),尋找最后一個(gè)子節點(diǎn)
longsiblingIndex = leftChildren[parentIndex];
//在siblingIndex中循環(huán)查找,直至值為0
while(rightSiblings[new Long(siblingIndex).intValue()] != 0){
siblingIndex= rightSiblings[new Long(siblingIndex).intValue()];
}
// 將新建節點(diǎn)作為最后一個(gè)字節點(diǎn)加入
rightSiblings[newLong(siblingIndex).intValue()] = nextIndex;
}
// 最后,自動(dòng)增加nextIndex 以便下一個(gè)等待插入
nextIndex++;
}
Jive將數據導出到XML文件時(shí),就是根據某個(gè)ForumMessage的ID,通過(guò)TreeWalker找出它的所有子節點(diǎn)ForumMessage的ID,然后將其內容導出。
XML 稱(chēng)為可擴充標記語(yǔ)言,是類(lèi)似HTML定義文檔標記語(yǔ)言的一個(gè)框架。XML以結構嚴謹著(zhù)稱(chēng),因此用來(lái)保存數據是非常適合的,這樣在數據庫之外,又多了一個(gè)持久化保存數據的方式。
在Java中,XML更多時(shí)是作為配置文件數據存儲體形式出現,在之前一般是使用properties來(lái)保存系統的配置文件,如下:
cache.maxsize=1024
cache.minsize=2
這兩句分別設置cache的最大值和最小值,那么在Java中通過(guò)下列語(yǔ)句讀?。?/span>
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 ");
這樣就可以獲得配置文件中的兩個(gè)值。
這種配置文件使用方法簡(jiǎn)單直接,但是只適合配置文件不很復雜的情況。在復雜的配置情況下,properties就不是很合適,例如設置系統的可選屬性,一個(gè)系統安裝在不同應用場(chǎng)合,客戶(hù)的要求總有些不一樣,有些功能是可選的,那么需要在配置文件中配置一些可選的功能,以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">
<LoggerclassName="org.apache.catalina.logger.FileLogger"
prefix="localhost_examples_log." suffix=".txt"
timestamp="true"/>
…
</Context>
在一個(gè)配置中有很多Context,每個(gè)Contexr都包含Logger等具體配置,XML格式本身是一種樹(shù)形結構的數據格式。在實(shí)際應用中,很多復雜的表示都可以使用樹(shù)形結構來(lái)分解代表。因此,使用XML來(lái)表示這種樹(shù)形結構的數據無(wú)疑是非常合適的。
在Jive中,jive_config.xml是Jive系統的配置文件。這個(gè)配置文件是在Jive系統安裝時(shí),按照用戶(hù)的選擇動(dòng)態(tài)生成的,其中包含數據庫連接參數、界面顯示顏色、電子郵件配置以及緩沖配置、搜索配置和文件或圖片上傳配置。
分析讀取XML數據有很多工具,如DOM(http://www.worg/DOM/)和SAX(http://www.saxproject.org/)。這兩種是標準的XML分析器,可以使用任何語(yǔ)言來(lái)實(shí)現,DOM分析XML數據時(shí),是將整個(gè)文檔一下子讀入內存,如果文檔很大,性能就發(fā)生影響,而SAX則是動(dòng)態(tài)地對每一行分析,無(wú)需全部讀入,因此在分析大文檔時(shí)速度比較快。
但是這兩種分析方法都是圍繞XML樹(shù)形結構展開(kāi)的,在編制這兩種分析器時(shí),會(huì )涉及到大量XML概念的API,需要一定的XML基礎和知識,使用起來(lái)有一定難度。
JDOM(http://www.jdom.org)封裝了DOM/SAX的具體使用技術(shù),以非常符合Java編程方式的形式來(lái)分析XML,因此使用起來(lái)非常方便。
在分析速度方面,JDOM比DOM要快,比SAX慢一點(diǎn)。但用在分析配置文件上,速度不是主要的,因為可以使用lazyinitialization。這類(lèi)似緩存機制,在第一次讀取后就保存在內存中,以后每次直接從內存中獲取。
在Jive中,JDOM操作基本是由JiveGlobals完成的。
public class JiveGlobals {
privatestatic final String JIVE_CONFIG_FILENAME = "jive_config.xml";
privatestatic XMLProperties properties = null;
...
//從配置文件獲取配置
publicstatic String getJiveProperty(String name) {
loadProperties();
returnproperties.getProperty(name);
}
//用JDOM載入配置文件
privatesynchronized static void loadProperties() {
if(properties == null) {
properties= new XMLProperties(jiveHome + File.separator +
JIVE_CONFIG_FILENAME);
}
}
//將配置保存到配置文件中
public static void setJiveProperty(Stringname, String value) {
loadProperties();
properties.setProperty(name,value);
}
}
從上面代碼看出,對XML文件讀寫(xiě)非常方便,使用properties.getProperty(name)就可以獲得name的配置值,而properties.setProperty(name, value)一句就可以將name和其值value保存到XML文件中,非常類(lèi)似Hashtable的讀取和存入。
XMLProperties是JDOM的一個(gè)屬性文件輔助包,它主要是對屬性名進(jìn)行分解和合成,例如XML如下:
<jive>
<email>
<fromName>Jive_Administrator</fromName>
<fromEmail>webmaster@example.com</fromEmail>
<subject>Yourthread 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,這種特定的寫(xiě)法就是XMLProperties可以支持的,在對XML文件保存細節中,由XMLProperties將這種屬性名稱(chēng)寫(xiě)法具體轉換成XML文檔操作。具體內部代碼如下:
public String getProperty(Stringname) {
if(propertyCache.containsKey(name)) { //從緩存中獲取
return(String)propertyCache.get(name);
}
//將email.fromName轉變?yōu)?/span>String數組
//例如propName[0] = jive; propName[1]= email …
String[]propName = parsePropertyName(name);
// 通過(guò)propName數組循環(huán),遍歷XML的樹(shù)形結構層次,尋找出對應的屬性值
Elementelement = doc.getRootElement();
for(int i = 0; i < propName.length; i++) {
element= element.getChild(propName[i]);
if(element == null) {
returnnull;
}
}
// 尋找到element后,獲得其內容
Stringvalue = element.getText();
if("".equals(value)) { returnnull; }
else{
// 保存到緩存中
value= value.trim();
propertyCache.put(name,value);
returnvalue;
}
}
以上只是分析了JDOM的XMLProperties包是如何做屬性配置提取的,正是因為JDOM內部做了很多基礎支持性的細節工作,才使得使用JDOM變得非常方便。
總結使用JDOM對配置文件讀寫(xiě)操作語(yǔ)法如下:
· 獲得配置(查詢(xún)):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。
注意,如果要在系統中支持上述功能,必須下載JDOM包,還要有DataFormatFilter. java、DataUnformatFilter.java、XMLFilterBase.java和XMLProperties.java支持。這幾個(gè)類(lèi)不包含在JDOM標準包中,作為一個(gè)應用包含在其Sample中。當然也可以直接從Jive中復制出來(lái)使用。
Jive中支持全文檢索,這個(gè)功能主要核心依賴(lài)另外一個(gè)開(kāi)放源代碼項目Lucene(http://jakarta.apache.org/lucene/docs/index.html)。Jakarta Lucene是一個(gè)高性能全文搜索引擎,可以跨平臺應用于任何搜索應用。
使用Lucene作為搜索引擎,應用系統需要做兩件事情:
(1)建立索引文件。將Jive數據庫中的數據內容建立索引文件,這是通過(guò)SearchManager來(lái)完成。SearchManager代碼如下:
public interface SearchManager{
public booleanisSearchEnabled();
public voidsetSearchEnabled(boolean searchEnabled);
/**
//如果SearchManage正在工作,返回真
public booleanisBusy();
//返回索引完成率
public int getPercentComplete();
//是否自動(dòng)建立索引
//通過(guò)TaskEngine.scheduleTask方法實(shí)現定期自動(dòng)索引
public booleanisAutoIndexEnabled();
public voidsetAutoIndexEnabled(boolean value);
//自動(dòng)索引間隔的分鐘數
public int getAutoIndexInterval();
public voidsetAutoIndexInterval(int minutes);
//獲得上次建立索引的時(shí)間
public DategetLastIndexedDate();
//在實(shí)時(shí)建立索引時(shí),將當前帖子加入索引
public voidaddToIndex(ForumMessage message);
public voidremoveFromIndex(ForumMessage message);
//手動(dòng)更新自上次建立索引后的新內容
public voidupdateIndex();
//手動(dòng)重新建立全部的索引
public voidrebuildIndex();
//優(yōu)化
public voidoptimize();
}
· SearchManager定義了建立索引的一些屬性,建立索引有兩種方式:當有新帖子加入時(shí),通過(guò)調用indexMessage()方法實(shí)時(shí)索引;或者通過(guò)TaskEngine.scheduleTask方法每隔一定時(shí)間建立索引。
· DbSearchManager作為SearchManager的子類(lèi)實(shí)現,又是一個(gè)線(xiàn)程類(lèi),它是建立索引的主要功能類(lèi)。在DbSearchManager中主要使用了Lucene的IndexWriter、 Analyzer、 Document和 Field等功能類(lèi)來(lái)建立索引。
· IndexWriter用戶(hù)建立新的索引,當然也可以將文檔加入已經(jīng)存在的索引。
在文本被索引之前,它必須通過(guò)一個(gè)分析器Analyzer。分析器Analyzer 負責從文本中分離出索引關(guān)鍵字。Lucene有幾種不同類(lèi)型的分析器:
· SimpleAnalyzer是將英文轉換為小寫(xiě)字母,按空格和標點(diǎn)符號切分出英文單詞,
如Iam Java這一句,使用SimpleAnalyzer切詞就會(huì )切分出下列詞語(yǔ):
token1=I
token2=am
token3=Java
· StandardAnalyzer是對英文進(jìn)行了較為復雜的處理。除了按詞語(yǔ)建立索引關(guān)鍵字(token)外,還能夠為特殊名稱(chēng)、郵件地址、縮寫(xiě)格式等建立索引單元,而且對“and”、“ the”等詞語(yǔ)做了過(guò)濾。
· ChineseAnalyzer是專(zhuān)門(mén)用來(lái)分析中文的索引的。關(guān)于中文分析器,有很多嘗試,如車(chē)東的http://sourceforge.net/projects/weblucene/;zhoujun的http://www.jdon.com/jive/thread.jsp? forum=61&thread=8400等,該問(wèn)題將在后面章節繼續討論。
一個(gè)索引是由一系列Document組成,每個(gè)Document是由一個(gè)或多個(gè)Field組成,每個(gè)Field都有一個(gè)名字和值,可以把Document作為關(guān)系數據庫中一條記錄,而Field則是記錄中某列字段。一般建立索引如下:
//指定將在哪個(gè)目錄建立索引
String indexDir = "/home/jdon/jive/WEB-INF/jiveHome";
//指定將要建立索引的文本
String text = "welcom here,I am Java,";
Analyzer analyzer = new StandardAnalyzer(); //使用StandardAnalyzer
//建立一個(gè)IndexWriter
IndexWriter writer = new IndexWriter(indexDir,analyzer, true);
//建立Document
Document document = new Document();
//進(jìn)行切詞、索引
document.add(Field.Text("fieldname",text));
//加入索引中
writer.addDocument(document);
writer.close();
其中,Field根據具體要求有不同用法,Lucene提供4種類(lèi)型的Field: Keyword、 UnIndexed、 UnStored和 Text。
· Keyword 不實(shí)現切詞,逐字地保存在索引中,這種類(lèi)型適合一些如URL、日期、個(gè)人姓名、社會(huì )安全號碼、電話(huà)號碼等需要原封不動(dòng)保留的詞語(yǔ)。
· UnIndexed既不實(shí)現切詞也不索引,但是其值是一個(gè)詞一個(gè)詞地保存在索引中,這不適合很大很長(cháng)的詞語(yǔ),適合于顯示一些不經(jīng)過(guò)直接搜索的結果值。
· UnStored與UnIndexed正好相反,將被切詞和索引,但是不保存在索引中,這適合巨大文本,如帖子內容、頁(yè)面內容等。
· Text是實(shí)現切詞、索引,并且保存在索引中。
在Jive中,索引的建立以DbSearchManager中加入帖子索引方法為例:
protected final void addMessageToIndex(longmessageID, long userID,
longthreadID, long forumID, String subject, String body,
java.util.DatecreationDate, IndexWriter writer) throws IOException
{
//建立一個(gè) Document
Document doc= new Document();
doc.add(Field.Keyword("messageID",Long.toString(messageID)));
doc.add(newField("userID", Long.toString(userID), false,true, false));
doc.add(new Field("threadID",Long.toString(threadID), false, true, false));
doc.add(newField("forumID", Long.toString(forumID), false,true, false));
doc.add(Field.UnStored("subject",subject));
doc.add(Field.UnStored("body",body));
doc.add(newField("creationDate", DateField.dateToString(creationDate),
false,true, false));
//將該Document加入當前索引中
writer.addDocument(doc);
}
在DbSearchManager中同時(shí)也實(shí)現了自動(dòng)建立索引的過(guò)程,通過(guò)在構造方法中生成TimeTask實(shí)例:
timerTask = TaskEngine.scheduleTask(
this,autoIndexInterval*JiveGlobals.MINUTE,
autoIndexInterval*JiveGlobals.MINUTE);
因為DbSearchManager是線(xiàn)程類(lèi),它在run方法中實(shí)現索引任務(wù)自動(dòng)運行:
TaskEngine.addTask(newIndexTask(false));
(2)建立完成后,就可以直接搜索特定的詞語(yǔ)了。搜索語(yǔ)句一般代碼如下:
Searcher searcher = new IndexSearcher((indexDir); //創(chuàng )建一個(gè)搜索器
//使用和索引同樣的語(yǔ)言分析器
Query query = QueryParser.parse(queryString, "body",new StandardAnalyzer());
//搜索結果使用Hits存儲
Hits hits = searcher.search(query);
//通過(guò)hits得到相應字段的數據和查詢(xún)的匹配度
for (int i=0; i<hits.length();i++) {
System.out.println(hits.doc(i).get("fieldname "));
};
Jive實(shí)現搜索就復雜得多,它為搜索專(zhuān)門(mén)建立了一個(gè)Query接口:
public interface Query {
//需要搜索的字符串
public StringgetQueryString();
public voidsetQueryString(String queryString);
public DategetBeforeDate();
public voidsetBeforeDate(Date beforeDate);
public DategetAfterDate();
public voidsetAfterDate(Date afterDate);
public UsergetFilteredUser();
public voidfilterOnUser(User user);
public ForumThreadgetFilteredThread();
public voidfilterOnThread(ForumThread thread);
public int resultCount();
public Iteratorresults();
public Iteratorresults(int startIndex, int numResults);
}
Query接口中主要定義了和搜索相關(guān)的一些參數,可以根據具體要求定制,直接使用Query就可以達到搜索的目的,如需要搜索Java is cool,那么使用下列代碼:
ForumFactory forumFactory = ForumFactory.getInstance();
Query query = forumFactory.createQuery(forums);
query.setQueryString("Jiveis cool");
Iterator iter = query.results();
while (iter.hasNext()) {
ForumMessagemessage = (ForumMessage)iter.nextElement();
//輸出結果
}
追查代碼會(huì )發(fā)現,上面forumFactory.createQuery(forums)方法實(shí)際內容是new DbQuery(forums, this)。DbQuery作為Query的一個(gè)子類(lèi),它的搜索語(yǔ)句通過(guò)executeQuery()方法中下列語(yǔ)句實(shí)現:
private void executeQuery() {
try {
Searchersearcher = getSearcher(); //創(chuàng )建一個(gè)搜索器
…
//使用分析器獲得Query對象
org.apache.lucene.search.QuerybodyQuery =
QueryParser.parse(queryString, "body",DbSearchManager.analyzer);
org.apache.lucene.search.QuerysubjectQuery =
QueryParser.parse(queryString, "subject",DbSearchManager.analyzer);
//將兩個(gè)Query對象加入BooleanQuery
BooleanQuerycomboQuery = new BooleanQuery();
comboQuery.add(subjectQuery,false,false);
comboQuery.add(bodyQuery,false,false);
//Jive自己的搜索結果過(guò)濾器
MultiFiltermultiFilter = new MultiFilter(3);
intfilterCount = 0;
if(factory.getForumCount() != forums.length) {
//將其他論壇內容搜索結果過(guò)濾掉
String[]forumIDs = new String[forums.length];
for(int i=0; i<forumIDs.length; i++) {
forumIDs[i]= Long.toString(forums[i].getID());
}
multiFilter.add(newFieldFilter("forumID", forumIDs));
filterCount++;
}
//日期過(guò)濾器 如只查詢(xún)某日期以后的內容
if(beforeDate != null || afterDate != null) {
if(beforeDate != null && afterDate != null) {
multiFilter.add(newDateFilter("creationDate", beforeDate, afterDate));
filterCount++;
}elseif (beforeDate == null) {
multiFilter.add(DateFilter.After("creationDate",afterDate));
filterCount++;
}else{
multiFilter.add(DateFilter.Before("creationDate",beforeDate));
filterCount++;
}
}
// 過(guò)濾用戶(hù)
if(user != null) {
StringuserID = Long.toString(user.getID());
multiFilter.add(newFieldFilter("userID", userID));
filterCount++;
}
// 主題過(guò)濾
if(thread != null) {
StringthreadID = Long.toString(thread.getID());
multiFilter.add(newFieldFilter("threadID", threadID));
filterCount++;
}
if(filterCount > 0) {//實(shí)現搜索
hits= searcher.search(comboQuery, multiFilter);
}else {
hits= searcher.search(comboQuery);
}
//搜索結果不要超過(guò)最大大小
intnumResults = 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的搜索使用了過(guò)濾器,以便過(guò)濾掉不想出現的結果,然后還對搜索結果進(jìn)行了限制轉換,這些在實(shí)際使用中都是必需的。
Jive默認的字符集編碼方式是ISO8859_1,即Latin-1字符集,這是國際標準化組織用來(lái)表示Latin等西方語(yǔ)言使用的字符集。
ISO8859_1字符集非常類(lèi)似常見(jiàn)的ASCII字符集。由于ISO8859_1是使用單字節來(lái)表示,而漢字是采取雙字節來(lái)表示一個(gè)漢字,我國制定了一套專(zhuān)門(mén)用來(lái)表示漢字GB2312和GBK編碼字符集。
在Java內部運算中,涉及到的所有字符串都會(huì )被轉化為UTF-8編碼來(lái)進(jìn)行運算。那么,在被Java轉化之前,字符串是什么樣的字符集? Java總是根據操作系統的默認編碼字符集來(lái)決定字符串的初始編碼,而且Java系統的輸入和輸出的都是采取操作系統的默認編碼。
因此,如果能統一Java系統的輸入、輸出和操作系統3者的編碼字符集合,將能夠使Java系統正確處理和顯示漢字。這是處理Java系統漢字的一個(gè)原則,但是在實(shí)際項目中,能夠正確抓住和控制住Java系統的輸入和輸出部分是比較難的。
Jive是運行在Web容器中的一個(gè)Servlet/JSP系統。在這個(gè)系統中,輸入途徑有很多種:一種是通過(guò)頁(yè)面表單打包成請求(request)發(fā)往服務(wù)器的;第二種是通過(guò)數據庫讀入;還有第3種輸入比較復雜,JSP在第一次運行時(shí)總是被編譯成Servlet,JSP中常常包含中文字符,那么編譯使用javac時(shí),Java將根據默認的操作系統編碼作為初始編碼。除非特別指定,如在Jbuilder中可以指定默認的字符集。
輸出途徑也有幾種:第一種是JSP頁(yè)面的輸出。由于JSP頁(yè)面已經(jīng)被編譯成Servlet,那么在輸出時(shí),也將根據操作系統的默認編碼來(lái)選擇輸出編碼,除非指定輸出編碼方式;還有輸出途徑是數據庫,將字符串輸出到數據庫。
由此看來(lái),一個(gè)J2EE系統的輸入輸出是非常復雜,而且是動(dòng)態(tài)變化的,而Java是跨平臺運行的,在實(shí)際編譯和運行中,都可能涉及到不同的操作系統,如果任由Java自由根據操作系統來(lái)決定輸入輸出的編碼字符集,這將不可控制地出現亂碼。
正是由于Java的跨平臺特性,使得字符集問(wèn)題必須由具體系統來(lái)統一解決,所以在一個(gè)Java應用系統中,解決中文亂碼的根本辦法是明確指定整個(gè)應用系統統一字符集。
在Jive中如果指定默認字符集為某個(gè)字符集,那么就要在所有的輸入輸出環(huán)節都要標識為這個(gè)字符集。但是,前面已經(jīng)提到,要完全在編碼時(shí)做到還是有一定難度,必須對Web程序有相當地掌握和理解,而且步驟較繁瑣。
有一種相對省事的做法,例如統一指定為ISO8859_1,因為目前大多數軟件都是西方人編制的,他們默認的字符集就是ISO8859_1,包括操作系統Linux和數據庫MySQL等。這樣,如果指定Jive統一編碼為ISO8859_1,那么就有下面3個(gè)環(huán)節必須把握:
· 開(kāi)發(fā)和編譯代碼時(shí)指定字符集為ISO8859_1。
· 運行操作系統的默認編碼必須是ISO8859_1,如Linux。
· 在JSP頭部聲明:<%@ page contentType="text/html;charset=ISO8859_1" %>。
如果統一指定為GBK中文字符集,上述3個(gè)環(huán)節同樣需要做到,不同的是只能運行在默認編碼為GBK的操作系統,如中文Windows。
所以統一編碼為ISO8859_1和GBK雖然帶來(lái)編制代碼的方便,但是也破壞了Java跨平臺運行的優(yōu)越性,只在一定范圍內行得通。
很多情況下,程序員大都是在中文Windows下開(kāi)發(fā)調試Java系統,然后直接部署到Linux等系統上真正運行。而且其中可能涉及到XML文件讀寫(xiě)。XML是對編碼方式要求嚴格的數據存儲體,XML又可以隨著(zhù)代碼移動(dòng)。因此,在進(jìn)行真正大規模Java系統開(kāi)發(fā)運行時(shí),上述臨時(shí)簡(jiǎn)單的變通方式就沒(méi)有效果了。
要從根本上解決Java的中文問(wèn)題,只要將Java系統的統一編碼定義為UTF-8。UTF-8編碼是一種兼容所有語(yǔ)言的編碼方式,惟一比較麻煩的就是要找到應用系統的所有出入口,然后使用UTF-8去“結扎”它。
Jive默認的字符集編碼方式是ISO8859_1,如果都統一為UTF-8,那么也需要做下列幾步工作:
· 開(kāi)發(fā)和編譯代碼時(shí)指定字符集為UTF-8。
· 使用過(guò)濾器,將所有請求(request)轉換為UTF-8;針對不同應用過(guò)濾器有兩種。
· 如果所有請求都經(jīng)過(guò)一個(gè)Servlet控制分配器,那么使用Servlet的filter執行語(yǔ)句。
· request.setCharacterEncoding("UTF-8")。
· 如果不經(jīng)過(guò)Servlet,而直接是JSP,那么每個(gè)JSP頭部設置上述語(yǔ)句。
· 在JSP頭部聲明:<%@ page contentType="text/html;charset=UTF-8" %>。
· 設定數據庫連接方式是UTF-8。
以上討論了Jive以及通用Java的中文問(wèn)題。如果整個(gè)應用系統是從開(kāi)始進(jìn)行開(kāi)發(fā),那么統一指定編碼為UTF-8就非常容易做到。如果是在英文源代碼基礎上二次開(kāi)發(fā),那么首先要將原來(lái)的源代碼轉換為統一編碼UTF-8,那么這種轉換工作會(huì )帶來(lái)一定的麻煩。
聯(lián)系客服