5 Jive的其他組件技術Jive是一個比較豐富的知識寶藏,從中可以學習到很多新的實戰(zhàn)技巧和具體功能實現方式。前面基本介紹了Jive中的一些主要架構技術,通過這些技術可以基本上掌握Jive論壇系統(tǒng)。 Jive中還有很多非常實用的組件技術和工具庫,分析學習可重用技術,可以在自己具體的項目重復使用,大大提高了新系統(tǒng)的開發(fā)速度和效率。 Jive的管理功能中提供了將Jive數據庫數據導出到XML文件的管理工具,在這個工具功能實現中,使用了樹形結構的遍歷技術。 Jive將ForumThread中的第一個ForumMessage作為root ForumMessage,以這個ForumMessage為根節(jié)點,每個ForumThread中包含了一套樹形結構。 TreeWalker是樹形結構的一個抽象接口,代碼如下: 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的一個實現,它是將一個ForumThread下所有帖子的ID從數據庫中裝入LongTree中。一句LongTree的樹形結構遍歷核心技術實現ForumThread中的帖子結構的遍歷。 LongTree類似之前的Cache類,封裝了樹形結構遍歷的核心算法,在LongTree中建立了3個數組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個數組是這樣保持信息的: keys[0] = 1000 keys[1] = 3000 keys[2] = 4000 keys[3] = 5000 keys[4] = 6000 keys[5] = 7000 keys數組中保存的是各個節(jié)點的數值,而leftChildren和rightSiblings數組保存的是keys數組的index,即0、1、2、3、4等數字。 1000節(jié)點有兩個子節(jié)點,那么其對應的leftChildren和rightSiblings分別是: leftChildren[0] = 1 leftChildren[0]中的索引0表示當前索引,keus[0]是1000,說明現在節(jié)點是1000;1也表示keys數組的索引,keys[1]的值是3000,所以上一句表示1000的子節(jié)點是3000。 1000節(jié)點沒有兄弟節(jié)點: rightSiblings[0] = -1 再看看3000節(jié)點,其keys的索引Index是1,其子節(jié)點是4000、6000和7000,取最近一個4000的索引index放入數組: leftChildren[1] = 2 這表示1000節(jié)點的子節(jié)點是4000,那么另外一個6000節(jié)點如何表示?這是以4000節(jié)點的兄弟節(jié)點表現出來的。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é)點)通過上述簡單兩句就表現出來了。 總結一個父子關系樹的表示方法:在父節(jié)點中,使用leftChildren保存最靠近父節(jié)點的一個子節(jié)點(父節(jié)點的第一個兒子,叫長子)的索引,其他子節(jié)點則是通過rightSiblings表明與長子是兄弟關系。 看看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; //無子節(jié)點 rightSiblings[1] = 0; //無兄弟姐妹 } 當加入一個節(jié)點時,其方法如下: public void addChild(long parentKey, long newKey) { // 根據parentKey找出其對應的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將數據導出到XML文件時,就是根據某個ForumMessage的ID,通過TreeWalker找出它的所有子節(jié)點ForumMessage的ID,然后將其內容導出。 5.2 XML和JDOMXML 稱為可擴充標記語言,是類似HTML定義文檔標記語言的一個框架。XML以結構嚴謹著稱,因此用來保存數據是非常適合的,這樣在數據庫之外,又多了一個持久化保存數據的方式。 在Java中,XML更多時是作為配置文件數據存儲體形式出現,在之前一般是使用properties來保存系統(tǒng)的配置文件,如下: cache.maxsize=1024 cache.minsize=2 這兩句分別設置cache的最大值和最小值,那么在Java中通過下列語句讀?。?/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 "); 這樣就可以獲得配置文件中的兩個值。 這種配置文件使用方法簡單直接,但是只適合配置文件不很復雜的情況。在復雜的配置情況下,properties就不是很合適,例如設置系統(tǒng)的可選屬性,一個系統(tǒ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格式本身是一種樹形結構的數據格式。在實際應用中,很多復雜的表示都可以使用樹形結構來分解代表。因此,使用XML來表示這種樹形結構的數據無疑是非常合適的。 在Jive中,jive_config.xml是Jive系統(tǒng)的配置文件。這個配置文件是在Jive系統(tǒng)安裝時,按照用戶的選擇動態(tài)生成的,其中包含數據庫連接參數、界面顯示顏色、電子郵件配置以及緩沖配置、搜索配置和文件或圖片上傳配置。 分析讀取XML數據有很多工具,如DOM(http://www.worg/DOM/)和SAX(http://www./)。這兩種是標準的XML分析器,可以使用任何語言來實現,DOM分析XML數據時,是將整個文檔一下子讀入內存,如果文檔很大,性能就發(fā)生影響,而SAX則是動態(tài)地對每一行分析,無需全部讀入,因此在分析大文檔時速度比較快。 但是這兩種分析方法都是圍繞XML樹形結構展開的,在編制這兩種分析器時,會涉及到大量XML概念的API,需要一定的XML基礎和知識,使用起來有一定難度。 JDOM(http://www.)封裝了DOM/SAX的具體使用技術,以非常符合Java編程方式的形式來分析XML,因此使用起來非常方便。 在分析速度方面,JDOM比DOM要快,比SAX慢一點。但用在分析配置文件上,速度不是主要的,因為可以使用lazy initialization。這類似緩存機制,在第一次讀取后就保存在內存中,以后每次直接從內存中獲取。 在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將這種屬性名稱寫法具體轉換成XML文檔操作。具體內部代碼如下: public String getProperty(String name) { 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); // 通過propName數組循環(huán),遍歷XML的樹形結構層次,尋找出對應的屬性值 Element element = doc.getRootElement(); for (int i = 0; i < propName.length; i++) { element = element.getChild(propName[i]); if (element == null) { return null; } } // 尋找到element后,獲得其內容 String value = element.getText(); if ("".equals(value)) { return null; } else { // 保存到緩存中 value = value.trim(); propertyCache.put(name, value); return value; } } 以上只是分析了JDOM的XMLProperties包是如何做屬性配置提取的,正是因為JDOM內部做了很多基礎支持性的細節(jié)工作,才使得使用JDOM變得非常方便。 總結使用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標準包中,作為一個應用包含在其Sample中。當然也可以直接從Jive中復制出來使用。 5.3 全文檢索和LuceneJive中支持全文檢索,這個功能主要核心依賴另外一個開放源代碼項目Lucene(http://jakarta./lucene/docs/index.html)。Jakarta Lucene是一個高性能全文搜索引擎,可以跨平臺應用于任何搜索應用。 使用Lucene作為搜索引擎,應用系統(tǒng)需要做兩件事情: (1)建立索引文件。將Jive數據庫中的數據內容建立索引文件,這是通過SearchManager來完成。SearchManager代碼如下: public interface SearchManager { public boolean isSearchEnabled(); public void setSearchEnabled(boolean searchEnabled); /** //如果SearchManage正在工作,返回真 public boolean isBusy(); //返回索引完成率 public int getPercentComplete(); //是否自動建立索引 //通過TaskEngine.scheduleTask方法實現定期自動索引 public boolean isAutoIndexEnabled(); public void setAutoIndexEnabled(boolean value); //自動索引間隔的分鐘數 public int getAutoIndexInterval(); public void setAutoIndexInterval(int minutes); //獲得上次建立索引的時間 public Date getLastIndexedDate(); //在實時建立索引時,將當前帖子加入索引 public void addToIndex(ForumMessage message); public void removeFromIndex(ForumMessage message); //手動更新自上次建立索引后的新內容 public void updateIndex(); //手動重新建立全部的索引 public void rebuildIndex(); //優(yōu)化 public void optimize(); } · SearchManager定義了建立索引的一些屬性,建立索引有兩種方式:當有新帖子加入時,通過調用indexMessage()方法實時索引;或者通過TaskEngine.scheduleTask方法每隔一定時間建立索引。 · DbSearchManager作為SearchManager的子類實現,又是一個線程類,它是建立索引的主要功能類。在DbSearchManager中主要使用了Lucene的IndexWriter、 Analyzer、 Document和 Field等功能類來建立索引。 · IndexWriter用戶建立新的索引,當然也可以將文檔加入已經存在的索引。 在文本被索引之前,它必須通過一個分析器Analyzer。分析器Analyzer 負責從文本中分離出索引關鍵字。Lucene有幾種不同類型的分析器: · SimpleAnalyzer是將英文轉換為小寫字母,按空格和標點符號切分出英文單詞, 如I am Java這一句,使用SimpleAnalyzer切詞就會切分出下列詞語: token1=I token2=am token3=Java · StandardAnalyzer是對英文進行了較為復雜的處理。除了按詞語建立索引關鍵字(token)外,還能夠為特殊名稱、郵件地址、縮寫格式等建立索引單元,而且對“and”、“ the”等詞語做了過濾。 · ChineseAnalyzer是專門用來分析中文的索引的。關于中文分析器,有很多嘗試,如車東的http:///projects/weblucene/;zhoujun的http://www. jdon.com/jive/thread.jsp? forum=61&thread=8400等,該問題將在后面章節(jié)繼續(xù)討論。 一個索引是由一系列Document組成,每個Document是由一個或多個Field組成,每個Field都有一個名字和值,可以把Document作為關系數據庫中一條記錄,而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根據具體要求有不同用法,Lucene提供4種類型的Field: Keyword、 UnIndexed、 UnStored和 Text。 · Keyword 不實現切詞,逐字地保存在索引中,這種類型適合一些如URL、日期、個人姓名、社會安全號碼、電話號碼等需要原封不動保留的詞語。 · UnIndexed既不實現切詞也不索引,但是其值是一個詞一個詞地保存在索引中,這不適合很大很長的詞語,適合于顯示一些不經過直接搜索的結果值。 · UnStored與UnIndexed正好相反,將被切詞和索引,但是不保存在索引中,這適合巨大文本,如帖子內容、頁面內容等。 · Text是實現切詞、索引,并且保存在索引中。 在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中同時也實現了自動建立索引的過程,通過在構造方法中生成TimeTask實例: timerTask = TaskEngine.scheduleTask( this,autoIndexInterval*JiveGlobals.MINUTE, autoIndexInterval*JiveGlobals.MINUTE); 因為DbSearchManager是線程類,它在run方法中實現索引任務自動運行: TaskEngine.addTask(new IndexTask(false)); (2)建立完成后,就可以直接搜索特定的詞語了。搜索語句一般代碼如下: Searcher searcher = new IndexSearcher((indexDir); //創(chuàng)建一個搜索器 //使用和索引同樣的語言分析器 Query query = QueryParser.parse(queryString, "body", new StandardAnalyzer()); //搜索結果使用Hits存儲 Hits hits = searcher.search(query); //通過hits得到相應字段的數據和查詢的匹配度 for (int i=0; i<hits.length(); i++) { System.out.println(hits.doc(i).get("fieldname ")); }; Jive實現搜索就復雜得多,它為搜索專門建立了一個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接口中主要定義了和搜索相關的一些參數,可以根據具體要求定制,直接使用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(); //輸出結果 } 追查代碼會發(fā)現,上面forumFactory.createQuery(forums)方法實際內容是new DbQuery(forums, this)。DbQuery作為Query的一個子類,它的搜索語句通過executeQuery()方法中下列語句實現: 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自己的搜索結果過濾器 MultiFilter multiFilter = new MultiFilter(3); int filterCount = 0;
if (factory.getForumCount() != forums.length) { //將其他論壇內容搜索結果過濾掉 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++; }
//日期過濾器 如只查詢某日期以后的內容 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) {//實現搜索 hits = searcher.search(comboQuery, multiFilter); } else { hits = searcher.search(comboQuery); } //搜索結果不要超過最大大小 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的搜索使用了過濾器,以便過濾掉不想出現的結果,然后還對搜索結果進行了限制轉換,這些在實際使用中都是必需的。 5.4 Jive的中文問題Jive默認的字符集編碼方式是ISO8859_1,即Latin-1字符集,這是國際標準化組織用來表示Latin等西方語言使用的字符集。 ISO8859_1字符集非常類似常見的ASCII字符集。由于ISO8859_1是使用單字節(jié)來表示,而漢字是采取雙字節(jié)來表示一個漢字,我國制定了一套專門用來表示漢字GB2312和GBK編碼字符集。 在Java內部運算中,涉及到的所有字符串都會被轉化為UTF-8編碼來進行運算。那么,在被Java轉化之前,字符串是什么樣的字符集? Java總是根據操作系統(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ā)往服務器的;第二種是通過數據庫讀入;還有第3種輸入比較復雜,JSP在第一次運行時總是被編譯成Servlet,JSP中常常包含中文字符,那么編譯使用javac時,Java將根據默認的操作系統(tǒng)編碼作為初始編碼。除非特別指定,如在Jbuilder中可以指定默認的字符集。 輸出途徑也有幾種:第一種是JSP頁面的輸出。由于JSP頁面已經被編譯成Servlet,那么在輸出時,也將根據操作系統(tǒng)的默認編碼來選擇輸出編碼,除非指定輸出編碼方式;還有輸出途徑是數據庫,將字符串輸出到數據庫。 由此看來,一個J2EE系統(tǒng)的輸入輸出是非常復雜,而且是動態(tài)變化的,而Java是跨平臺運行的,在實際編譯和運行中,都可能涉及到不同的操作系統(tǒng),如果任由Java自由根據操作系統(tǒng)來決定輸入輸出的編碼字符集,這將不可控制地出現亂碼。 正是由于Java的跨平臺特性,使得字符集問題必須由具體系統(tǒng)來統(tǒng)一解決,所以在一個Java應用系統(tǒng)中,解決中文亂碼的根本辦法是明確指定整個應用系統(tǒng)統(tǒng)一字符集。 在Jive中如果指定默認字符集為某個字符集,那么就要在所有的輸入輸出環(huán)節(jié)都要標識為這個字符集。但是,前面已經提到,要完全在編碼時做到還是有一定難度,必須對Web程序有相當地掌握和理解,而且步驟較繁瑣。 有一種相對省事的做法,例如統(tǒng)一指定為ISO8859_1,因為目前大多數軟件都是西方人編制的,他們默認的字符集就是ISO8859_1,包括操作系統(tǒng)Linux和數據庫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)越性,只在一定范圍內行得通。 很多情況下,程序員大都是在中文Windows下開發(fā)調試Java系統(tǒng),然后直接部署到Linux等系統(tǒng)上真正運行。而且其中可能涉及到XML文件讀寫。XML是對編碼方式要求嚴格的數據存儲體,XML又可以隨著代碼移動。因此,在進行真正大規(guī)模Java系統(tǒng)開發(fā)運行時,上述臨時簡單的變通方式就沒有效果了。 要從根本上解決Java的中文問題,只要將Java系統(tǒng)的統(tǒng)一編碼定義為UTF-8。UTF-8編碼是一種兼容所有語言的編碼方式,惟一比較麻煩的就是要找到應用系統(tǒng)的所有出入口,然后使用UTF-8去“結扎”它。 Jive默認的字符集編碼方式是ISO8859_1,如果都統(tǒng)一為UTF-8,那么也需要做下列幾步工作: · 開發(fā)和編譯代碼時指定字符集為UTF-8。 · 使用過濾器,將所有請求(request)轉換為UTF-8;針對不同應用過濾器有兩種。 · 如果所有請求都經過一個Servlet控制分配器,那么使用Servlet的filter執(zhí)行語句。 · request.setCharacterEncoding("UTF-8")。 · 如果不經過Servlet,而直接是JSP,那么每個JSP頭部設置上述語句。 · 在JSP頭部聲明:<%@ page contentType="text/html;charset= UTF-8" %>。 · 設定數據庫連接方式是UTF-8。 以上討論了Jive以及通用Java的中文問題。如果整個應用系統(tǒng)是從開始進行開發(fā),那么統(tǒng)一指定編碼為UTF-8就非常容易做到。如果是在英文源代碼基礎上二次開發(fā),那么首先要將原來的源代碼轉換為統(tǒng)一編碼UTF-8,那么這種轉換工作會帶來一定的麻煩。 |
|