每日一得

不求多得,只求一得 about java,hibernate,spring,design,database,Ror,ruby,快速开发
最近关心的内容:SSH,seam,flex,敏捷,TDD
本站的官方站点是:颠覆软件

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  220 随笔 :: 9 文章 :: 421 评论 :: 0 Trackbacks

5  Jive 的其他组件技术

Jive 是一个比较丰富的知识宝藏,从中可以学习到很多新的实战技巧和具体功能实现方式。前面基本介绍了 Jive 中的一些主要架构技术,通过这些技术可以基本上掌握 Jive 论坛系统。

Jive 中还有很多非常实用的组件技术和工具库,分析学习可重用技术,可以在自己具体的项目重复使用,大大提高了新系统的开发速度和效率。

5.1  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 ,然后将其内容导出。

5.2  XML JDOM

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 的搜索使用了过滤器,以便过滤掉不想出现的结果,然后还对搜索结果进行了限制转换,这些在实际使用中都是必需的。

5.4  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 ,那么这种转换工作会带来一定的麻烦。

posted on 2006-08-31 12:27 Alex 阅读(648) 评论(0)  编辑  收藏 所属分类: java

只有注册用户登录后才能发表评论。


网站导航: