Jive
是一个比较丰富的知识宝藏,从中可以学习到很多新的实战技巧和具体功能实现方式。前面基本介绍了
Jive
中的一些主要架构技术,通过这些技术可以基本上掌握
Jive
论坛系统。
Jive
中还有很多非常实用的组件技术和工具库,分析学习可重用技术,可以在自己具体的项目重复使用,大大提高了新系统的开发速度和效率。
Jive
的管理功能中提供了将
Jive
数据库数据导出到
XML
文件的管理工具,在这个工具功能实现中,使用了树形结构的遍历技术。
Jive
将
ForumThread
中的第一个
ForumMessage
作为
root ForumMessage
,以这个
ForumMessage
为根节点,每个
ForumThread
中包含了一套树形结构。
TreeWalker
是树形结构的一个抽象接口,代码如下:
public interface TreeWalker {
//
根节点
public ForumMessage getRoot();
//
获得父节点
public ForumMessage getParent(ForumMessage child)
throws ForumMessageNotFoundException;
//
获得子节点
public ForumMessage getChild(ForumMessage parent, int index)
throws ForumMessageNotFoundException;
//
获得所有子节点
public Iterator children(ForumMessage parent);
//
获得所有的子节点,包括子节点的子节点
…
public Iterator recursiveChildren(ForumMessage parent);
//
获得一个节点的深度,相对根节点而言
public int getMessageDepth(ForumMessage message);
public int getChildCount(ForumMessage parent);
public int getRecursiveChildCount(ForumMessage parent);
/**
*
返回相对父节点的子节点索引。例如
* <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);
//
一个节点是否是叶,叶相对枝来说,叶没有子节点了
public boolean isLeaf(ForumMessage node);
}
DbTreeWalker
是
TreeWalker
的一个实现,它是将一个
ForumThread
下所有帖子的
ID
从数据库中装入
LongTree
中。一句
LongTree
的树形结构遍历核心技术实现
ForumThread
中的帖子结构的遍历。
LongTree
类似之前的
Cache
类,封装了树形结构遍历的核心算法,在
LongTree
中建立了
3
个数组
long [] keys
、
char [] leftChildren
和
char [] rightSiblings
。
一个节点有两个特性:它有子节点;它有兄弟节点。
leftChildren
保存的是这个节点的子节点的索引;而
rightSiblings
保存的是这个节点兄弟节点的索引。例如:
1000
|-- 3000
|-- |--4000
|-- |--6000
|-- |--7000
|-- 5000
1000
是个根节点,
1000
下有两个子节点
3000
和
5000
,而
3000
则有
3
个子节点
4000
、
6000
和
7000
,
3000
还有一个兄弟节点
5000
,使用上述
3
个数组是这样保持信息的:
keys[0] = 1000
keys[1] = 3000
keys[2] = 4000
keys[3] = 5000
keys[4] = 6000
keys[5] = 7000
keys
数组中保存的是各个节点的数值,而
leftChildren
和
rightSiblings
数组保存的是
keys
数组的
index
,即
0
、
1
、
2
、
3
、
4
等数字。
1000
节点有两个子节点,那么其对应的
leftChildren
和
rightSiblings
分别是:
leftChildren[0] = 1
leftChildren[0]
中的索引
0
表示当前索引,
keus[0]
是
1000
,说明现在节点是
1000
;
1
也表示
keys
数组的索引,
keys[1]
的值是
3000
,所以上一句表示
1000
的子节点是
3000
。
1000
节点没有兄弟节点:
rightSiblings[0] =
-
1
再看看
3000
节点,其
keys
的索引
Index
是
1
,其子节点是
4000
、
6000
和
7000
,取最近一个
4000
的索引
index
放入数组:
leftChildren[1] = 2
这表示
1000
节点的子节点是
4000
,那么另外一个
6000
节点如何表示?这是以
4000
节点的兄弟节点表现出来的。
4000
节点的
keys
的索引
index
是
2
,通过下列表示:
rightSiblings[2] = 4
其中,
4
表示
6000
在
keys
中的索引
Index
。同样,第
3
个子节点
7000
表示如下:
rightSiblings[4] = 5
这样,
3000
节点有
3
个子节点
4000
、
6000
和
7000
(
4000
、
6000
和
7000
是兄弟节点)通过上述简单两句就表现出来了。
总结一个父子关系树的表示方法:在父节点中,使用
leftChildren
保存最靠近父节点的一个子节点(父节点的第一个儿子,叫长子)的索引,其他子节点则是通过
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; //
无子节点
rightSiblings[1] = 0; //
无兄弟姐妹
}
当加入一个节点时,其方法如下:
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
创建节点
keys[nextIndex] = newKey;
leftChildren[nextIndex] = 0;
rightSiblings[nextIndex] = 0;
//
将新建节点标志为父节点的子节点
if (leftChildren[parentIndex] == 0) {
//
如果父节点原来没有子节点,那就将新建节点作为其子节点
leftChildren[parentIndex] = nextIndex;
}else {
//
如果父节点有子节点,寻找最后一个子节点
long siblingIndex = leftChildren[parentIndex];
//
在
siblingIndex
中循环查找,直至值为
0
while (rightSiblings[new Long(siblingIndex).intValue()] != 0) {
siblingIndex = rightSiblings[new Long(siblingIndex).intValue()];
}
//
将新建节点作为最后一个字节点加入
rightSiblings[new Long(siblingIndex).intValue()] = nextIndex;
}
//
最后,自动增加
nextIndex
以便下一个等待插入
nextIndex++;
}
Jive
将数据导出到
XML
文件时,就是根据某个
ForumMessage
的
ID
,通过
TreeWalker
找出它的所有子节点
ForumMessage
的
ID
,然后将其内容导出。
XML
称为可扩充标记语言,是类似
HTML
定义文档标记语言的一个框架。
XML
以结构严谨著称,因此用来保存数据是非常适合的,这样在数据库之外,又多了一个持久化保存数据的方式。
在
Java
中,
XML
更多时是作为配置文件数据存储体形式出现,在之前一般是使用
properties
来保存系统的配置文件,如下:
cache.maxsize=1024
cache.minsize=2
这两句分别设置
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 ");
这样就可以获得配置文件中的两个值。
这种配置文件使用方法简单直接,但是只适合配置文件不很复杂的情况。在复杂的配置情况下,
properties
就不是很合适,例如设置系统的可选属性,一个系统安装在不同应用场合,客户的要求总有些不一样,有些功能是可选的,那么需要在配置文件中配置一些可选的功能,以
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
系统的配置文件。这个配置文件是在
Jive
系统安装时,按照用户的选择动态生成的,其中包含数据库连接参数、界面显示颜色、电子邮件配置以及缓冲配置、搜索配置和文件或图片上传配置。
分析读取
XML
数据有很多工具,如
DOM
(
http://www.worg/DOM/
)和
SAX
(
http://www.saxproject.org/
)。这两种是标准的
XML
分析器,可以使用任何语言来实现,
DOM
分析
XML
数据时,是将整个文档一下子读入内存,如果文档很大,性能就发生影响,而
SAX
则是动态地对每一行分析,无需全部读入,因此在分析大文档时速度比较快。
但是这两种分析方法都是围绕
XML
树形结构展开的,在编制这两种分析器时,会涉及到大量
XML
概念的
API
,需要一定的
XML
基础和知识,使用起来有一定难度。
JDOM
(
http://www.jdom.org
)封装了
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
文件保存细节中,由
XMLProperties
将这种属性名称写法具体转换成
XML
文档操作。具体内部代码如下:
public String getProperty(String name) {
if (propertyCache.containsKey(name)) { //
从缓存中获取
return (String)propertyCache.get(name);
}
//
将
email.fromName
转变为
String
数组
//
例如
propName[0] = jive; propName[1] = email …
String[] propName = parsePropertyName(name);
//
通过
propName
数组循环,遍历
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
内部做了很多基础支持性的细节工作,才使得使用
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
。
注意,如果要在系统中支持上述功能,必须下载
JDOM
包,还要有
DataFormatFilter. java
、
DataUnformatFilter.java
、
XMLFilterBase.java
和
XMLProperties.java
支持。这几个类不包含在
JDOM
标准包中,作为一个应用包含在其
Sample
中。当然也可以直接从
Jive
中复制出来使用。
5.3
全文检索和
Lucene
Jive
中支持全文检索,这个功能主要核心依赖另外一个开放源代码项目
Lucene
(
http://jakarta.apache.org/lucene/docs/index.html
)。
Jakarta Lucene
是一个高性能全文搜索引擎,可以跨平台应用于任何搜索应用。
使用
Lucene
作为搜索引擎,应用系统需要做两件事情:
(
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();
//
优化
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://sourceforge.net/projects/weblucene/
;
zhoujun
的
http://www. jdon.com/jive/thread.jsp? forum=61&thread=8400
等,该问题将在后面章节继续讨论。
一个索引是由一系列
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); //
创建一个搜索器
//
使用和索引同样的语言分析器
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();
//
输出结果
}
追查代码会发现,上面
forumFactory.createQuery(forums)
方法实际内容是
new DbQuery(forums, this)
。
DbQuery
作为
Query
的一个子类,它的搜索语句通过
executeQuery()
方法中下列语句实现:
private void executeQuery() {
try {
Searcher searcher = getSearcher(); //
创建一个搜索器
…
//
使用分析器获得
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
的搜索使用了过滤器,以便过滤掉不想出现的结果,然后还对搜索结果进行了限制转换,这些在实际使用中都是必需的。
Jive
默认的字符集编码方式是
ISO8859_1
,即
Latin-1
字符集,这是国际标准化组织用来表示
Latin
等西方语言使用的字符集。
ISO8859_1
字符集非常类似常见的
ASCII
字符集。由于
ISO8859_1
是使用单字节来表示,而汉字是采取双字节来表示一个汉字,我国制定了一套专门用来表示汉字
GB2312
和
GBK
编码字符集。
在
Java
内部运算中,涉及到的所有字符串都会被转化为
UTF-8
编码来进行运算。那么,在被
Java
转化之前,字符串是什么样的字符集?
Java
总是根据操作系统的默认编码字符集来决定字符串的初始编码,而且
Java
系统的输入和输出的都是采取操作系统的默认编码。
因此,如果能统一
Java
系统的输入、输出和操作系统
3
者的编码字符集合,将能够使
Java
系统正确处理和显示汉字。这是处理
Java
系统汉字的一个原则,但是在实际项目中,能够正确抓住和控制住
Java
系统的输入和输出部分是比较难的。
Jive
是运行在
Web
容器中的一个
Servlet/JSP
系统。在这个系统中,输入途径有很多种:一种是通过页面表单打包成请求(
request
)发往服务器的;第二种是通过数据库读入;还有第
3
种输入比较复杂,
JSP
在第一次运行时总是被编译成
Servlet
,
JSP
中常常包含中文字符,那么编译使用
javac
时,
Java
将根据默认的操作系统编码作为初始编码。除非特别指定,如在
Jbuilder
中可以指定默认的字符集。
输出途径也有几种:第一种是
JSP
页面的输出。由于
JSP
页面已经被编译成
Servlet
,那么在输出时,也将根据操作系统的默认编码来选择输出编码,除非指定输出编码方式;还有输出途径是数据库,将字符串输出到数据库。
由此看来,一个
J2EE
系统的输入输出是非常复杂,而且是动态变化的,而
Java
是跨平台运行的,在实际编译和运行中,都可能涉及到不同的操作系统,如果任由
Java
自由根据操作系统来决定输入输出的编码字符集,这将不可控制地出现乱码。
正是由于
Java
的跨平台特性,使得字符集问题必须由具体系统来统一解决,所以在一个
Java
应用系统中,解决中文乱码的根本办法是明确指定整个应用系统统一字符集。
在
Jive
中如果指定默认字符集为某个字符集,那么就要在所有的输入输出环节都要标识为这个字符集。但是,前面已经提到,要完全在编码时做到还是有一定难度,必须对
Web
程序有相当地掌握和理解,而且步骤较繁琐。
有一种相对省事的做法,例如统一指定为
ISO8859_1
,因为目前大多数软件都是西方人编制的,他们默认的字符集就是
ISO8859_1
,包括操作系统
Linux
和数据库
MySQL
等。这样,如果指定
Jive
统一编码为
ISO8859_1
,那么就有下面
3
个环节必须把握:
·
开发和编译代码时指定字符集为
ISO8859_1
。
·
运行操作系统的默认编码必须是
ISO8859_1
,如
Linux
。
·
在
JSP
头部声明:
<%@ page contentType="text/html;charset=ISO8859_1" %>
。
如果统一指定为
GBK
中文字符集,上述
3
个环节同样需要做到,不同的是只能运行在默认编码为
GBK
的操作系统,如中文
Windows
。
所以统一编码为
ISO8859_1
和
GBK
虽然带来编制代码的方便,但是也破坏了
Java
跨平台运行的优越性,只在一定范围内行得通。
很多情况下,程序员大都是在中文
Windows
下开发调试
Java
系统,然后直接部署到
Linux
等系统上真正运行。而且其中可能涉及到
XML
文件读写。
XML
是对编码方式要求严格的数据存储体,
XML
又可以随着代码移动。因此,在进行真正大规模
Java
系统开发运行时,上述临时简单的变通方式就没有效果了。
要从根本上解决
Java
的中文问题,只要将
Java
系统的统一编码定义为
UTF-8
。
UTF-8
编码是一种兼容所有语言的编码方式,惟一比较麻烦的就是要找到应用系统的所有出入口,然后使用
UTF-8
去“结扎”它。
Jive
默认的字符集编码方式是
ISO8859_1
,如果都统一为
UTF-8
,那么也需要做下列几步工作:
·
开发和编译代码时指定字符集为
UTF-8
。
·
使用过滤器,将所有请求(
request
)转换为
UTF-8
;针对不同应用过滤器有两种。
·
如果所有请求都经过一个
Servlet
控制分配器,那么使用
Servlet
的
filter
执行语句。
·
request.setCharacterEncoding("UTF-8")
。
·
如果不经过
Servlet
,而直接是
JSP
,那么每个
JSP
头部设置上述语句。
·
在
JSP
头部声明:
<%@ page contentType="text/html;charset= UTF-8" %>
。
·
设定数据库连接方式是
UTF-8
。
以上讨论了
Jive
以及通用
Java
的中文问题。如果整个应用系统是从开始进行开发,那么统一指定编码为
UTF-8
就非常容易做到。如果是在英文源代码基础上二次开发,那么首先要将原来的源代码转换为统一编码
UTF-8
,那么这种转换工作会带来一定的麻烦。