2006年04月29日

from:   http://确实忘记啦,今天整理电脑的时候发现了这个,贴出来.

几个月之前,在网上找到了一个中文词库素材(几百K),当时便想写一个分词程序了.我对汉语分词没有什么研究,也就凭自己臆想而写.若有相关方面专家,还请多给意见.

  

  一、词库

  

  词库大概有5万多词语(google能搜到,类似的词库都能用),我摘要如下:

  

  地区 82

  重要 81

  新华社 80

  技术 80

  会议 80

  自己 79

  干部 78

  职工 78

  群众 77

  没有 77

  今天 76

  同志 76

  部门 75

  加强 75

  组织 75

  第一列是词,第二列是权重.我写的这个分词算法目前并未利用权重.

  

  二、设计思路

  

  算法简要描述:

  

  对一个字符串S,从前到后扫描,对扫描的每个字,从词库中寻找最长匹配.比如假设S="我是中华人民共和国公民",词库中有"中华人民共和国","中华","公民","人民","共和国"……等词.当扫描到"",那么从中字开始,向后分别取1,2,3,……个字("","中华","中华人","中华人民","中华人民共","中华人民共和","中华人民共和国",,"中华人民共和国公"),词库中的最长匹配字符串是"中华人民共和国",那么就此切分开,扫描器推进到"".

  

  数据结构:

  

  选择什么样的数据结构对性能影响很大.我采用Hashtable _rootTable记录词库.键值对为(,插入次数).对每一个词语,如果该词语有N个字,则将该词语的1,1~2,1~3,……1~N个字作为键,插入_rootTable.而同一个键如果重复插入,则后面的值递增.

  

  三、程序

  

  具体程序如下(程序中包含权重,插入次数等要素,目前的算法并没有利用这些.可以借此写出更有效的分词算法):

  

  ChineseWordUnit.cs //struct–(词语,权重)

  

  

   1 public struct ChineseWordUnit

   2 {

   3 private string _word;

   4 private int _power;

   5

   6 /**//// <summary>

   7 /// 中文词语单元所对应的中文词。

   8 /// </summary>

   9 public string Word

  10 {

  11 get

  12 {

  13 return _word;

  14 }

  15 }

  16

  17 /**//// <summary>

  18 /// 该中文词语的权重。

  19 /// </summary>

  20 public int Power

  21 {

  22 get

  23 {

  24 return _power;

  25 }

  26 }

  27

  28 /**//// <summary>

  29 /// 结构初始化。

  30 /// </summary>

  31 /// <param name="word">中文词语</param>

  32 /// <param name="power">该词语的权重</param>

  33 public ChineseWordUnit(string word, int power)

  34 {

  35 this._word = word;

  36 this._power = power;

  37 }

  38 }

  

  

  ChineseWordsHashCountSet.cs //词库容器

  

  

  

   1 /**//// <summary>

   2 /// 记录字符串出现在中文字典所录中文词语的前端的次数的字典类。如字符串“中”出现在“中国”的前端,则在字典中记录一个次数。

   3 /// </summary>

   4 public class ChineseWordsHashCountSet

   5 {

   6 /**//// <summary>

   7 /// 记录字符串在中文词语中出现次数的Hashtable。键为特定的字符串,值为该字符串在中文词语中出现的次数。

   8 /// </summary>

   9 private Hashtable _rootTable;

  10

  11 /**//// <summary>

  12 /// 类型初始化。

  13 /// </summary>

  14 public ChineseWordsHashCountSet()

  15 {

  16 _rootTable = new Hashtable();

  17 }

  18

  19 /**//// <summary>

  20 /// 查询指定字符串出现在中文字典所录中文词语的前端的次数。

  21 /// </summary>

  22 /// <param name="s">指定字符串</param>

  23 /// <returns>字符串出现在中文字典所录中文词语的前端的次数。若为-1,表示不出现。</returns>

  24 public int GetCount(string s)

  25 {

  26 if (!this._rootTable.ContainsKey(s.Length))

  27 {

  28 return -1;

  29 }

  30 Hashtable _tempTable = (Hashtable)this._rootTable[s.Length];

  31 if (!_tempTable.ContainsKey(s))

  32 {

  33 return -1;

  34 }

  35 return (int)_tempTable[s];

  36 }

  37

  38 /**//// <summary>

  39 /// 向次数字典中插入一个词语。解析该词语,插入次数字典。

  40 /// </summary>

  41 /// <param name="s">所处理的字符串。</param>

  42 public void InsertWord(string s)

  43 {

  44 for(int i=0;i<s.Length;i++)

  45 {

  46 string _s = s.Substring(0,i+1);

  47 this.InsertSubString(_s);

  48 }

  49 }

  50

  51 /**//// <summary>

  52 /// 向次数字典中插入一个字符串的次数记录。

  53 /// </summary>

  54 /// <param name="s">所插入的字符串。</param>

  55 private void InsertSubString(string s)

  56 {

  57 if (!_rootTable.ContainsKey(s.Length)&&s.Length>0)

  58 {

  59 Hashtable _newHashtable = new Hashtable();

  60 _rootTable.Add(s.Length,_newHashtable);

  61 }

  62 Hashtable _tempTable = (Hashtable)_rootTable[s.Length];

  63 if (!_tempTable.ContainsKey(s))

  64 {

  65 _tempTable.Add(s,1);

  66 }

  67 else

  68 {

  69 _tempTable[s]=(int)_tempTable[s]+1;

  70 }

  71 }

  72 }

 

 

ChineseParse.cs //分词器

  

   1 /**//// <summary>

   2 /// 中文分词器。

   3 /// </summary>

   4 public class ChineseParse

   5 {

   6 private static ChineseWordsHashCountSet _countTable;

   7

   8 static ChineseParse()

   9 {

  10 _countTable = new ChineseWordsHashCountSet();

  11 InitFromFile("ChineseDictionary.txt");

  12 }

  13

  14 /**//// <summary>

  15 /// 从指定的文件中初始化中文词语字典和字符串次数字典。

  16 /// </summary>

  17 /// <param name="fileName">文件名</param>

  18 private static void InitFromFile(string fileName)

  19 {

  20 string path = Directory.GetCurrentDirectory() +@"\" + fileName;

  21 if (File.Exists(path))

  22 {

  23 using (StreamReader sr = File.OpenText(path))

  24 {

  25 string s = "";

  26 while ((s = sr.ReadLine()) != null)

  27 {

  28 ChineseWordUnit _tempUnit = InitUnit(s);

  29 _countTable.InsertWord(_tempUnit.Word);

  30 }

  31 }

  32 }

  33 }

  34

  35 /**//// <summary>

  36 /// 将一个字符串解析为ChineseWordUnit

  37 /// </summary>

  38 /// <param name="s">字符串</param>

  39 /// <returns>解析得到的ChineseWordUnit</returns>

  40 private static ChineseWordUnit InitUnit(string s)

  41 {

  42 Regex reg = new Regex(@"\s+");

  43 string[] temp = reg.Split(s);

  44 if (temp.Length!=2)

  45 {

  46 throw new Exception("字符串解析错误:"+s);

  47 }

  48 return new ChineseWordUnit(temp[0],Int32.Parse(temp[1]));

  49 }

  50

  51 /**//// <summary>

  52 /// 分析输入的字符串,将其切割成一个个的词语。

  53 /// </summary>

  54 /// <param name="s">待切割的字符串</param>

  55 /// <returns>所切割得到的中文词语数组</returns>

  56 public static string[] ParseChinese(string s)

  57 {

  58 int _length = s.Length;

  59 string _temp = String.Empty;

  60 ArrayList _words = new ArrayList();

  61

  62 for(int i=0;i<s.Length;)

  63 {

  64 _temp = s.Substring(i,1);

  65 if (_countTable.GetCount(_temp)>1)

  66 {

  67 int j=2;

  68

  69 for (;i+j<s.Length+1&&_countTable.GetCount(s.Substring(i,j))>0;j++)

  70 {

  71 }

  72 _temp = s.Substring(i,j-1);

  73 i = i + j – 2;

  74 }

  75 i++;

  76 _words.Add(_temp);

  77 }

  78

  79 string[] _tempStringArray = new string[_words.Count];

  80 _words.CopyTo(_tempStringArray);

  81 return _tempStringArray;

  82 }

  83 }

 

 

 

 

四、测试

  

  和海量分词演示程序对比测试:

  

  Case 1:  新浪体育讯 在被尤文淘汰之后,皇马主帅博斯克拒绝接受媒体对球队后防线的批评,同时还为自己排出的首发阵容进行了辩护。“失利是全队的责任,而不仅仅是后防线该受指责,”博斯克说,“我并不认为我们踢得一塌糊涂。”“我们进入了半决赛,而且在晋级的道路上一路奋战。即使是今天的比赛我们也有几个翻身的机会,但我们面对的对手非常强大,他们踢得非常好。”“我们的球迷应该为过去几个赛季里我们在冠军杯中的表现感到骄傲。”博斯克还说。对于博斯克在首发中排出了久疏战阵的坎比亚索,赛后有记者提出了质疑,认为完全应该将队内的另一名球员帕文派遣上场以加强后卫线。对于这一疑议,博斯克拒绝承担所谓的“责任”,认为球队的首发没有问题。“我们按照整个赛季以来的方式做了,对于人员上的变化我没有什么可说的。”对于球队在本赛季的前景,博斯克表示皇马还有西甲联赛的冠军作为目标。“皇家马德里在冠军杯中战斗到了最后,我们在联赛中也将这么做。”

  

  海量分词结果:

  

      新浪 体育   尤文 淘汰 之后 皇马 主帅 博斯克 拒绝 接受 媒体 球队 后防线 批评 同时 自己 排出 首发 阵容 进行 辩护 失利 全队 责任 仅仅 后防线 指责 博斯克 认为 我们 一塌糊涂 。” 我们 进入 半决赛 而且 晋级 道路 一路 奋战 即使 今天 比赛 我们 几个 翻身 机会 我们 面对 对手 非常 强大 他们 非常 。” 我们 球迷 应该 过去 几个 赛季 我们 冠军 杯中 表现 感到 骄傲 。” 博斯克 对于 博斯克 首发 排出 战阵 坎比亚索 赛后 记者 提出 质疑 认为 完全 应该 一名 球员 帕文 派遣 上场 加强 后卫线 对于 博斯克 拒绝 承担 所谓 责任 认为 球队 首发 没有 问题 我们 按照 整个 赛季 以来 方式 对于 人员 变化 没有 什么 。” 对于 球队 赛季 前景 博斯克 表示 皇马 还有 西 联赛 冠军 作为 目标 皇家 马德里 冠军 杯中 战斗 最后 我们 联赛 这么 。”

  

  ChineseParse分词结果:

  

      体育   淘汰 之后 主帅 博斯 拒绝 接受 媒体 球队 后防线 批评 同时 自己 首发 阵容 进行 辩护 。“ 失利 全队 责任 不仅仅 后防线 指责 博斯 认为 我们 一塌糊涂 我们 进入 半决赛 而且 晋级 道路 上一 奋战 即使 今天 比赛 我们 翻身 机会 我们 面对 对手 非常 强大 他们 非常 我们 球迷 应该 赛季 我们 冠军杯 表现 感到 骄傲 博斯 。对于 博斯 首发 中排 赛后 记者 提出 质疑 认为 完全 应该 另一 球员 派遣 上场 加强 后卫线 对于 这一 博斯 拒绝 承担 所谓 责任 认为 球队 首发 没有 问题 我们 按照 整个 赛季 以来 方式 对于 人员 变化 没有 什么 对于 球队 本赛 前景 博斯 表示 还有 西 联赛 冠军 作为 目标 皇家 马德里 冠军杯 战斗 最后 我们 联赛 这么

  

  因为没有体育专业词库和人名专业词库,所以ChineseParse不能认识这些专业词.

  

  Case 2: 我国汽车社会第一次重大转型历经十多年时间。在1994年出台的《汽车工业产业政策》中,最醒目的一条就是“逐步改变以行政机关、团体、事业单位及国有企业为主的公款购买、使用小汽车的消费结构”。从公款购买汽车为主到汽车逐渐进入家庭,第一次重大转型给人民生活质量带来了巨大提升。这次转型的主要推动力是态度鲜明的产业政策、持续高速增长的国民经济以及蓬勃发展的国内汽车工业。 然而,当我们快速迈进以私人汽车为主体的汽车社会的时候,也面临着新的形势、新的考验:中央强调树立和落实科学发展观,要求国内企业提高自主创新能力;今年“两会”期间,中央又提出构建和谐社会和节约型社会的精神;同时,我国汽车社会面临能源紧缺、燃油价格上涨、土地资源有限等诸多不利因素。在这样的大背景下,进行第二次重大转型刻不容缓。

  

  海量分词结果:

  

  我国 汽车 社会 第一次 重大 转型 历经 十多年 时间 1994 出台 汽车 工业 产业 政策 醒目 一条 就是 逐步 改变 行政 机关 团体 事业 单位 国有 企业 为主 公款 购买 使用 小汽车 消费 结构 公款 购买 汽车 为主 汽车 逐渐 进入 家庭 第一次 重大 转型 人民 生活 质量 带来 巨大 提升 这次 转型 主要 推动力 态度 鲜明 产业 政策 持续 高速 增长 国民经济 以及 蓬勃 发展 国内 汽车 工业 然而 我们 快速 迈进 私人 汽车 主体 汽车 社会 时候 面临 形势 考验 中央 强调 树立 落实 科学 发展观 要求 国内 企业 提高 自主 创新 能力 今年 两会 期间 中央 提出 构建 和谐 社会 节约型 社会 精神 同时 我国 汽车 社会 面临 能源 紧缺 燃油 价格 上涨 土地 资源 有限 诸多 不利 因素 这样 背景 进行 第二次 重大 转型 刻不容缓

  

  ChineseParse分词结果:

  

  我国 汽车 社会 第一 重大 转型 历经 多年 时间 1 9 9 4 出台 汽车 工业 产业 政策 醒目 一条 就是 逐步 改变 行政 机关 、团体 事业 单位 国有 企业 为主 公款 购买 使用 小汽车 消费 结构 ”。 公款 购买 汽车 为主 汽车 逐渐 进入 家庭 第一 重大 转型 人民 生活 质量 带来 巨大 提升 这次 转型 主要 推动力 态度 鲜明 产业 政策 持续 高速 增长 国民经济 以及 蓬勃 发展 国内 汽车 工业 然而 我们 快速 迈进 私人 汽车 为主 汽车 社会 时候 面临 形势 考验 中央 强调 树立 落实 科学 发展观 要求 国内 企业 提高 自主 创新 能力 今年 两会 期间 中央 提出 构建 和谐 社会 节约 社会 精神 同时 我国 汽车 社会 面临 能源 紧缺 燃油 价格 上涨 土地 资源 有限 诸多不 因素 这样 背景 进行 第二 重大 转型 刻不容缓

  

  可以看出,ChineseParse不能智能处理"第一次","第二次"这种词,对数字也没识别能力,不过基本的分词效果还是可以的.

  

  (毕竟我3个小时就把程序搞定了,怎么能和别人十年积累的比呢?)

  

  

  性能测试(迅驰1.5M): 每秒钟67.7万字

  

  程序优化有应该更高.

  

  五、小结

  

  进一步应该做的:

  1,能识别简单的外语,数字

  2,具备简单智能

  3,扩充词库

  

  然后就有实用价值了.

  

  :前几个月写的大多都是诸如此类简单的中文处理小程序,如繁简转换,自动排版,批量替换,中文分词,有时间的话我会把这些程序集中起来打包成一个实用的中文处理工具.不知道大家还有什么需求,不防说说.

2006年04月26日

from:          http://www-128.ibm.com/developerworks/cn/webservices/ws-bpelcol/index.html


developerWorks 中国 > SOA and Web services >
developerWorks


使用 BPEL4WS 的业务流程
e-mail it!

订阅:
developerWorks 时事通讯

Sanjiva Weerawarana, 研究人员, IBM
Francisco (Paco) Curbera, 研究人员, IBM
Rania Khalaf, 软件工程师, IBM
Matthew Duftler, 软件工程师, IBM
Nirmal K. Mukhi, 软件工程师, IBM

2002 年 6 月 04 日

欢迎来到使用 XML 专栏 — developerWorks 上的一个新专栏 。该专栏的前提是,开发人员最好通过研究代码来学习,因此我会随同专栏一起开发一系列 XML 项目,这些项目将在几篇专栏文章中讨论。感谢这种形式,这样我可以解决更大、更现实的项目,而不是通常可能仅为一篇文章的情景所构思的项目。请注意,您可以在本专栏伙伴站点上找到作为开放源码项目的演示项目本身(请参阅参考资料)。我期待着这些项目可以随着你我的使用而不断发展,届时我会在这里报告那些更改。

使用 BPEL4WS 的业务流程:学习 BPEL4WS,第 8 部分 New!

本文阐述三个 BPEL 活动的使用:switch、pick 和 compensate。

使用 BPEL4WS 的业务流程:学习 BPEL4WS,第 7 部分:将相关性和故障处理添加到流程中

本文扩展前几篇文章中一直讨论的简单 BPEL4WS 流程,使它能够和一个已经存在的流程实例进行通信并能够捕获自身执行过程中可能发生的故障。

使用 BPEL4WS 的业务流程:学习 BPEL4WS,第 6 部分:相关性、故障处理和补偿

本文讨论 BPEL4WS 的高级属性,它们是定义和执行业务流程的基础。

使用 BPEL4WS 的业务流程:学习 BPEL4WS,第 5 部分:添加链接并操作数据

本文继续前面第二部分中的示例并将它扩展到 BPEL4WS 规范和 BPWS4J 示例所包含的贷款批准流程中。在 BPEL4WS 中,条件是 XPath 表达式,本文显示条件如何合并流程的容器数据。

使用 BPEL4WS 的业务流程:学习 BPEL4WS,第 4 部分:用 BPWS4J 编辑器创建流程

作者在本文中描述了创建 BPEL4WS 流程的设计方式,还描述了如何使用 BPWS4J 编辑器来创建、修改和验证这些流程。

使用 BPEL4WS 的业务流程:学习 BPEL4WS,第 3 部分:各种活动以及内存中模型

这一部分将更详细介绍每一种活动。我们还将介绍如何在内存中表示和操作各种 BPEL4WS 构造。

使用 BPEL4WS 的业务流程:学习 BPEL4WS,第 2 部分:创建一个简单的流程

本文将阐述一个整合的主要方面,还将说明服务的 WSDL 描述与 BPEL4WS 流程定义有什么关系以及 BPEL4WS 流程定义如何使用它。

使用 BPEL4WS 的业务流程:学习 BPEL4WS,第 1 部分:业务流程中的概念

本文是本系列的第 1 部分,将引领读者创建第一个简单的流程。后续的部分将以不同方式扩展这个示例,以便阐述并解释 BPEL4WS 语言的关键部分,包括数据操纵、相关性、故障处理、补偿以及 BPEL4WS 中的各种结构化活动。

作者简介
Sanjiva Weerawarana 是 IBM T.J. Watson Research Center 的 Component Systems 组的一名研究人员。他是 BPEL4WS 规范、WSDL 规范以及 WSFL 规范的作者之一,也是 BPWS4J、Apache SOAP、WSTK、WSDL Toolkit、WSIF 和 WSGW 的开发者之一。他获得了 Purdue University 的计算机科学博士学位。您可以通过 sanjiva@us.ibm.com与作者联系。


Francisco Curbera 是 IBM T.J. Watson Research Center 的 Component Systems 组的一名研究人员,也是该组的主管。他是 BPEL4WS 规范、WSDL 规范以及 WSFL 规范的作者之一,也是 BPWS4J、Apache SOAP 和 WSTK 的开发者之一。他获得了 Columbia University 的应用数学博士学位。请通过 curbera@us.ibm.com与他联系。


Rania Khalaf 是 IBM TJ Watson 研究中心的 Componet Systems 小组的一名软件工程师。2001 年,她从 MIT 获得学士学位和工程硕士学位后进入 IBM。Rania 是 IBM BPEL4WS 引擎的创建者之一,您可以从 alphaWorks 上获得 BPWS4J。您可以通过 rkhalaf@watson.ibm.com与作者联系。


Matthew J. Duftler 是 IBM T.J. Watson Research Center 的 Component Systems 小组的一名软件工程师。他是 Apache SOAP 的原作者之一,是 JSR110(Java APIs for WSDL)的带头人之一,还是 IBM BPEL4WS 引擎的创作者之一。您可以通过 duftler@us.ibm.com与 Matthew Duftler 联系。


Rania Khalaf 是 IBM TJ Watson Research Center 的 Component System 小组的一名软件工程师。2001 年,她从 MIT 获得学士学位和工程硕士学位后进入 IBM。Rania 是 IBM BPEL4WS 引擎的创作者之一,您可以从 alphaWorks 上获得 BPWS4J。您可以通过 nmukhi@us.ibm.com与作者联系。




e-mail it!

developerWorks 中国 > SOA and Web services >
developerWorks


    关于 IBM 隐私条约 联系 IBM

2006年04月21日

from:      http://zh.transwiki.org/wiki/index.php/%E8%AF%AD%E4%B9%89%E7%BD%91%E7%95%8C%E8%91%97%E5%90%8D%E4%BA%BA%E7%89%A9

语义网界著名人物

TransWiki – W3CHINA.ORG开放翻译计划(OTP)

United States


W3C Semantic Web Interest Group (http://www.w3.org/2001/sw/interest/)


Carnegie Mellon University(CMU) (http://www.cmu.edu)


Stanford University (http://www.stanford.edu)

  • KSL(Knowledge System Labs) (http://ksl.stanford.edu)
  • Fikes, Richard (http://ksl.stanford.edu/people/bio/fikes.html) 元老级人物。曾参与过KIF, STRIPS, Ontolingua, OKBC, DAML的开发或研究。
  • McGuinness, Deborah L. (http://ksl.stanford.edu/people/dlm/) 描述逻辑大牛,W3C OWL推荐标准的 co-editor, 参与过著名的描述逻辑系统 CLASSIC以及后来的OIL, DAML+OIL, OWL的研究。
  • McIlraith, Sheila (http://ksl.stanford.edu/people/sam/)
  • Gruber, Tom (http://ksl-web.stanford.edu/people/gruber/index.html) Ontology大牛,他对计算机中Ontology的定义至今仍被被反复引用着


University of Maryland, College Park (http://www.umd.edu)


The University of Maryland, Baltimore County (http://www.umbc.edu/)

  • ebiquity Group (http://ebiquity.umbc.edu/) lead by Tim Finin.
  • Tim Finin (http://www.csee.umbc.edu/~finin/) KQML作者。


University of WestFlorida



Canada


University of Toronto




United Kindom


University of Manchester

  • Ian Horrocks (http://www.cs.man.ac.uk/~horrocks/) 描述逻辑界不得不提的人物,The Description Logic Handbook作者之一。参与过OIL与OWL的开发,提出了SWRL。早期参与过FaCT系统的设计与实现,其他成果包括OilEd, Instance Store等。

University of Edinburg


University of Southampton 




Holland


Vrije University of Amsterdan




Germany


University of Karlsruhe




Japan


Keio University

2006年04月11日

frOM:   http://boole.cs.iastate.edu/semanticweb/topic.cgi?forum=10&topic=73&show=50

面向语义Web的知识表示框架  
作者:刘炎禄, 俞 勇  来源:不详  阅读次数:223  上传:ccnuzhb001 摘 要: 语义互联网(SemanticWeb)主要在于提供计算机软件可处理的元数据(Metadata)描述和信息表达方式.介绍了以RDF(S)技术为基础的在Web上表达知识信息的方法,讨论了运用RDF(S)技术建立知识表示本体(Ontology),以及通过扩展基本的RDF(S)描述集合方法来增强表示特定领域知识的能力.提出了运用基于RDFS技术的基本RDF(S)元语集合、通用描述元语集合、公理描述元语集合和关系约束元语集合进行知识表示的技术框架.使用这4层元语集合,可以将一般领域的知识信息以RDF(S)的方式存储在Web平台上,并通过相应支持XML的程序进行处理.
关键词:资源描述框架;本体;元数据;核心元素集合;语义互联网中图分类号:TP393.4   
文献标识码:A
KnowledgeRepresentationonSemanticWeb
LIUYan-lu,YUYong
(Dept.ofComputerScienceandEng.,ShanghaiJiaotongUniv.,Shanghai200030,China)
Abstract:SemanticWebisthenextgenerationofwebtechnology,itprovidesarepresentationofmetadataandinformationwhichcanbeprocessedbycomputersoftware.Thispaperpresentedanoutlineofontolo-gy-basedknowledgerepresentationontheWebbyusingRDF(S).AndtheextensionofRDF(S)specifica-tionwasalsointroducedtoenhancetheabilityofdomainknowledgeexpression.ByusingfourmetasetsincludingthebasicRDFS,Dublincore,axiomsandpropertyconstraints,ageneraldomainknowledgecanbestoredasRDF(S)documentsontheWebandbeprocessedbyXMLenabledapplications.
Keywords:resourcedescriptionframework(RDF);ontology;metadata;dublincore(DC);semanticweb  

关键字: 资源描述框架;本体;元数据;核心元素集合;语义互联网中图分类

随着互联网信息的日益增加,基于Web的系统平台正从简单的提供人们浏览的网页向提供更多的具有语义特征信息的方向转变[1].下一代Web技术支撑的平台将支持信息语义化和智能服务,例如智能搜索引擎和智能代理等.语义互联网(SemanticWeb)正逐渐将Internet变成一个巨大的全球化知识库.这个知识库能够满足人们浏览信息的需要,更重要的是通过标准的语义规范使计算机自动读取和处理信息.资源描述框架(ResourceDescriptionFramework,RDF)是由W3C提出的一套可描述知识概念和实例的规范标准[2].在RDF技术的基础上,W3C又提出了资源描述框架定义集(ResourceDescriptionFrameworkSchema,RDFS)[3].为满足描述信息的需要,RDFS允许用户自定义除了RDF基本描述集合以外的特定领域的概念元数据集合,即本体(Ontology).目前,已有一些通过RDFS来定义的通用知识库概念集合,如DublinCore[4],OntologyInferenceLayer[5].总之,RDFS就是将实例信息中概念与概念之间的关系抽取出来,表示为知识库中的Ontology.
OntologyRepresentation技术已经在信息集成、信息代理、专家系统等领域取得了很大成功,RDF提供了将Ontology在Web上实现的方法,它将与XML一起成为下一代互联网的标准技术规范.通过本文所介绍的基本RDF(S)元语集合、通用描述元语集合、公理描述元语集合和关系约束元语集合这4层元语集合共同构造Ontology模型,探讨了在多个元语集合研究成果的基础上将它们融合在一起的方法,该方法对建立基于语义互联网的知识管理系统有着关键性作用.
1 用RDF(S)元语建立Ontology
1.1资源描述框架(RDF)
在RDF中,除了基本类型(如字符串等),所有的信息都被定义为资源(Resource),它可以通过属性(Property)与其他Resource或者基本类型建立联系,构成语义网[6].每个关系都可以看作是联系两个Resource或者一个Resource和一个基本类型的纽带,这3者构成了语句(Statement).语句是由主语(Resource),谓语(Property)和宾语(Resource或者基本类型)构成的.图1所示为基于RDF的老师与学生实例的描述.图1 老师和学生的RDF实例表示Fig.1RDFinstancesofteacherandstudent

1.2资源描述框架模式
相对于RDF而言,RDFS在建立特定领域Ontology方面作用更为重要.RDFS在RDF基础之上定义了一组可清晰描述Ontology的元语集合.本文要探讨的多个元语集合共同扩展知识表达能力的概念就是建立在RDFS之上,下面介绍RDFS的基本框架.在RDFS中,最上层的抽象根类结点是rdf:Resource,它又派生出两个子类rdfs:Class和rdf:Property,任何领域的知识都可以认为是这两个子类的实例.rdfs:Class语义上代表了领域中的Ontology,rdf:Property代表了领域中Ontology的Property.在RDFS规范中,特别定义了rdfs:sub-ClassOf作为rdf:Property的实例来表示rdfs:Class的实例属性.这样,就可以定义不同Ontology之间类的从属关系,从而建立知识表达中最基本的Ontology语义层次结构.类似的rdfs:subProperty-Of作为rdf:Property的实例表示rdf:Property的实例属性,可以定义不同Property之间的从属关系.在RDFS规范中,定义了rdfs:domain和rdfs:range表示rdf:Property的实例所应用的范畴.
1.3 Ontology的序列化XML
文档文献[7]中的研究表明,RDF中所描述的实例信息可以序列化成XML文档,同时,相应的RDFSOntology定义可以构造出该XML文档的Schema.这是通过将rdfs:Class和rdf:Property实例对应到XMLSchema中的Element来实现的.由于目前大多数应用都不能处理RDF(S)信息,故这一特性十分重要.它可以允许现有的应用程序读取、写入和修改知识库中包含语义的信息.通过命名空间,不同领域的Ontology可以综合表示成一个XML文档.
2 RDF(S)元语集合的扩展
2.1通用描述元语集合
在建立不同领域的Ontology过程中,有一些描述Ontology的通用元语集合,如Title,Author,Version,Publisher等.目前,国际上认可的RDFDublinCore元语集合(1.1版本)一共定义了15个元语Ontology,DublinCore元语集合(简称DC)允许不同的知识表达系统来交换Ontology定义的头部信息.它已经被广泛的应用到信息交换、代理搜索和智能服务等各个方面.
2.2 公理描述元语集合
公理在知识表示领域中也是Ontology,与一般的Ontology不同的是,公理Ontology可能有大于1的关系联系着其他的Ontology.由于RDF(S)仅仅提供了最基本的描述元语,因此公理Ontology是没有预先定义的.而公理是进行知识推理的基本要求,由此对公理的RDFS表示方式研究也是非常重要的.目前,文献[8]中对这一方面进行了研究.常用知识表达基本公理元语包括对称与反对称(Symme-try和Asymmetry)、关系的传递性(Transitivity)、关系的逆反性(Inverse)、Ontology的关联性(Dis-joint)以及Ontology的涵盖性(Cover).公理中最为简单的是对称关系.然而,对于Disjoint和Cover等元语,涉及了多个Ontology和Property.例如校园里有老师、学生和职工,同时知道任何一个人都是且只能是这三种类型中的一种.因此,这种知识表达要通过公理元语来表达这个约束条件,如下所示:  
 〈rdfs:ClassID=“Disjoint”〉
 〈rdfs:subClassOfrdf:resource=“#Axiom”/〉 
〈/rdfs:Class〉〈rdfs:ClassID=“Cover”〉
 〈rdfs:subClassOfrdf:resource=“#Axiom”/〉
 〈/rdfs:Class〉〈Disjoint〉 
〈rdf:Bag〉〈rdf:liresource=“#Teacher”〉 
〈rdf:liresource=“#Student”〉 〈rdf:liresource=“#Worker”〉
〈/rdf:Bag〉 〈Disjoint〉〈Coveraxiom:ID=“#Person”〉 
〈rdf:Bag〉〈rdf:liresource=“#Teacher”〉 
〈rdf:liresource=“#Student”〉 
〈rdf:liresource=“#Worker”〉 
〈/rdf:Bag〉〈/Cover〉
目前,对采用RDFS来描述公理的研究正在进行中,为了与RDF的基本规则相一致,许多传统人工智能领域中知识表示的方式都需要一定的修改才能移植到RDFS上.
2.3 关系约束元语集合
为了准确定义OntologyProperty,往往需要对其进行一些条件限制,如大小、集合的势、基本数据类型等,这也是在XMLSchema中进行Property检验所必需的.
2.3.1 集合的势 
下面通过一个例子来说明这个问题.在学校这个领域中,班级可以说是一个Ontol-ogy,班级是由学生构成的,如下所示: 〈rdfs:ClassID=“SchoolClass”〉 〈rdfs:subClassOfrdf:resource=“#resource”/〉 〈/rdfs:Class〉〈rdf:PropertyID=“member”〉 〈rdfs:domainrdf:resource=“#SchoolClass”/〉 〈rdfs:rangerdf:resource=“#Student”/〉 〈/rdf:Property〉人数必须小于60人.因此Class中的Member属性集合的势必须受到约束. 〈rdf:PropertyID=“member”〉 〈rdfs:domainrdf:resource=“#SchoolClass”/〉 〈rdfs:rangerdf:resource=“#Student”/〉 〈maxCardinalityvalues=“60”〉 〈minCardinalityvalues=“1”〉〈/rdf:Property〉上例增加了2个约束元语和1个属性元语,分别是:maxCardinality、minCardinality和values.当用这些Ontology描述具体信息序列化成XML文档时,新添加的约束元语很容易生成相应的XMLSchema来进行验证.
2.3.2 数据类型和范围
 在RDFS基本规范中,将简单信息归结成文本(Literal),并没有给出任何类型定义.在描述Ontology的过程以及XMLSchema的生成中,都需要明确的数据类型定义.这些数据类型包括:Integer,Float,Currency,Date,String等.下面定义Person这个Ontology的年龄,以下用扩展数据类型的RDFS表示: 〈rdf:PropertyID=“Age”〉 〈rdfs:subPropertyOfrdf:resource=“#Property”/〉 〈rdfs:domainrdf:resource=“Person”/〉 〈rdfs:rangerdf:resource=“Literal”/〉 〈valuetyperdf:resource=“Integer”/max=“100”min=“0”/〉 〈/rdf:Property〉
2.3.3 特定属性约束
 由RDFS基本定义可知,RDFS对Ontology和Proerty的定义在同一层次上.例如Age这个Property的定义是在Person这个Ontology之外的,因为Age这个Property不但属于人,也可能属于其他的Ontology,只要增加年龄中的rdfs:domain属性就可以实现.但是,这样也产生了新的问题,对于Age的约束条件不能放在Property本身定义中,因为不同的Ontology对这个Age的要求是不一样的.所以也就出现了特殊属性约束,它是作用在Ontology上的,如下所示: 〈rdfs:ClassID=“Teacher”〉 〈rdfs:subClassOfrdf:resource=“#Person”/〉 〈ConstraintOnPropertyrdf:resource=“#Age”〉 〈valuetyperdf:resource=“Integer”/max=“60”min=“20”/〉 〈/ConstraintOnProperty〉〈/rdfs:Class〉综上所述可知,RDF(S)规范定义使得增加新的元语集合非常方便,这是通过应用XML中命名空间机制来实现的.应用这3种扩展元语集合与RDFS一起可以表示一般范畴内的知识领域.通过对扩展元语集合的介绍,也可以了解到扩展RDFS的方式,特定领域的知识创建者可以根据需要自行创建元语集合.
3 结 语
本文提出了运用基于RDFS技术的4层元语集合进行知识表示的技术框架,给出了通过RDF(S)的知识建模到动态XML文档自动生成的示例.这些技术对于将RDF(S)知识表示与现有的XML应用融合起到了关键性的作用.目前,作者已经实现了Java和C++两个版本的自动生成.本文通过介绍RDF(S)技术提出了一种通过动态添加元语集合的方式来扩展知识表达能力的方法.目前,作者正在进行关于布尔表达、一阶谓词逻辑等价元语集合的研究,这些将在以后介绍.
参考文献:
[1] Berners-LeeT.SemanticWebRoadmap[DB/OL].http:/w3.org/DesignIssues/Semantic.html
[2]LassilaO,SwickRR.Resourcedescriptionframe-work(RDF)modelandsyntaxspecification[DB/OL].http://www.w3.org/TR/REC-rdf-syntax
[3]BricklyD,GuhaR.Resourcedescriptionframework(RDF)schemaspecification1.0[DB/OL].http://www.w3.org/TR/2000/CR-rdf-schema-20000327
[4]WeibelS.Dublincorespecification[DB/OL].http://dublincore.org/
[5]BechhoferS.AninformaldescriptionofstandardOILandinstanceOIL[DB/OL].http://www.on-toknowledge.org/oil/downl/oil-whitepaper.pdf
[6]StaabS.Anextensibleapproachformodelingon-tologiesinRDF(S)[A].ECDL2000WorkshopontheSemanticWeb[C].Lisbon,Portugal:[s.n.],2000.
[7]ErdmannM,StuderR.OntologiesasconceptualmodelsforXMLdocuments[DB/OL].http://sern.ucalgary.ca/KSI/KAW/KAW99/papers/Erdmann1/erdmann.pdf
[8] HorrocksI.Theontologyinferencelayer[DB/OL].http://www.ontoknowledge.org/

2006年04月07日

from:

http://www.lucene.com.cn/about.htm

LUCENE.COM.CN 中国

 

 

··· 2

第一节 全文检索系统与Lucene简介··· 3

一、       什么是全文检索与全文检索系统?··· 3

二、       什么是Lucene··· 4

三、       Lucene的应用、特点及优势··· 4

四、       本文的重点问题与cLucene项目··· 5

第二节 Lucene系统结构分析··· 5

一、       系统结构组织··· 5

二、       数据流分析··· 6

三、       基于Lucene的应用开发··· 8

第三节 Lucene索引文件格式分析··· 9

一、       Lucene源码实现分析的说明··· 9

二、       Lucene索引文件格式··· 10

三、       一些公用的基础类··· 12

四、       存储抽象··· 13

五、       关于cLucene项目··· 15

第四节 Lucene索引构建逻辑模块分析··· 15

一、       绪论··· 15

二、       对象体系与UML··· 16

1     项(Term··· 16

2     域(Field··· 17

3     文档(document··· 18

4     段(segment··· 19

5     IndexReader类与IndexWirter··· 23

三、       数据流逻辑··· 24

四、       关于cLucene项目··· 25

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

开放源代码的全文检索引擎Lucene

――介绍、系统结构与源码实现分析

 

 

 

 

 

 

 

 

 

 

 

 

 

 

第一节 全文检索系统与Lucene简介

 

一、             什么是全文检索与全文检索系统?

 

全文检索是指计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。这个过程类似于通过字典中的检索字表查字的过程。

 

全文检索的方法主要分为按字检索和按词检索两种。按字检索是指对于文章中的每一个字都建立索引,检索时将词分解为字的组合。对于各种不同的语言而言,字有不同的含义,比如英文中字与词实际上是合一的,而中文中字与词有很大分别。按词检索指对文章中的词,即语义单位建立索引,检索时按词检索,并且可以处理同义项等。英文等西方文字由于按照空白切分词,因此实现上与按字处理类似,添加同义处理也很容易。中文等东方文字则需要切分字词,以达到按词索引的目的,关于这方面的问题,是当前全文检索技术尤其是中文全文检索技术中的难点,在此不做详述。

 

全文检索系统是按照全文检索理论建立起来的用于提供全文检索服务的软件系统。一般来说,全文检索需要具备建立索引和提供查询的基本功能,此外现代的全文检索系统还需要具有方便的用户接口、面向WWW[1]的开发接口、二次应用开发接口等等。功能上,全文检索系统核心具有建立索引、处理查询返回结果集、增加索引、优化索引结构等等功能,外围则由各种不同应用具有的功能组成。结构上,全文检索系统核心具有索引引擎、查询引擎、文本分析引擎、对外接口等等,加上各种外围应用系统等等共同构成了全文检索系统。图1.1展示了上述全文检索系统的结构与功能。

 

在上图中,我们看到:全文检索系统中最为关键的部分是全文检索引擎,各种应用程序都需要建立在这个引擎之上。一个全文检索应用的优异程度,根本上由全文检索引擎来决定。因此提升全文检索引擎的效率即是我们提升全文检索应用的根本。另一个方面,一个优异的全文检索引擎,在做到效率优化的同时,还需要具有开放的体系结构,以方便程序员对整个系统进行优化改造,或者是添加原有系统没有的功能。比如在当今多语言处理的环境下,有时需要给全文检索系统添加处理某种语言或者文本格式的功能,比如在英文系统中添加中文处理功能,在纯文本系统中添加XML[2]或者HTML[3]格式的文本处理功能,系统的开放性和扩充性就十分的重要。

 

二、             什么是Lucene

 

Luceneapache软件基金会[4] jakarta项目组的一个子项目,是一个开放源代码[5]的全文检索引擎工具包,即它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎(英文与德文两种西方语言)。Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。

 

Lucene的原作者是Doug Cutting,他是一位资深全文索引/检索专家,曾经是V-Twin搜索引擎[6]的主要开发者,后在Excite[7]担任高级系统架构设计师,目前从事于一些Internet底层架构的研究。早先发布在作者自己的http://www.lucene.com/,后来发布在SourceForge[8]2001年年底成为apache软件基金会jakarta的一个子项目:http://jakarta.apache.org/lucene/

 

三、             Lucene的应用、特点及优势

 

作为一个开放源代码项目,Lucene从问世之后,引发了开放源代码社群的巨大反响,程序员们不仅使用它构建具体的全文检索应用,而且将之集成到各种系统软件中去,以及构建Web应用,甚至某些商业软件也采用了Lucene作为其内部全文检索子系统的核心。apache软件基金会的网站使用了Lucene作为全文检索的引擎,IBM的开源软件eclipse[9]2.1版本中也采用了Lucene作为帮助子系统的全文索引引擎,相应的IBM的商业软件Web Sphere[10]中也采用了LuceneLucene以其开放源代码的特性、优异的索引结构、良好的系统架构获得了越来越多的应用。

 

Lucene作为一个全文检索引擎,其具有如下突出的优点:

1)索引文件格式独立于应用平台。Lucene定义了一套以8位字节为基础的索引文件格式,使得兼容系统或者不同平台的应用能够共享建立的索引文件。

2)在传统全文检索引擎的倒排索引的基础上,实现了分块索引,能够针对新的文件建立小文件索引,提升索引速度。然后通过与原有索引的合并,达到优化的目的。

3)优秀的面向对象的系统架构,使得对于Lucene扩展的学习难度降低,方便扩充新功能。

4)设计了独立于语言和文件格式的文本分析接口,索引器通过接受Token流完成索引文件的创立,用户扩展新的语言和文件格式,只需要实现文本分析的接口。

5)已经默认实现了一套强大的查询引擎,用户无需自己编写代码即使系统可获得强大的查询能力,Lucene的查询实现中默认实现了布尔操作、模糊查询(Fuzzy Search[11])、分组查询等等。

 

    面对已经存在的商业全文检索引擎,Lucene也具有相当的优势。首先,它的开发源代码发行方式(遵守Apache Software License[12]),在此基础上程序员不仅仅可以充分的利用Lucene所提供的强大功能,而且可以深入细致的学习到全文检索引擎制作技术和面相对象编程的实践,进而在此基础上根据应用的实际情况编写出更好的更适合当前应用的全文检索引擎。在这一点上,商业软件的灵活性远远不及Lucene。其次,Lucene秉承了开放源代码一贯的架构优良的优势,设计了一个合理而极具扩充能力的面向对象架构,程序员可以在Lucene的基础上扩充各种功能,比如扩充中文处理能力,从文本扩充到HTMLPDF[13]等等文本格式的处理,编写这些扩展的功能不仅仅不复杂,而且由于Lucene恰当合理的对系统设备做了程序上的抽象,扩展的功能也能轻易的达到跨平台的能力。最后,转移到apache软件基金会后,借助于apache软件基金会的网络平台,程序员可以方便的和开发者、其它程序员交流,促成资源的共享,甚至直接获得已经编写完备的扩充功能。最后,虽然Lucene使用Java语言写成,但是开放源代码社区的程序员正在不懈的将之使用各种传统语言实现(例如.net framework[14]),在遵守Lucene索引文件格式的基础上,使得Lucene能够运行在各种各样的平台上,系统管理员可以根据当前的平台适合的语言来合理的选择。

 

四、             本文的重点问题与cLucene项目

 

作为中国人民大学信息学院99级本科生的一个毕业设计项目,我们对Lucene进行了深入的研究,包括系统的结构,索引文件结构,各个部分的实现等等。并且我们启动了cLucene项目,做为一个LuceneC++语言的重新实现,以期望带来更快的速度和更加广泛的应用范围。我们先分析了系统结构,文件结构,然后在研究各个部分的具体实现的同时开始进行的cLucene实现。限于时间的限制,到本文完成为止,cLucene项目并没有完成,对于Lucene的具体实现部分也仅仅完成到了索引引擎部分。

 

接下来的部分,本文将对Lucene的系统结构、文件结构、索引引擎部分做一个彻底的分析。以期望提供对Lucene全文检索引擎的系统架构和部分程序实现的清晰的了解。cLucene项目则作为一个开放源代码的项目,继续进行的开发。

 

       有关cLucene项目的一些信息:

n         开发语言:ISO C++[15]STLport 4.5.3[16]OpenTop 1.1[17]

n         目标平台:Win32POSIX

n         授权协议:GNU General Public License (GPL)[18]

 

 

第二节 Lucene系统结构分析

 

一、             系统结构组织

 

Lucene作为一个优秀的全文检索引擎,其系统结构具有强烈的面向对象特征。首先是定义了一个与平台无关的索引文件格式,其次通过抽象将系统的核心组成部分设计为抽象类,具体的平台实现部分设计为抽象类的实现,此外与具体平台相关的部分比如文件存储也封装为类,经过层层的面向对象式的处理,最终达成了一个低耦合高效率,容易二次开发的检索引擎系统。

 

以下将讨论Lucene系统的结构组织,并给出系统结构与源码组织图:

 

    从图中我们清楚的看到,Lucene的系统由基础结构封装、索引核心、对外接口三大部分组成。其中直接操作索引文件的索引核心又是系统的重点。Lucene的将所有源码分为了7个模块(在java语言中以包即package来表示),各个模块所属的系统部分也如上图所示。需要说明的是org.apache.lucene.queryPaser是做为org.apache.lucene.search的语法解析器存在,不被系统之外实际调用,因此这里没有当作对外接口看待,而是将之独立出来。

 

    从面象对象的观点来考察,Lucene应用了最基本的一条程序设计准则:引入额外的抽象层以降低耦合性。首先,引入对索引文件的操作org.apache.lucene.store的封装,然后将索引部分的实现建立在(org.apache.lucene.index)其之上,完成对索引核心的抽象。在索引核心的基础上开始设计对外的接口org.apache.lucene.searchorg.apache.lucene.analysis。在每一个局部细节上,比如某些常用的数据结构与算法上,Lucene也充分的应用了这一条准则。在高度的面向对象理论的支撑下,使得Lucene的实现容易理解,易于扩展。

 

    Lucene在系统结构上的另一个特点表现为其引入了传统的客户端服务器结构以外的的应用结构。Lucene可以作为一个运行库被包含进入应用本身中去,而不是做为一个单独的索引服务器存在。这自然和Lucene开放源代码的特征分不开,但是也体现了Lucene在编写上的本来意图:提供一个全文索引引擎的架构,而不是实现。

 

二、             数据流分析

 

理解Lucene系统结构的另一个方式是去探讨其中数据流的走向,并以此摸清楚Lucene系统内部的调用时序。在此基础上,我们能够更加深入的理解Lucene的系统结构组织,以方便以后在Lucene系统上的开发工作。这部分的分析,是深入Lucene系统的钥匙,也是进行重写的基础。

 

   我们来看看在Lucene系统中的主要的数据流以及它们之间的关系图:



索引查找逻辑

 



索引构建逻辑

 



查询语句语法分析逻辑

 



词法分析逻辑

 

流程图:文档: 查询结果流程图:顺序访问存储器: 查询语句


存储抽象

 

流程图:多文档: 索引文件流程图:多文档: 被索引文件

 

    2.2很好的表明了Lucene在内部的数据流组织情况,并且沿着数据流的方向我们也可以对与Lucene内部的执行时序有一个清楚的了解。现在将图中的涉及到的流的类型与各个逻辑对应系统的相关部分的关系说明一下。

 

    图中共存在4种数据流,分别是文本流、token流、字节流与查询语句对象流。文本流表示了对于索引目标和交互控制的抽象,即用文本流表示了将要索引的文件,用文本流向用户输出信息;在实际的实现中,Lucene中的文本流采用了UCS-2[19]作为编码,以达到适应多种语言文字的处理的目的。Token流是Lucene内部所使用的概念,是对传统文字中的词的概念的抽象,也是Lucene在建立索引时直接处理的最小单位;简单的讲Token就是一个词和所在域值的组合,后面在叙述文件格式时也将继续涉及到token,这里不详细展开。字节流则是对文件抽象的直接操作的体现,通过固定长度的字节(Lucene定义为8比特位长,后面文件格式将详细叙述)流的处理,将文件操作解脱出来,也做到了与平台文件系统的无关性。查询语句对象流则是仅仅在查询语句解析时用到的概念,它对查询语句抽象,通过类的继承结构反映查询语句的结构,将之传送到查找逻辑来进行查找的操作。

 

    图中的涉及到了多种逻辑,基本上直接对应于系统某一模块,但是也有跨模块调用的问题发生,这是因为Lucene的重用程度非常好,因此很多实现直接调用了以前的工作成果,这在某种程度上其实是加强了模块耦合性,但是也是为了避免系统的过于庞大和不必要的重复设计的一种折衷体现。词法分析逻辑对应于org.apache.lucene.analysis部分。查询语句语法分析逻辑对应于org.apache.lucene.queryParser部分,并且调用了org.apache.lucene.analysis的代码。查询结束之后向评分排序逻辑输出token流,继而由评分排序逻辑处理之后给出文本流的结果,这一部分的实现也包含在了org.apache.lucene.search中。索引构建逻辑对应于org.apache.lucene.index部分。索引查找逻辑则主要是org.apache.lucene.search,但是也大量的使用了org.apache.lucene.index部分的代码和接口定义。存储抽象对应于org.apache.lucene.store。没有提到的模块则是做为系统公共基础设施存在。

 

三、             基于Lucene的应用开发

 

通过以上的系统结构分析和数据流分析,我们已经很清楚的了解了Lucene的系统的结构特征。在此基础上,我们可以通过扩充Lucene系统来完成一个完备的全文检索引擎,紧接着还可以在全文检索引擎的基础上构建各种应用系统。鉴于本文的目的并不在此,以下我们只是略为叙述一下相关的步骤,从而给出应用开发的一些思路。

 

首先,我们需要的是按照目标语言的词法结构来构建相应的词法分析逻辑,实现Luceneorg.apache.lucene.analysis中定义的接口,为Lucene提供目标系统所使用的语言处理能力。Lucene默认的已经实现了英文和德文的简单词法分析逻辑(按照空格分词,并去除常用的语法词,如英语中的isamare等等)。在这里,主要需要参考实现的接口在org.apache.lucene.analysis中的Analyzer.javaTokenizer.java中定义,Lucene提供了很多英文规范的实现样本,也可以做为实现时候的参考资料。其次,需要按照被索引的文件的格式来提供相应的文本分析逻辑,这里是指除开词法分析之外的部分,比如HTML文件,通常需要把其中的内容按照所属于域分门别类加入索引,这就需要从org.apache.lucene.document中定义的类document继承,定义自己的HTMLDocument类,然后就可以将之交给org.apache.lucene.index模块来写入索引文件。完成了这两步之后,Lucene全文检索引擎就基本上完备了。这个过程可以用下图表示:

 

    当然,上面所示的仅仅只是对于Lucene的基本扩充过程,它将Lucene由不完备的变成完备的(尤其是对于非英语的语言检索)。除此之外我们还可以在很多方面对Lucene进行改造。第一个方面即为按照文档索引的域,比如标题,作者之类的信息对返回的查询结果排序,这即需要改造Lucene的评分排序逻辑。默认的,Lucene采用其内部的相关性方法来处理评分和排序,我们可以根据需要改变它。遗憾的是,这部分Lucene并没有做到如同扩充词法解析和文档类型那样的条理清晰,没有留下很好的接口,因此需要仔细的分析其源代码的实现,自行扩充等等。其他的方面,比如改进其索引的效率,改进其返回结果时候的缓冲机制等等,都是加强Lucene系统的方面,在此也不再叙述。

 

    完成了Lucene系统,之后就可以开始考虑其上的应用系统开发。如果应用系统也使用java语言开发,那么Lucene系统能够方便的嵌入到整个系统中去,作为一个API集来调用。这个过程十分简单,以下便是一个示例程序,配合注释理解起来很容易。



2.4 Lucene应用代码示例

 

文本框: public class IndexFiles {<br />
  //使用方法:: IndexFiles [索引输出目录] [索引的文件列表] ...<br />
  public static void main(String[] args) throws Exception {<br />
    String indexPath = args[0];<br />
    IndexWriter writer;<br />
    //用指定的语言分析器构造一个新的写索引器(第3个参数表示是否为追加索引)<br />
    writer = new IndexWriter(indexPath, new SimpleAnalyzer(), false);</p>
<p>    for (int i=1; i<args.length; i++) {<br />
      System.out.println("Indexing file " + args[i]);<br />
      InputStream is = new FileInputStream(args[i]);</p>
<p>      //构造包含2个字段Field的Document对象<br />
      //一个是路径path字段,不索引,只存储<br />
      //一个是内容body字段,进行全文索引,并存储<br />
      Document doc = new Document();<br />
      doc.add(Field.UnIndexed("path", args[i]));<br />
      doc.add(Field.Text("body", (Reader) new InputStreamReader(is)));<br />
      //将文档写入索引<br />
      writer.addDocument(doc);<br />
      is.close();<br />
    };<br />
    //关闭写索引器<br />
    writer.close();<br />
  }<br />
}<br />

 

    或者,Lucene全文检索引擎也可作为服务器程序启动,但是这就需要用户自行扩充其他应用与Lucene的接口。这个可以通过传统的包装方式,比如客户服务器结构,或者采用现在流行的Web方式。诸如此类的应用方案,本文也不再继续叙述。参考Lucene的项目网站中的用户邮件列表能找到更多的信息。

 

 

第三节 Lucene索引文件格式分析

 

一、             Lucene源码实现分析的说明

 

通过以上对Lucene系统结构的分析,我们已经大致的清楚了Lucene系统的组成,以及在Lucene系统之上的开发步骤。接下来,我们试图来分析Lucene项目(采用Lucene 1.2版本)的源码实现,考察其实现的细节。这不仅仅是我们尝试用C++语言重新实现Lucene的必须工作,也是进一步做Lucene开发工作的必要准备。因此,这一部分所涉及到的内容,对于Lucene上的应用开发也是有价值的,尤其是本部分所做的文件格式分析。

 

    由于本文建立在我们的毕设项目之上,且同时我们需要实现cLucene项目,因此很遗憾的我们并没有完全的完成Lucene的所有源码实现的分析工作。接下来的部分,我们将涉及的部分为Lucene文件格式分析,Lucene中的存储抽象模块分析,以及Lucene中的索引构建逻辑模块分析。这一部分,我们主要涉及到的是文件格式分析与存储抽象模块分析。

 

二、             Lucene索引文件格式

 

Luceneweb站点上,有关于Lucene的文件格式的规范,其规定了Lucene的文件格式采取的存储单位、组织结构、命名规范等等内容,但是它仅仅是一个规范说明,并没有从实现者角度来衡量这个规范的实现。因此,我们以下的内容,结合了我们自己的分析与文件格式的定义规范,以期望给出一个更加清晰的文件格式说明。具体的文档规范可以参考后面的文献2

 

    首先在Lucene的文件格式中,以字节为基础,定义了如下的数据类型:

 

3.1 Lucene文件格式中定义的数据类型

数据类型

所占字节长度(字节)

说明

Byte

1

基本数据类型,其他数据类型以此为基础定义

UInt32

4

32位无符号整数,高位优先

UInt64

8

64位无符号整数,高位优先

VInt

不定,最少1字节

动态长度整数,每字节的最高位表明还剩多少字节,每字节的低七位表明整数的值,高位优先。可以认为值可以为无限大。其示例如下

字节1

字节2

字节3

0

00000000

 

 

1

00000001

 

 

2

00000010

 

 

127

01111111

 

 

128

10000000

00000001

 

129

10000001

00000001

 

130

10000010

00000001

 

16383

10000000

10000000

00000001

16384

10000001

10000000

00000001

16385

10000010

10000000

00000001

Chars

不定,最少1字节

采用UTF-8编码[20]Unicode字符序列

String

不定,最少2字节

VIntChars组成的字符串类型,VInt表示Chars的长度,Chars则表示了String的值

 

    以上的数据类型就是Lucene索引文件格式中用到的全部数据类型,由于它们都以字节为基础定义而来,因此保证了是平台无关,这也是Lucene索引文件格式平台无关的主要原因。接下来我们看看Lucene索引文件的概念组成和结构组成。

    以上就是Lucene的索引文件的概念结构。Lucene索引index由若干段(segment)组成,每一段由若干的文档(document)组成,每一个文档由若干的域(field)组成,每一个域由若干的项(term)组成。项是最小的索引概念单位,它直接代表了一个字符串以及其在文件中的位置、出现次数等信息。域是一个关联的元组,由一个域名和一个域值组成,域名是一个字串,域值是一个项,比如将“标题”和实际标题的项组成的域。文档是提取了某个文件中的所有信息之后的结果,这些组成了段,或者称为一个子索引。子索引可以组合为索引,也可以合并为一个新的包含了所有合并项内部元素的子索引。我们可以清楚的看出,Lucene的索引结构在概念上即为传统的倒排索引结构[21]

 

    从概念上映射到结构中,索引被处理为一个目录(文件夹),其中含有的所有文件即为其内容,这些文件按照所属的段不同分组存放,同组的文件拥有相同的文件名,不同的扩展名。此外还有三个文件,分别用来保存所有的段的记录、保存已删除文件的记录和控制读写的同步,它们分别是segmentsdeletablelock文件,都没有扩展名。每个段包含一组文件,它们的文件扩展名不同,但是文件名均为记录在文件segments中段的名字。让我们看如下的结构图3.2



项集合信息

 



项位置

 

流程图:文档: segment1.frq


项频数

 



被删除文档

 

流程图:文档: segment1.del


标准化因子

 

流程图:文档: segment1.tis流程图:文档: segment1.tii流程图:文档: segment1.prx流程图:文档: segment1.nrm


3.2 Lucene索引文件结构组成

 



segment1所含文件

 



项字典

 



域值存储表

 



域集合信息

 

流程图:文档: segment1.fdt流程图:文档: segment1.fdx流程图:文档: segment1.fnm


index

 

流程图:文档: segments流程图:文档: deletable流程图:文档: lock流程图:多文档: segment1

 

    关于图3.2中的各个文件具体的内部格式,在参考文献3中,均可以找到详细的说明。接下来我们从宏观关系上说明一下这些文件组成。在这些宏观上的关系理清楚之后,仔细阅读参考文献3,即可清楚的明白具体的Lucene文件格式。

 

    每个段的文件中,主要记录了两大类的信息:域集合与项集合。这两个集合中所含有的文件在图3.2中均有表明。由于索引信息是静态存储的,域集合与项集合中的文件组采用了一种类似的存储办法:一个小型的索引文件,运行时载入内存;一个对应于索引文件的实际信息文件,可以按照索引中指示的偏移量随机访问;索引文件与信息文件在记录的排列顺序上存在隐式的对应关系,即索引文件中按照“索引项1、索引项2…”排列,则信息文件则也按照“信息项1、信息项2…”排列。比如在图3.2所示文件中,segment1.fdxsegment1.fdt之间,segment1.tiisegment1.tissegment1.prxsegment1.frq之间,都存在这样的组织关系。而域集合与项集合之间则通过域的在域记录文件(比如segment1.fnm)中所记录的域记录号维持对应关系,在图3.2segment1.fdxsegment1.tii中就是通过这种方式保持联系。这样,域集合和项集合不仅仅联系起来,而且其中的文件之间也相互联系起来。此外,标准化因子文件和被删除文档文件则提供了一些程序内部的辅助设施(标准化因子用在评分排序机制中,被删除文档是一种伪删除手段)。这样,整个段的索引信息就通过这些文档有机的组成。

 

    以上所阐述的,就是Lucene所采用的索引文件格式。基本上而言,它是一个倒排索引,但是Lucene在文件的安排上做了一些努力,比如使用索引/信息文件的方式,从文件安排的形式上提高查找的效率。这是一种数据库之外的处理方法,其有其优点(格式平台独立、速度快),也有其缺点(独立性带来的共享访问接口问题等等),具体如何衡量两种方法之间的利弊,本文这里就不讨论了。

 

三、             一些公用的基础类

 

分析完索引文件格式,我们接下来应该着手对存储抽象也就是org.apache.lucenestore中的源码做一些分析。我们先不着急分析这部分,而是分析图2.1中基础结构封装那一部分,因为这是整个系统的基石,然后我们在下一部分再来分析存储抽象。

 

    基础结构封装,或者基础类,由org.apache.lucene.utilorg.apache.lucene.document两个包组成,前者定义了一些常量和优化过的常用的数据结构和算法,后者则是对于文档(document)和域(field)概念的一个类定义。以下我们用列表的方式来分析这些封装类,指出其要点。

 

3.2 基础类包org.apache.lucene.util

说明

Arrays

一个关于数组的排序方法的静态类,提供了优化的基于快排序的排序方法sort

BitVector

C/C++语言中位域的java实现品,但是加入了序列化能力

Constants

常量静态类,定义了一些常量

PriorityQueue

一个优先队列的抽象类,用于后面实现各种具体的优先队列,提供常数时间内的最小元素访问能力,内部实现机制是哈析表和堆排序算法

 

3.3 基础类包org.apache.lucene.document

说明

Document

是文档概念的一个实现类,每个文档包含了一个域表(fieldList),并提供了一些实用的方法,比如多种添加域的方法、返回域表的迭代器的方法

Field

是域概念的一个实现类,每个域包含了一个域名和一个值,以及一些相关的属性

DateField

提供了一些辅助方法的静态类,这些方法将javaDateTime数据类型和String相互转化

 

总的来说,这两个基础类包中含有的类都比较简单,通过阅读源代码,可以很容易的理解,因此这里不作过多的展开。

 

四、             存储抽象

 

有了上面的知识,我们接下来来分析存储抽象部分,也就是org.apache.lucene.store包。存储抽象是唯一能够直接对索引文件存取的包,因此其主要目的是抽象出和平台文件系统无关的存储抽象,提供诸如目录服务(增、删文件)、输入流和输出流。在分析其实现之前,首先我们看一下UML[22]图。

3.3 存储抽象实现UML图(一)

3.4 存储抽象实现UML图(二)

3.4 存储抽象实现UML图(三)

 

    3.23.4展示了整个org.apache.lucene.store中主要的继承体系。共有三个抽象类定义:DirectoryInputStreamOutputStrem,构成了一个完整的基于抽象文件系统的存取体系结构,在此基础上,实作出了两个实现品:(FSDirectoryFSInputStreamFSOutputStream)和(RAMDirectoryRAMInputStreamRAMOutputStream)。前者是以实际的文件系统做为基础实现的,后者则是建立在内存中的虚拟文件系统。前者主要用来永久的保存索引文件,后者的作用则在于索引操作时是在内存中建立小的索引,然后一次性的输出合并到文件中去,这一点我们在后面的索引逻辑部分能够看到。此外,还定以了org.apache.lucene.store.lockorg.apache.lucene.store.with两个辅助内部实现的类用在实现Directory方法的makeLock的时候,以在锁定索引读写之前来让客户程序做一些准备工作。

 

    FSDirectoryFSInputStreamFSOutputStream)的内部实现依托于java语言中的io类库,只是简单的做了一个外部逻辑的包装。这当然要归功于java语言所提供的跨平台特性,同时也带了一些隐患:文件存取的效率提升需要依耐于文件类库的优化。如果需要继续优化文件存取的效率,应该还提供一个文件与目录的抽象,以根据各种文件系统或者文件类型来提供一个优化的机会。当然,这是应用开发者所不需要关系的问题。

 

    RAMDirectoryRAMInputStreamRAMOutputStream)的内部实现就比较直接了,直接采用了虚拟的文件RAMFile类(定义于文件RAMDirectory.java中)来表示文件,目录则看作一个StringRAMFile对应的关联数组。RAMFile中采用数组来表示文件的存储空间。在此的基础上,完成各项操作的实现,就形成了基于内存的虚拟文件系统。因为在实际使用时,并不会牵涉到很大字节数量的文件,因此这种设计是简单直接的,也是高效率的。

 

    这部分的实现在理清楚继承体系后,相当的简单。因此接下来的部分,我们可以通过直接阅读源代码解决。接下来我们看看这个部分的源代码如何在实际中使用的。

 

    一般来说,我们使用的是抽象类提供的接口而不是实际的实现类本身。在实现类中一般都含有几个静态函数,比如createFile,它能够返回一个OutputStream接口,或者openFile,它能够返回一个InputStream接口,利用这些接口之中的方法,比如writeStringwriteByte等等,我们就能够在抽象的层次上处理Lucene定义的数据类型的读写。简单的说,Lucene中存储抽象这部分设计时采用了工厂模式(Factory parttern[23]。我们利用静态类的方法也就是工厂来创建对象,返回接口,通过接口来执行操作。

 

五、             关于cLucene项目

 

这一部分详细的说明了Lucene系统中所采用的索引文件格式、一些基础类和存储抽象。接下来我们来叙述一下我们在项目cLucene中重新实现这些结构时候的一些考虑。

 

    cLucene彻底的遵守了Lucene所定义的索引文件格式,这是Lucene对于各个兼容系统的基本要求。在此基础上,cLucene系统和Lucene系统才能够共享索引文件数据。或者说,cLucene生成的索引文件和Lucene生成的索引文件完全等价。

 

    在基础类问题上,cLucene同样封装了类似的结构。我们同样列表描述,请和前面的表3.23.3对照比较。

3.4 基础类包cLucene::util

说明

Arrays

没有实现,直接利用了STL库中的快排序算法实现

BitVector

C/C++语言版本的实现,与java实现版本类似

Constants

常量静态类,定义了一些常量,但是与java版本不同的是,这里主要定义了一些宏

PriorityQueue

这是一个类型定义,直接利用STL库中的std::priority_queue

 

3.3 基础类包cLucene::document

说明

Document

C/C++语言版本的实现,与java实现版本类似

Field

C/C++语言版本的实现,与java实现版本类似

DateField

没有实现,直接利用OpenTop库中的ot::StringUtil

 

    存储抽象的实现上,也同样是类似于java实现。由于我们采用了OpenTop库,因此同样得以借助其中对于文件系统抽象的ot::io包来解决文件系统问题。这部分问题与前面一样,存在优化的可能。在实现的类层次上、对外接口上,均与java版本的一样。

 

 

第四节 Lucene索引构建逻辑模块分析

 

一、             绪论

 

这一个部分,我们将分析Lucene中的索引构建逻辑模块。它与前面介绍的存储抽象一起构成了Lucene的索引核心部分。无论是对外接口中的查询,还是分析各种文本以进一步生成索引,都需要直接调用这部分来获得对索引文件的访问能力,因此,这部分在系统中至关重要。构建一个高效的、易使用的索引构建逻辑,即是Lucene在这一部分需要达到的目的。

 

    从面向对象的经典思考方式出发来看,我们只需要使用继承体系来表达图3.1中的各个概念,就可以通过这个继承体系来控制索引文件的结构,然后设计合适的永久化方法,以及接受分析token流的操作,即可将索引构建逻辑完成。原理上就是这样的简单。由于两个关键的概念documentfield都已经在org.apache.lucene.document中当作基础类定义过了,因此实际上Lucene在这部分需要完善的概念结构还有segmentterm。在此基础上继续编写各个逻辑结构的永久化方法,然后提供一个进入的接口方法,即是宣告完成了这个过程。其中永久化的部分,Lucene使用了另外实现一个代理类的方式来实现,即对于某个类X,存在XWriter类和XReader类来负责写出和读入的功能;用作永久化功能的类是被永久化的类的友元。

 

    在接下来的分析过程中,我们按照这样一个思路,以UML图和对象体系的描述来叙述这部分的设计和实现,然后通过内部的数据流理清楚调用时序。

 

二、             对象体系与UML

 

1.  项(Term

 

这部分主要是分析针对项(Term)这个概念所做的设计,包括概念所实际涉及的类、永久化类。首先,我们从图3.2和阅读参考文献3知道,项(Term)所表示的是一个字符串,它拥有域、频数和位置信息等等属性。因此,Lucene中设计了两个类来表示这个概念,如下图

4.1 UML图(-)

 

上图中,有意的突出了类TermTermInfo中的数据成员,因为它反映了对于项(Term)这个概念的具体表示。同时上图中也同时列出了用于永久化项(Term)的代理类TermInfosWriterTermInfosReader,它们完成永久化的功能,需要注意的是,TermInfosReader内部使用了数组indexTermsindexInfos来存储一系列项;而TermInfosWriter则是一个类似于链表的结构,通过一个other指向下一个TermInfosWriter,每一个TermInfosWriter只负责本身那个lastTermlastTi的永久化工作。这是一个设计上的技巧,通过批量读取(或者称为缓冲的方式)来获得读入时候的效率优化;而通过一个链表式的、各负其责的方式,来获得写出时候的设计简化。

 

项(term)这部分的设计中,还有一些重要的接口和类,我们先介绍如下,同样我们也先展示UML

4.2 UML图(二)

 

4.2中,我们看到三个类:TermEnumTermDocsTermPositions,第一个是抽象类,后两个都是接口。TermEnum的设计主要用在后面SegmentDocument等等的实现中,以提供枚举其中每一个项(Term)的能力。TermDocs是一个接口,用来继承以提供返回<document, frequency>值对的能力,通过这个接口就可以获得某个项(Term)在某个文档中出现的频数。TermPositions则是在TermDocs上的扩展,将项(Term)在文档中的位置信息也表示出来。TermDocsTermPositions)接口的使用方式类似于java中的Enumration接口,即通过next方法跳转,通过docfreq等方法获得当前的属性值。

 

2.  域(Field

 

由于Field的基本概念在org.apache.lucene.document中已经做了定义,因此在这部分主要是针对项文件(.fnm文件、.fdx文件、.fdt文件)所需要的信息再来设计一些类。

4.3 UML图(三)

 

4.3中展示的,就是表示与域(Field)所关联的属性信息的类。其中isIndexed表示的这个域的值是否被索引过,即值是否被分词然后索引;另外两个属性所表示的意思则很明显:一个是域的名字,一个是域的编号。

 

接下来我们来看关于域表和存取逻辑的UML图。

4.4 UML图(四)

FieldInfos即为域表的概念表示,内部采用了冗余的方式以获取在通过域的编号访问或者通过域的名字来访问时候的高效率。FieldsReaderFieldsWriter则分别是写出和读入的代理类。在功能和实现上,这两个类都比较简单。至于FieldInfos中采用的冗余方式,则是基于域的数目相对比较少而做出的一种折衷处理。

 

3.  文档(document

 

文档(document)同样也是在org.apache.lucene.document中定义过的结构。由于对于这部分比较重要,我们也来看看其UML图。

4.5 UML图(五)

 

在图4.5中我们看到,Document的设计基本上沿用了链表的处理方法。左边的Document类作为一个数据外包类,用来提供对于内部结构DocumentFieldList的增加删除访问操作等等。DocumentFieldList才是实际上的数据存储单位,它用了链表的处理方法,直接指向一个当前的Field对象和下一个DocumentFieldList对象,这个与前面的类似。为了能够逐个访问链表中的节点,还设计了DocumentFieldEnumeration枚举类。

4.6 UML图(六)

    实际上定义于org.apache.lucene.index中的有关于Document的就是永久化的代理类。在图4.6中给出了其UML图。需要说明的是为什么没有出现读入的方法:这个方法已经隐含在图4.5Document类中的add方法中了,结合图2.4中的程序代码段,我们就能够清楚的理解这种设计。

 

4.  段(segment

 

段(Segment)这一部分设计的比较特殊,在实现简单的对象结构之上,还特意的设计了用于段之间合并的类。接下来,我们仍然采取对照UML分析的方式逐个叙述。接下来我们看Lucene中如何表示段这个概念。

4.7 UML图(七)

Lucene定义了一个类SegmentInfo用来表示每一个段(Segment)的信息,包括名字(name)、含有的文档的数目(docCount)和段所位于的目录的位置(dir)。根据索引文件中的段的意义,有了这三点,就能唯一确定一个段了。SegmentInfos这个类则是用来表示一个段的链表(从标准的java.util.Vector继承而来),实际上,也就是索引(index)的意思了。需要注意的是,这里并没有在SegmentInfo中安插一个文档(document)的链表。这样做的原因牵涉到Lucene内部对于文档(相当于一个被索引文件)的处理;Lucene内部采用了赋予文档编号,给域赋值的方式来处理文档,即加入的文档顺次编号,以后用文档号表示文档,而路径信息,文件名字等等在以后索引查找需要的属性,都作为域存储下来;因此SegmentInfo中并没有另外存储一个文档(document)的链表,对于这些的写出和读入,则交给了永久化的代理类来做。

 

4.8 UML图(八)

4.8给出了负责段(segment)的读入操作的代理类,而负责段(segment)的写出操作也同样没有定义,这些操作都直接实现在了类IndexWriter类中(后面会详细分析)。段的操作同样采用了之前的数组或者说是缓冲的处理方式,相关的细节也不在这里详细叙述了。

 

然后,针对前面项(term)那部分定义的几个接口,段(segment)这部分也需要做相应的接口实现,因为提供直接遍历访问段中的各个项的能力对于检索来说,无疑是十分重要的。即这部分的设计,实际上都是在为了检索在服务。

4.9 UML图(九)

 

4.10 UML图(十)

4.9和图4.10分别展示了前面项(term)那里定义的接口是如何在这里通过继承实现的。Lucene在处理这部分的时候,也是分成两部分(SegmentSegments开头的类)来实现,而且很合理的运用了数组的技法,以及注意了继承重用。但是细化到局部,终归是比较简单的按照语义来获得结果而已了,因此关于更多的也就不多做分析了,我们完全可以通过阅读源代码来解决。

 

接下来所介绍的,就是在Lucene的设计过程中比较特殊的一个部分:段合并类(SegmentMerger)。这首先需要介绍Lucene中的建立索引时的段合并策略。

 

Lucene为了兼顾建立索引时的效率和读取索引查找的速度,引入了分小段建立索引的方式,即每一次批量建立索引时,先在内存中的虚拟文件系统中为每一个文档单独建立一个段,然后在输出的时候将这些段合并之后输出成为索引文件,这时仅仅存在一个段。多次建立的索引后,如果想优化索引文件,也可采取合并段的方法,将索引中的段合并成为一个段。我们来看一下在IndexWriter类中相应的方法的实现,来了解一下这中建立索引的实现。

    对于上面的代码,我们不做过多注释了,结合源码中的注解应该很容易理解。在最后那个mergeSegments函数中,将用到几个重要的类结构,它们记录了合并时候的一些重要信息,完成合并时候的工作。接下来,我们来看这几个类的UML图。

4.12 UML图(十一)

从图4.12中,我们看到Lucene设计一个类SegmentMergeInfo用来保存每一个被合并的段的信息,也保存能够访问其内部的接口句柄,也就是说合并时的操作使用这个类作为对被合并的段的操作代理。类SegmentMergeQueue则设计为org.apache.lucene.util.PriorityQueue的子类,做为SegmentMergeInfo的容器类,而且附带能够自动排序。SegmentMerger是主要进行操作的类,里面各个方法环环相扣,分别完成合并各个数据项的问题。

 

5.  IndexReader类与IndexWirter

 

最后剩下的,就是整个索引逻辑部分的使用接口类了。外界通过这两个类以及文档(document)类的构造函数调用之,比如图2.4中的代码示例所示。下面我们来看一下这部分最后两个类的UML图。

4.13 UML图(十二)

 

    IndexWriter的设计与IndexReader的设计很不相同,前者是一个实现类,而后者是一个抽象类,带有没有实现的接口。IndexWriter的主要作用就是接收新加入的文档(document),然后在内部为之生成相应的小段,最后再合并并向索引文件中输出,图4.11中已经给出了一些实现的代码。由于Lucene在面向对象上封装的努力,通过各个构造函数就已经完成了对于各个概念的构造过程,剩下部分的代码主要是依据各个数组或者是链表中的信息,逐个逐个的将信息写出到相应的文件中去了。IndexReader部分则只是做了接口设计,没有具体的实现,这个和本部分所完成的主要功能有关:索引构建逻辑。设计这个抽象类的目的是,预先完成一些函数,为以后的检索(search)部分的各种形式的IndexReader铺平道路,也是利用了在同一个包内可以方便访问其它类的保护变量这个java语言的限制。

 

    到此,在索引构建逻辑部分出现的类我们就分析完毕了,需要说明主要是做的一个宏观上的组成结构上的分析,并指出一些实现上的要点。具体的实现,由于Lucene的开放源码而显得并不是非常的重要,因为Lucene在做到良好的面相对象设计之后,实际带来的是局部复杂性的减小,因此某一些单独的函数或者实现就比较容易编写,也容易让人阅读。本文不再继续叙述这方面的细节,作为一个总结,下一个部分我们通过索引构建逻辑的数据流图的方式,再来理清楚一下索引构建逻辑这部分的调用时序。

 

三、             数据流逻辑

 

 

从宏观上明白一个系统的设计,理清楚其中的运行规律,最好的方式应该是通过数据流图。在分析了各个位于索引构建逻辑部分的类的设计之后,我们接下来就通过分析数据流图的方式来总结一下。但是由于之前提到的原因:索引读入部分在这一部分并没有完全实现,所以我们在数据流图中主要给出的是索引构建的数据流图。

 



4.14 索引构建部分的数据流逻辑

 



合并输出

 



字节流输入

 



内存文件系统

 

文本框: 顺次调用流程图:多文档: 索引文件文本框: 索引构建阶段


writeNorms写出标准化因子

 



sortPostingTable排序位置信息

 



writePostings写出索引信息

 



invertDocument分析文档

 



addDocument生成小段

 



加入document对象

 



document对象方式传入

 

文本框: 准备阶段


调用

 



生成field对象,根据对象性质不同,为值赋予String值,或者是Reader

 



生成document对象,调用add方法加入field对象

 



通过java语言的io类以输入流方式传入

 

流程图:多文档: 被索引文件

 

对于图4.14中所描述的内容,结合Lucene源代码中的一些文件看,能够加深理解。准备阶段可以参考demo文件夹中的org.apache.lucene.demo.IndexFiles类和java文件夹中的org.apache.lucene.document文件包。索引构建阶段的主要源码位于java文件夹中org.apache.lucene.index.IndexWriter类,因此这部分可以结合这个类的实现来看。至于内存文件系统,比较复杂,但是这时的逻辑相对简单,因此也不难理解。

 

    上面的数据流图十分清楚的勾画除了整个索引构建逻辑这部分的设计:通过层层嵌套的类结构,在构建时候即分步骤有计划的生成了索引结构,将之存储到内存中的文件系统中,然后通过对内存中的文件系统优化合并输出到实际的文件系统中。

 

四、             关于cLucene项目

 

前面的三个部分,已经完成了分析索引构建逻辑的任务,这里我们还是有针对性的谈谈我们这次的毕业设计项目cLucene在这一部分的情况。

 

在实现这部分的时候,为了将一些java语法中比较特殊的部分,比如内隐类、同步函数、同步对象等等,我们不得不采用了一些比较晦涩和艰深的C++语法,在OpenTop这个类库所提供的类似于java语言的设施上来实现。这个尤其体现在实现Segment相关类时,为了处理原来java源代码中用内隐类实现的Lock文件创建机制的时候,我们不得不定义了大量的cLucene::store::With的子类,并为之传入调用类的指针,设置它为调用类的友元,才得以精确的模拟了原有的语义。陷于我们这次的重写以移植为主,系统结构基本上没有大的变化,不得不产生这种重复而且大量的工作。如果需要改进这中状况,我们应该考虑按照C++语言的特点来设计索引构建部分的类库继承结构,但是很可惜在本文成文之前,时间不允许我们这样做。

 

来自java语法的特殊性只是我们解决问题的一个方面,我们还需要处理引用的调用方式。由于java语言拥有了垃圾收集机制,因此得以将一切的参数形式看作为引用,而不考虑其分配与消亡的问题。C++语言并不具备这种机制,它需要程序员自行管理分配空间与销毁对象的问题。在这里,我们使用的是来自OpenTop中所引入的计数指针RefPtr<>模板,它能够模拟指针的语义,并且计算指针被引用的次数,在引用次数为0时就自动释放资源:这是一种类似于java语言中引用的方式,不过它显得更加高效率。我们在cLucene的实现中大量的使用了计数指针模板。

 

    除此之外,我们没有改变Lucene所定义的索引构建逻辑的结构和语义,我们实现的是一个完全和java版本Lucene兼容的版本。

 

 

 

 

 

 

 

 

 

 

 

 

第一节 全文检索系统与Lucene简介

 

一、             什么是全文检索与全文检索系统?

 

全文检索是指计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。这个过程类似于通过字典中的检索字表查字的过程。

 

全文检索的方法主要分为按字检索和按词检索两种。按字检索是指对于文章中的每一个字都建立索引,检索时将词分解为字的组合。对于各种不同的语言而言,字有不同的含义,比如英文中字与词实际上是合一的,而中文中字与词有很大分别。按词检索指对文章中的词,即语义单位建立索引,检索时按词检索,并且可以处理同义项等。英文等西方文字由于按照空白切分词,因此实现上与按字处理类似,添加同义处理也很容易。中文等东方文字则需要切分字词,以达到按词索引的目的,关于这方面的问题,是当前全文检索技术尤其是中文全文检索技术中的难点,在此不做详述。

 

全文检索系统是按照全文检索理论建立起来的用于提供全文检索服务的软件系统。一般来说,全文检索需要具备建立索引和提供查询的基本功能,此外现代的全文检索系统还需要具有方便的用户接口、面向WWW[1]的开发接口、二次应用开发接口等等。功能上,全文检索系统核心具有建立索引、处理查询返回结果集、增加索引、优化索引结构等等功能,外围则由各种不同应用具有的功能组成。结构上,全文检索系统核心具有索引引擎、查询引擎、文本分析引擎、对外接口等等,加上各种外围应用系统等等共同构成了全文检索系统。图1.1展示了上述全文检索系统的结构与功能。

 

在上图中,我们看到:全文检索系统中最为关键的部分是全文检索引擎,各种应用程序都需要建立在这个引擎之上。一个全文检索应用的优异程度,根本上由全文检索引擎来决定。因此提升全文检索引擎的效率即是我们提升全文检索应用的根本。另一个方面,一个优异的全文检索引擎,在做到效率优化的同时,还需要具有开放的体系结构,以方便程序员对整个系统进行优化改造,或者是添加原有系统没有的功能。比如在当今多语言处理的环境下,有时需要给全文检索系统添加处理某种语言或者文本格式的功能,比如在英文系统中添加中文处理功能,在纯文本系统中添加XML[2]或者HTML[3]格式的文本处理功能,系统的开放性和扩充性就十分的重要。

 

二、             什么是Lucene

 

Luceneapache软件基金会[4] jakarta项目组的一个子项目,是一个开放源代码[5]的全文检索引擎工具包,即它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎(英文与德文两种西方语言)。Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。

 

Lucene的原作者是Doug Cutting,他是一位资深全文索引/检索专家,曾经是V-Twin搜索引擎[6]的主要开发者,后在Excite[7]担任高级系统架构设计师,目前从事于一些Internet底层架构的研究。早先发布在作者自己的http://www.lucene.com/,后来发布在SourceForge[8]2001年年底成为apache软件基金会jakarta的一个子项目:http://jakarta.apache.org/lucene/

 

三、             Lucene的应用、特点及优势

 

作为一个开放源代码项目,Lucene从问世之后,引发了开放源代码社群的巨大反响,程序员们不仅使用它构建具体的全文检索应用,而且将之集成到各种系统软件中去,以及构建Web应用,甚至某些商业软件也采用了Lucene作为其内部全文检索子系统的核心。apache软件基金会的网站使用了Lucene作为全文检索的引擎,IBM的开源软件eclipse[9]2.1版本中也采用了Lucene作为帮助子系统的全文索引引擎,相应的IBM的商业软件Web Sphere[10]中也采用了LuceneLucene以其开放源代码的特性、优异的索引结构、良好的系统架构获得了越来越多的应用。

 

Lucene作为一个全文检索引擎,其具有如下突出的优点:

1)索引文件格式独立于应用平台。Lucene定义了一套以8位字节为基础的索引文件格式,使得兼容系统或者不同平台的应用能够共享建立的索引文件。

2)在传统全文检索引擎的倒排索引的基础上,实现了分块索引,能够针对新的文件建立小文件索引,提升索引速度。然后通过与原有索引的合并,达到优化的目的。

3)优秀的面向对象的系统架构,使得对于Lucene扩展的学习难度降低,方便扩充新功能。

4)设计了独立于语言和文件格式的文本分析接口,索引器通过接受Token流完成索引文件的创立,用户扩展新的语言和文件格式,只需要实现文本分析的接口。

5)已经默认实现了一套强大的查询引擎,用户无需自己编写代码即使系统可获得强大的查询能力,Lucene的查询实现中默认实现了布尔操作、模糊查询(Fuzzy Search[11])、分组查询等等。

 

    面对已经存在的商业全文检索引擎,Lucene也具有相当的优势。首先,它的开发源代码发行方式(遵守Apache Software License[12]),在此基础上程序员不仅仅可以充分的利用Lucene所提供的强大功能,而且可以深入细致的学习到全文检索引擎制作技术和面相对象编程的实践,进而在此基础上根据应用的实际情况编写出更好的更适合当前应用的全文检索引擎。在这一点上,商业软件的灵活性远远不及Lucene。其次,Lucene秉承了开放源代码一贯的架构优良的优势,设计了一个合理而极具扩充能力的面向对象架构,程序员可以在Lucene的基础上扩充各种功能,比如扩充中文处理能力,从文本扩充到HTMLPDF[13]等等文本格式的处理,编写这些扩展的功能不仅仅不复杂,而且由于Lucene恰当合理的对系统设备做了程序上的抽象,扩展的功能也能轻易的达到跨平台的能力。最后,转移到apache软件基金会后,借助于apache软件基金会的网络平台,程序员可以方便的和开发者、其它程序员交流,促成资源的共享,甚至直接获得已经编写完备的扩充功能。最后,虽然Lucene使用Java语言写成,但是开放源代码社区的程序员正在不懈的将之使用各种传统语言实现(例如.net framework[14]),在遵守Lucene索引文件格式的基础上,使得Lucene能够运行在各种各样的平台上,系统管理员可以根据当前的平台适合的语言来合理的选择。

 

四、             本文的重点问题与cLucene项目

 

作为中国人民大学信息学院99级本科生的一个毕业设计项目,我们对Lucene进行了深入的研究,包括系统的结构,索引文件结构,各个部分的实现等等。并且我们启动了cLucene项目,做为一个LuceneC++语言的重新实现,以期望带来更快的速度和更加广泛的应用范围。我们先分析了系统结构,文件结构,然后在研究各个部分的具体实现的同时开始进行的cLucene实现。限于时间的限制,到本文完成为止,cLucene项目并没有完成,对于Lucene的具体实现部分也仅仅完成到了索引引擎部分。

 

接下来的部分,本文将对Lucene的系统结构、文件结构、索引引擎部分做一个彻底的分析。以期望提供对Lucene全文检索引擎的系统架构和部分程序实现的清晰的了解。cLucene项目则作为一个开放源代码的项目,继续进行的开发。

 

       有关cLucene项目的一些信息:

n         开发语言:ISO C++[15]STLport 4.5.3[16]OpenTop 1.1[17]

n         目标平台:Win32POSIX

n         授权协议:GNU General Public License (GPL)[18]

 

 

第二节 Lucene系统结构分析

 

一、             系统结构组织

 

Lucene作为一个优秀的全文检索引擎,其系统结构具有强烈的面向对象特征。首先是定义了一个与平台无关的索引文件格式,其次通过抽象将系统的核心组成部分设计为抽象类,具体的平台实现部分设计为抽象类的实现,此外与具体平台相关的部分比如文件存储也封装为类,经过层层的面向对象式的处理,最终达成了一个低耦合高效率,容易二次开发的检索引擎系统。

 

以下将讨论Lucene系统的结构组织,并给出系统结构与源码组织图:

 

    从图中我们清楚的看到,Lucene的系统由基础结构封装、索引核心、对外接口三大部分组成。其中直接操作索引文件的索引核心又是系统的重点。Lucene的将所有源码分为了7个模块(在java语言中以包即package来表示),各个模块所属的系统部分也如上图所示。需要说明的是org.apache.lucene.queryPaser是做为org.apache.lucene.search的语法解析器存在,不被系统之外实际调用,因此这里没有当作对外接口看待,而是将之独立出来。

 

    从面象对象的观点来考察,Lucene应用了最基本的一条程序设计准则:引入额外的抽象层以降低耦合性。首先,引入对索引文件的操作org.apache.lucene.store的封装,然后将索引部分的实现建立在(org.apache.lucene.index)其之上,完成对索引核心的抽象。在索引核心的基础上开始设计对外的接口org.apache.lucene.searchorg.apache.lucene.analysis。在每一个局部细节上,比如某些常用的数据结构与算法上,Lucene也充分的应用了这一条准则。在高度的面向对象理论的支撑下,使得Lucene的实现容易理解,易于扩展。

 

    Lucene在系统结构上的另一个特点表现为其引入了传统的客户端服务器结构以外的的应用结构。Lucene可以作为一个运行库被包含进入应用本身中去,而不是做为一个单独的索引服务器存在。这自然和Lucene开放源代码的特征分不开,但是也体现了Lucene在编写上的本来意图:提供一个全文索引引擎的架构,而不是实现。

 

二、             数据流分析

 

理解Lucene系统结构的另一个方式是去探讨其中数据流的走向,并以此摸清楚Lucene系统内部的调用时序。在此基础上,我们能够更加深入的理解Lucene的系统结构组织,以方便以后在Lucene系统上的开发工作。这部分的分析,是深入Lucene系统的钥匙,也是进行重写的基础。

 

   我们来看看在Lucene系统中的主要的数据流以及它们之间的关系图:



索引查找逻辑

 



索引构建逻辑

 



查询语句语法分析逻辑

 



词法分析逻辑

 

流程图:文档: 查询结果流程图:顺序访问存储器: 查询语句


存储抽象

 

流程图:多文档: 索引文件流程图:多文档: 被索引文件

 

    2.2很好的表明了Lucene在内部的数据流组织情况,并且沿着数据流的方向我们也可以对与Lucene内部的执行时序有一个清楚的了解。现在将图中的涉及到的流的类型与各个逻辑对应系统的相关部分的关系说明一下。

 

    图中共存在4种数据流,分别是文本流、token流、字节流与查询语句对象流。文本流表示了对于索引目标和交互控制的抽象,即用文本流表示了将要索引的文件,用文本流向用户输出信息;在实际的实现中,Lucene中的文本流采用了UCS-2[19]作为编码,以达到适应多种语言文字的处理的目的。Token流是Lucene内部所使用的概念,是对传统文字中的词的概念的抽象,也是Lucene在建立索引时直接处理的最小单位;简单的讲Token就是一个词和所在域值的组合,后面在叙述文件格式时也将继续涉及到token,这里不详细展开。字节流则是对文件抽象的直接操作的体现,通过固定长度的字节(Lucene定义为8比特位长,后面文件格式将详细叙述)流的处理,将文件操作解脱出来,也做到了与平台文件系统的无关性。查询语句对象流则是仅仅在查询语句解析时用到的概念,它对查询语句抽象,通过类的继承结构反映查询语句的结构,将之传送到查找逻辑来进行查找的操作。

 

    图中的涉及到了多种逻辑,基本上直接对应于系统某一模块,但是也有跨模块调用的问题发生,这是因为Lucene的重用程度非常好,因此很多实现直接调用了以前的工作成果,这在某种程度上其实是加强了模块耦合性,但是也是为了避免系统的过于庞大和不必要的重复设计的一种折衷体现。词法分析逻辑对应于org.apache.lucene.analysis部分。查询语句语法分析逻辑对应于org.apache.lucene.queryParser部分,并且调用了org.apache.lucene.analysis的代码。查询结束之后向评分排序逻辑输出token流,继而由评分排序逻辑处理之后给出文本流的结果,这一部分的实现也包含在了org.apache.lucene.search中。索引构建逻辑对应于org.apache.lucene.index部分。索引查找逻辑则主要是org.apache.lucene.search,但是也大量的使用了org.apache.lucene.index部分的代码和接口定义。存储抽象对应于org.apache.lucene.store。没有提到的模块则是做为系统公共基础设施存在。

 

三、             基于Lucene的应用开发

 

通过以上的系统结构分析和数据流分析,我们已经很清楚的了解了Lucene的系统的结构特征。在此基础上,我们可以通过扩充Lucene系统来完成一个完备的全文检索引擎,紧接着还可以在全文检索引擎的基础上构建各种应用系统。鉴于本文的目的并不在此,以下我们只是略为叙述一下相关的步骤,从而给出应用开发的一些思路。

 

首先,我们需要的是按照目标语言的词法结构来构建相应的词法分析逻辑,实现Luceneorg.apache.lucene.analysis中定义的接口,为Lucene提供目标系统所使用的语言处理能力。Lucene默认的已经实现了英文和德文的简单词法分析逻辑(按照空格分词,并去除常用的语法词,如英语中的isamare等等)。在这里,主要需要参考实现的接口在org.apache.lucene.analysis中的Analyzer.javaTokenizer.java中定义,Lucene提供了很多英文规范的实现样本,也可以做为实现时候的参考资料。其次,需要按照被索引的文件的格式来提供相应的文本分析逻辑,这里是指除开词法分析之外的部分,比如HTML文件,通常需要把其中的内容按照所属于域分门别类加入索引,这就需要从org.apache.lucene.document中定义的类document继承,定义自己的HTMLDocument类,然后就可以将之交给org.apache.lucene.index模块来写入索引文件。完成了这两步之后,Lucene全文检索引擎就基本上完备了。这个过程可以用下图表示:

 

    当然,上面所示的仅仅只是对于Lucene的基本扩充过程,它将Lucene由不完备的变成完备的(尤其是对于非英语的语言检索)。除此之外我们还可以在很多方面对Lucene进行改造。第一个方面即为按照文档索引的域,比如标题,作者之类的信息对返回的查询结果排序,这即需要改造Lucene的评分排序逻辑。默认的,Lucene采用其内部的相关性方法来处理评分和排序,我们可以根据需要改变它。遗憾的是,这部分Lucene并没有做到如同扩充词法解析和文档类型那样的条理清晰,没有留下很好的接口,因此需要仔细的分析其源代码的实现,自行扩充等等。其他的方面,比如改进其索引的效率,改进其返回结果时候的缓冲机制等等,都是加强Lucene系统的方面,在此也不再叙述。

 

    完成了Lucene系统,之后就可以开始考虑其上的应用系统开发。如果应用系统也使用java语言开发,那么Lucene系统能够方便的嵌入到整个系统中去,作为一个API集来调用。这个过程十分简单,以下便是一个示例程序,配合注释理解起来很容易。



2.4 Lucene应用代码示例

 

文本框: public class IndexFiles {<br />
  //使用方法:: IndexFiles [索引输出目录] [索引的文件列表] ...<br />
  public static void main(String[] args) throws Exception {<br />
    String indexPath = args[0];<br />
    IndexWriter writer;<br />
    //用指定的语言分析器构造一个新的写索引器(第3个参数表示是否为追加索引)<br />
    writer = new IndexWriter(indexPath, new SimpleAnalyzer(), false);</p>
<p>    for (int i=1; i<args.length; i++) {<br />
      System.out.println("Indexing file " + args[i]);<br />
      InputStream is = new FileInputStream(args[i]);</p>
<p>      //构造包含2个字段Field的Document对象<br />
      //一个是路径path字段,不索引,只存储<br />
      //一个是内容body字段,进行全文索引,并存储<br />
      Document doc = new Document();<br />
      doc.add(Field.UnIndexed("path", args[i]));<br />
      doc.add(Field.Text("body", (Reader) new InputStreamReader(is)));<br />
      //将文档写入索引<br />
      writer.addDocument(doc);<br />
      is.close();<br />
    };<br />
    //关闭写索引器<br />
    writer.close();<br />
  }<br />
}<br />

 

    或者,Lucene全文检索引擎也可作为服务器程序启动,但是这就需要用户自行扩充其他应用与Lucene的接口。这个可以通过传统的包装方式,比如客户服务器结构,或者采用现在流行的Web方式。诸如此类的应用方案,本文也不再继续叙述。参考Lucene的项目网站中的用户邮件列表能找到更多的信息。

 

 

第三节 Lucene索引文件格式分析

 

一、             Lucene源码实现分析的说明

 

通过以上对Lucene系统结构的分析,我们已经大致的清楚了Lucene系统的组成,以及在Lucene系统之上的开发步骤。接下来,我们试图来分析Lucene项目(采用Lucene 1.2版本)的源码实现,考察其实现的细节。这不仅仅是我们尝试用C++语言重新实现Lucene的必须工作,也是进一步做Lucene开发工作的必要准备。因此,这一部分所涉及到的内容,对于Lucene上的应用开发也是有价值的,尤其是本部分所做的文件格式分析。

 

    由于本文建立在我们的毕设项目之上,且同时我们需要实现cLucene项目,因此很遗憾的我们并没有完全的完成Lucene的所有源码实现的分析工作。接下来的部分,我们将涉及的部分为Lucene文件格式分析,Lucene中的存储抽象模块分析,以及Lucene中的索引构建逻辑模块分析。这一部分,我们主要涉及到的是文件格式分析与存储抽象模块分析。

 

二、             Lucene索引文件格式

 

Luceneweb站点上,有关于Lucene的文件格式的规范,其规定了Lucene的文件格式采取的存储单位、组织结构、命名规范等等内容,但是它仅仅是一个规范说明,并没有从实现者角度来衡量这个规范的实现。因此,我们以下的内容,结合了我们自己的分析与文件格式的定义规范,以期望给出一个更加清晰的文件格式说明。具体的文档规范可以参考后面的文献2

 

    首先在Lucene的文件格式中,以字节为基础,定义了如下的数据类型:

 

3.1 Lucene文件格式中定义的数据类型

数据类型

所占字节长度(字节)

说明

Byte

1

基本数据类型,其他数据类型以此为基础定义

UInt32

4

32位无符号整数,高位优先

UInt64

8

64位无符号整数,高位优先

VInt

不定,最少1字节

动态长度整数,每字节的最高位表明还剩多少字节,每字节的低七位表明整数的值,高位优先。可以认为值可以为无限大。其示例如下

字节1

字节2

字节3

0

00000000

 

 

1

00000001

 

 

2

00000010

 

 

127

01111111

 

 

128

10000000

00000001

 

129

10000001

00000001

 

130

10000010

00000001

 

16383

10000000

10000000

00000001

16384

10000001

10000000

00000001

16385

10000010

10000000

00000001

Chars

不定,最少1字节

采用UTF-8编码[20]Unicode字符序列

String

不定,最少2字节

VIntChars组成的字符串类型,VInt表示Chars的长度,Chars则表示了String的值

 

    以上的数据类型就是Lucene索引文件格式中用到的全部数据类型,由于它们都以字节为基础定义而来,因此保证了是平台无关,这也是Lucene索引文件格式平台无关的主要原因。接下来我们看看Lucene索引文件的概念组成和结构组成。

    以上就是Lucene的索引文件的概念结构。Lucene索引index由若干段(segment)组成,每一段由若干的文档(document)组成,每一个文档由若干的域(field)组成,每一个域由若干的项(term)组成。项是最小的索引概念单位,它直接代表了一个字符串以及其在文件中的位置、出现次数等信息。域是一个关联的元组,由一个域名和一个域值组成,域名是一个字串,域值是一个项,比如将“标题”和实际标题的项组成的域。文档是提取了某个文件中的所有信息之后的结果,这些组成了段,或者称为一个子索引。子索引可以组合为索引,也可以合并为一个新的包含了所有合并项内部元素的子索引。我们可以清楚的看出,Lucene的索引结构在概念上即为传统的倒排索引结构[21]

 

    从概念上映射到结构中,索引被处理为一个目录(文件夹),其中含有的所有文件即为其内容,这些文件按照所属的段不同分组存放,同组的文件拥有相同的文件名,不同的扩展名。此外还有三个文件,分别用来保存所有的段的记录、保存已删除文件的记录和控制读写的同步,它们分别是segmentsdeletablelock文件,都没有扩展名。每个段包含一组文件,它们的文件扩展名不同,但是文件名均为记录在文件segments中段的名字。让我们看如下的结构图3.2



项集合信息

 



项位置

 

流程图:文档: segment1.frq


项频数

 



被删除文档

 

流程图:文档: segment1.del


标准化因子

 

流程图:文档: segment1.tis流程图:文档: segment1.tii流程图:文档: segment1.prx流程图:文档: segment1.nrm


3.2 Lucene索引文件结构组成

 



segment1所含文件

 



项字典

 



域值存储表

 



域集合信息

 

流程图:文档: segment1.fdt流程图:文档: segment1.fdx流程图:文档: segment1.fnm


index

 

流程图:文档: segments流程图:文档: deletable流程图:文档: lock流程图:多文档: segment1

 

    关于图3.2中的各个文件具体的内部格式,在参考文献3中,均可以找到详细的说明。接下来我们从宏观关系上说明一下这些文件组成。在这些宏观上的关系理清楚之后,仔细阅读参考文献3,即可清楚的明白具体的Lucene文件格式。

 

    每个段的文件中,主要记录了两大类的信息:域集合与项集合。这两个集合中所含有的文件在图3.2中均有表明。由于索引信息是静态存储的,域集合与项集合中的文件组采用了一种类似的存储办法:一个小型的索引文件,运行时载入内存;一个对应于索引文件的实际信息文件,可以按照索引中指示的偏移量随机访问;索引文件与信息文件在记录的排列顺序上存在隐式的对应关系,即索引文件中按照“索引项1、索引项2…”排列,则信息文件则也按照“信息项1、信息项2…”排列。比如在图3.2所示文件中,segment1.fdxsegment1.fdt之间,segment1.tiisegment1.tissegment1.prxsegment1.frq之间,都存在这样的组织关系。而域集合与项集合之间则通过域的在域记录文件(比如segment1.fnm)中所记录的域记录号维持对应关系,在图3.2segment1.fdxsegment1.tii中就是通过这种方式保持联系。这样,域集合和项集合不仅仅联系起来,而且其中的文件之间也相互联系起来。此外,标准化因子文件和被删除文档文件则提供了一些程序内部的辅助设施(标准化因子用在评分排序机制中,被删除文档是一种伪删除手段)。这样,整个段的索引信息就通过这些文档有机的组成。

 

    以上所阐述的,就是Lucene所采用的索引文件格式。基本上而言,它是一个倒排索引,但是Lucene在文件的安排上做了一些努力,比如使用索引/信息文件的方式,从文件安排的形式上提高查找的效率。这是一种数据库之外的处理方法,其有其优点(格式平台独立、速度快),也有其缺点(独立性带来的共享访问接口问题等等),具体如何衡量两种方法之间的利弊,本文这里就不讨论了。

 

三、             一些公用的基础类

 

分析完索引文件格式,我们接下来应该着手对存储抽象也就是org.apache.lucenestore中的源码做一些分析。我们先不着急分析这部分,而是分析图2.1中基础结构封装那一部分,因为这是整个系统的基石,然后我们在下一部分再来分析存储抽象。

 

    基础结构封装,或者基础类,由org.apache.lucene.utilorg.apache.lucene.document两个包组成,前者定义了一些常量和优化过的常用的数据结构和算法,后者则是对于文档(document)和域(field)概念的一个类定义。以下我们用列表的方式来分析这些封装类,指出其要点。

 

3.2 基础类包org.apache.lucene.util

说明

Arrays

一个关于数组的排序方法的静态类,提供了优化的基于快排序的排序方法sort

BitVector

C/C++语言中位域的java实现品,但是加入了序列化能力

Constants

常量静态类,定义了一些常量

PriorityQueue

一个优先队列的抽象类,用于后面实现各种具体的优先队列,提供常数时间内的最小元素访问能力,内部实现机制是哈析表和堆排序算法

 

3.3 基础类包org.apache.lucene.document

说明

Document

是文档概念的一个实现类,每个文档包含了一个域表(fieldList),并提供了一些实用的方法,比如多种添加域的方法、返回域表的迭代器的方法

Field

是域概念的一个实现类,每个域包含了一个域名和一个值,以及一些相关的属性

DateField

提供了一些辅助方法的静态类,这些方法将javaDateTime数据类型和String相互转化

 

总的来说,这两个基础类包中含有的类都比较简单,通过阅读源代码,可以很容易的理解,因此这里不作过多的展开。

 

四、             存储抽象

 

有了上面的知识,我们接下来来分析存储抽象部分,也就是org.apache.lucene.store包。存储抽象是唯一能够直接对索引文件存取的包,因此其主要目的是抽象出和平台文件系统无关的存储抽象,提供诸如目录服务(增、删文件)、输入流和输出流。在分析其实现之前,首先我们看一下UML[22]图。

3.3 存储抽象实现UML图(一)

3.4 存储抽象实现UML图(二)

3.4 存储抽象实现UML图(三)

 

    3.23.4展示了整个org.apache.lucene.store中主要的继承体系。共有三个抽象类定义:DirectoryInputStreamOutputStrem,构成了一个完整的基于抽象文件系统的存取体系结构,在此基础上,实作出了两个实现品:(FSDirectoryFSInputStreamFSOutputStream)和(RAMDirectoryRAMInputStreamRAMOutputStream)。前者是以实际的文件系统做为基础实现的,后者则是建立在内存中的虚拟文件系统。前者主要用来永久的保存索引文件,后者的作用则在于索引操作时是在内存中建立小的索引,然后一次性的输出合并到文件中去,这一点我们在后面的索引逻辑部分能够看到。此外,还定以了org.apache.lucene.store.lockorg.apache.lucene.store.with两个辅助内部实现的类用在实现Directory方法的makeLock的时候,以在锁定索引读写之前来让客户程序做一些准备工作。

 

    FSDirectoryFSInputStreamFSOutputStream)的内部实现依托于java语言中的io类库,只是简单的做了一个外部逻辑的包装。这当然要归功于java语言所提供的跨平台特性,同时也带了一些隐患:文件存取的效率提升需要依耐于文件类库的优化。如果需要继续优化文件存取的效率,应该还提供一个文件与目录的抽象,以根据各种文件系统或者文件类型来提供一个优化的机会。当然,这是应用开发者所不需要关系的问题。

 

    RAMDirectoryRAMInputStreamRAMOutputStream)的内部实现就比较直接了,直接采用了虚拟的文件RAMFile类(定义于文件RAMDirectory.java中)来表示文件,目录则看作一个StringRAMFile对应的关联数组。RAMFile中采用数组来表示文件的存储空间。在此的基础上,完成各项操作的实现,就形成了基于内存的虚拟文件系统。因为在实际使用时,并不会牵涉到很大字节数量的文件,因此这种设计是简单直接的,也是高效率的。

 

    这部分的实现在理清楚继承体系后,相当的简单。因此接下来的部分,我们可以通过直接阅读源代码解决。接下来我们看看这个部分的源代码如何在实际中使用的。

 

    一般来说,我们使用的是抽象类提供的接口而不是实际的实现类本身。在实现类中一般都含有几个静态函数,比如createFile,它能够返回一个OutputStream接口,或者openFile,它能够返回一个InputStream接口,利用这些接口之中的方法,比如writeStringwriteByte等等,我们就能够在抽象的层次上处理Lucene定义的数据类型的读写。简单的说,Lucene中存储抽象这部分设计时采用了工厂模式(Factory parttern[23]。我们利用静态类的方法也就是工厂来创建对象,返回接口,通过接口来执行操作。

 

五、             关于cLucene项目

 

这一部分详细的说明了Lucene系统中所采用的索引文件格式、一些基础类和存储抽象。接下来我们来叙述一下我们在项目cLucene中重新实现这些结构时候的一些考虑。

 

    cLucene彻底的遵守了Lucene所定义的索引文件格式,这是Lucene对于各个兼容系统的基本要求。在此基础上,cLucene系统和Lucene系统才能够共享索引文件数据。或者说,cLucene生成的索引文件和Lucene生成的索引文件完全等价。

 

    在基础类问题上,cLucene同样封装了类似的结构。我们同样列表描述,请和前面的表3.23.3对照比较。

3.4 基础类包cLucene::util

说明

Arrays

没有实现,直接利用了STL库中的快排序算法实现

BitVector

C/C++语言版本的实现,与java实现版本类似

Constants

常量静态类,定义了一些常量,但是与java版本不同的是,这里主要定义了一些宏

PriorityQueue

这是一个类型定义,直接利用STL库中的std::priority_queue

 

3.3 基础类包cLucene::document

说明

Document

C/C++语言版本的实现,与java实现版本类似

Field

C/C++语言版本的实现,与java实现版本类似

DateField

没有实现,直接利用OpenTop库中的ot::StringUtil

 

    存储抽象的实现上,也同样是类似于java实现。由于我们采用了OpenTop库,因此同样得以借助其中对于文件系统抽象的ot::io包来解决文件系统问题。这部分问题与前面一样,存在优化的可能。在实现的类层次上、对外接口上,均与java版本的一样。

 

 

第四节 Lucene索引构建逻辑模块分析

 

一、             绪论

 

这一个部分,我们将分析Lucene中的索引构建逻辑模块。它与前面介绍的存储抽象一起构成了Lucene的索引核心部分。无论是对外接口中的查询,还是分析各种文本以进一步生成索引,都需要直接调用这部分来获得对索引文件的访问能力,因此,这部分在系统中至关重要。构建一个高效的、易使用的索引构建逻辑,即是Lucene在这一部分需要达到的目的。

 

    从面向对象的经典思考方式出发来看,我们只需要使用继承体系来表达图3.1中的各个概念,就可以通过这个继承体系来控制索引文件的结构,然后设计合适的永久化方法,以及接受分析token流的操作,即可将索引构建逻辑完成。原理上就是这样的简单。由于两个关键的概念documentfield都已经在org.apache.lucene.document中当作基础类定义过了,因此实际上Lucene在这部分需要完善的概念结构还有segmentterm。在此基础上继续编写各个逻辑结构的永久化方法,然后提供一个进入的接口方法,即是宣告完成了这个过程。其中永久化的部分,Lucene使用了另外实现一个代理类的方式来实现,即对于某个类X,存在XWriter类和XReader类来负责写出和读入的功能;用作永久化功能的类是被永久化的类的友元。

 

    在接下来的分析过程中,我们按照这样一个思路,以UML图和对象体系的描述来叙述这部分的设计和实现,然后通过内部的数据流理清楚调用时序。

 

二、             对象体系与UML

 

1.  项(Term

 

这部分主要是分析针对项(Term)这个概念所做的设计,包括概念所实际涉及的类、永久化类。首先,我们从图3.2和阅读参考文献3知道,项(Term)所表示的是一个字符串,它拥有域、频数和位置信息等等属性。因此,Lucene中设计了两个类来表示这个概念,如下图

4.1 UML图(-)

 

上图中,有意的突出了类TermTermInfo中的数据成员,因为它反映了对于项(Term)这个概念的具体表示。同时上图中也同时列出了用于永久化项(Term)的代理类TermInfosWriterTermInfosReader,它们完成永久化的功能,需要注意的是,TermInfosReader内部使用了数组indexTermsindexInfos来存储一系列项;而TermInfosWriter则是一个类似于链表的结构,通过一个other指向下一个TermInfosWriter,每一个TermInfosWriter只负责本身那个lastTermlastTi的永久化工作。这是一个设计上的技巧,通过批量读取(或者称为缓冲的方式)来获得读入时候的效率优化;而通过一个链表式的、各负其责的方式,来获得写出时候的设计简化。

 

项(term)这部分的设计中,还有一些重要的接口和类,我们先介绍如下,同样我们也先展示UML

4.2 UML图(二)

 

4.2中,我们看到三个类:TermEnumTermDocsTermPositions,第一个是抽象类,后两个都是接口。TermEnum的设计主要用在后面SegmentDocument等等的实现中,以提供枚举其中每一个项(Term)的能力。TermDocs是一个接口,用来继承以提供返回<document, frequency>值对的能力,通过这个接口就可以获得某个项(Term)在某个文档中出现的频数。TermPositions则是在TermDocs上的扩展,将项(Term)在文档中的位置信息也表示出来。TermDocsTermPositions)接口的使用方式类似于java中的Enumration接口,即通过next方法跳转,通过docfreq等方法获得当前的属性值。

 

2.  域(Field

 

由于Field的基本概念在org.apache.lucene.document中已经做了定义,因此在这部分主要是针对项文件(.fnm文件、.fdx文件、.fdt文件)所需要的信息再来设计一些类。

4.3 UML图(三)

 

4.3中展示的,就是表示与域(Field)所关联的属性信息的类。其中isIndexed表示的这个域的值是否被索引过,即值是否被分词然后索引;另外两个属性所表示的意思则很明显:一个是域的名字,一个是域的编号。

 

接下来我们来看关于域表和存取逻辑的UML图。

4.4 UML图(四)

FieldInfos即为域表的概念表示,内部采用了冗余的方式以获取在通过域的编号访问或者通过域的名字来访问时候的高效率。FieldsReaderFieldsWriter则分别是写出和读入的代理类。在功能和实现上,这两个类都比较简单。至于FieldInfos中采用的冗余方式,则是基于域的数目相对比较少而做出的一种折衷处理。

 

3.  文档(document

 

文档(document)同样也是在org.apache.lucene.document中定义过的结构。由于对于这部分比较重要,我们也来看看其UML图。

4.5 UML图(五)

 

在图4.5中我们看到,Document的设计基本上沿用了链表的处理方法。左边的Document类作为一个数据外包类,用来提供对于内部结构DocumentFieldList的增加删除访问操作等等。DocumentFieldList才是实际上的数据存储单位,它用了链表的处理方法,直接指向一个当前的Field对象和下一个DocumentFieldList对象,这个与前面的类似。为了能够逐个访问链表中的节点,还设计了DocumentFieldEnumeration枚举类。

4.6 UML图(六)

    实际上定义于org.apache.lucene.index中的有关于Document的就是永久化的代理类。在图4.6中给出了其UML图。需要说明的是为什么没有出现读入的方法:这个方法已经隐含在图4.5Document类中的add方法中了,结合图2.4中的程序代码段,我们就能够清楚的理解这种设计。

 

4.  段(segment

 

段(Segment)这一部分设计的比较特殊,在实现简单的对象结构之上,还特意的设计了用于段之间合并的类。接下来,我们仍然采取对照UML分析的方式逐个叙述。接下来我们看Lucene中如何表示段这个概念。

4.7 UML图(七)

Lucene定义了一个类SegmentInfo用来表示每一个段(Segment)的信息,包括名字(name)、含有的文档的数目(docCount)和段所位于的目录的位置(dir)。根据索引文件中的段的意义,有了这三点,就能唯一确定一个段了。SegmentInfos这个类则是用来表示一个段的链表(从标准的java.util.Vector继承而来),实际上,也就是索引(index)的意思了。需要注意的是,这里并没有在SegmentInfo中安插一个文档(document)的链表。这样做的原因牵涉到Lucene内部对于文档(相当于一个被索引文件)的处理;Lucene内部采用了赋予文档编号,给域赋值的方式来处理文档,即加入的文档顺次编号,以后用文档号表示文档,而路径信息,文件名字等等在以后索引查找需要的属性,都作为域存储下来;因此SegmentInfo中并没有另外存储一个文档(document)的链表,对于这些的写出和读入,则交给了永久化的代理类来做。

 

4.8 UML图(八)

4.8给出了负责段(segment)的读入操作的代理类,而负责段(segment)的写出操作也同样没有定义,这些操作都直接实现在了类IndexWriter类中(后面会详细分析)。段的操作同样采用了之前的数组或者说是缓冲的处理方式,相关的细节也不在这里详细叙述了。

 

然后,针对前面项(term)那部分定义的几个接口,段(segment)这部分也需要做相应的接口实现,因为提供直接遍历访问段中的各个项的能力对于检索来说,无疑是十分重要的。即这部分的设计,实际上都是在为了检索在服务。

4.9 UML图(九)

 

4.10 UML图(十)

4.9和图4.10分别展示了前面项(term)那里定义的接口是如何在这里通过继承实现的。Lucene在处理这部分的时候,也是分成两部分(SegmentSegments开头的类)来实现,而且很合理的运用了数组的技法,以及注意了继承重用。但是细化到局部,终归是比较简单的按照语义来获得结果而已了,因此关于更多的也就不多做分析了,我们完全可以通过阅读源代码来解决。

 

接下来所介绍的,就是在Lucene的设计过程中比较特殊的一个部分:段合并类(SegmentMerger)。这首先需要介绍Lucene中的建立索引时的段合并策略。

 

Lucene为了兼顾建立索引时的效率和读取索引查找的速度,引入了分小段建立索引的方式,即每一次批量建立索引时,先在内存中的虚拟文件系统中为每一个文档单独建立一个段,然后在输出的时候将这些段合并之后输出成为索引文件,这时仅仅存在一个段。多次建立的索引后,如果想优化索引文件,也可采取合并段的方法,将索引中的段合并成为一个段。我们来看一下在IndexWriter类中相应的方法的实现,来了解一下这中建立索引的实现。

    对于上面的代码,我们不做过多注释了,结合源码中的注解应该很容易理解。在最后那个mergeSegments函数中,将用到几个重要的类结构,它们记录了合并时候的一些重要信息,完成合并时候的工作。接下来,我们来看这几个类的UML图。

4.12 UML图(十一)

从图4.12中,我们看到Lucene设计一个类SegmentMergeInfo用来保存每一个被合并的段的信息,也保存能够访问其内部的接口句柄,也就是说合并时的操作使用这个类作为对被合并的段的操作代理。类SegmentMergeQueue则设计为org.apache.lucene.util.PriorityQueue的子类,做为SegmentMergeInfo的容器类,而且附带能够自动排序。SegmentMerger是主要进行操作的类,里面各个方法环环相扣,分别完成合并各个数据项的问题。

 

5.  IndexReader类与IndexWirter

 

最后剩下的,就是整个索引逻辑部分的使用接口类了。外界通过这两个类以及文档(document)类的构造函数调用之,比如图2.4中的代码示例所示。下面我们来看一下这部分最后两个类的UML图。

4.13 UML图(十二)

 

    IndexWriter的设计与IndexReader的设计很不相同,前者是一个实现类,而后者是一个抽象类,带有没有实现的接口。IndexWriter的主要作用就是接收新加入的文档(document),然后在内部为之生成相应的小段,最后再合并并向索引文件中输出,图4.11中已经给出了一些实现的代码。由于Lucene在面向对象上封装的努力,通过各个构造函数就已经完成了对于各个概念的构造过程,剩下部分的代码主要是依据各个数组或者是链表中的信息,逐个逐个的将信息写出到相应的文件中去了。IndexReader部分则只是做了接口设计,没有具体的实现,这个和本部分所完成的主要功能有关:索引构建逻辑。设计这个抽象类的目的是,预先完成一些函数,为以后的检索(search)部分的各种形式的IndexReader铺平道路,也是利用了在同一个包内可以方便访问其它类的保护变量这个java语言的限制。

 

    到此,在索引构建逻辑部分出现的类我们就分析完毕了,需要说明主要是做的一个宏观上的组成结构上的分析,并指出一些实现上的要点。具体的实现,由于Lucene的开放源码而显得并不是非常的重要,因为Lucene在做到良好的面相对象设计之后,实际带来的是局部复杂性的减小,因此某一些单独的函数或者实现就比较容易编写,也容易让人阅读。本文不再继续叙述这方面的细节,作为一个总结,下一个部分我们通过索引构建逻辑的数据流图的方式,再来理清楚一下索引构建逻辑这部分的调用时序。

 

三、             数据流逻辑

 

 

从宏观上明白一个系统的设计,理清楚其中的运行规律,最好的方式应该是通过数据流图。在分析了各个位于索引构建逻辑部分的类的设计之后,我们接下来就通过分析数据流图的方式来总结一下。但是由于之前提到的原因:索引读入部分在这一部分并没有完全实现,所以我们在数据流图中主要给出的是索引构建的数据流图。

 



4.14 索引构建部分的数据流逻辑

 



合并输出

 



字节流输入

 



内存文件系统

 

文本框: 顺次调用流程图:多文档: 索引文件文本框: 索引构建阶段


writeNorms写出标准化因子

 



sortPostingTable排序位置信息

 



writePostings写出索引信息

 



invertDocument分析文档

 



addDocument生成小段

 



加入document对象

 



document对象方式传入

 

文本框: 准备阶段


调用

 



生成field对象,根据对象性质不同,为值赋予String值,或者是Reader

 



生成document对象,调用add方法加入field对象

 



通过java语言的io类以输入流方式传入

 

流程图:多文档: 被索引文件

 

对于图4.14中所描述的内容,结合Lucene源代码中的一些文件看,能够加深理解。准备阶段可以参考demo文件夹中的org.apache.lucene.demo.IndexFiles类和java文件夹中的org.apache.lucene.document文件包。索引构建阶段的主要源码位于java文件夹中org.apache.lucene.index.IndexWriter类,因此这部分可以结合这个类的实现来看。至于内存文件系统,比较复杂,但是这时的逻辑相对简单,因此也不难理解。

 

    上面的数据流图十分清楚的勾画除了整个索引构建逻辑这部分的设计:通过层层嵌套的类结构,在构建时候即分步骤有计划的生成了索引结构,将之存储到内存中的文件系统中,然后通过对内存中的文件系统优化合并输出到实际的文件系统中。

 

四、             关于cLucene项目

 

前面的三个部分,已经完成了分析索引构建逻辑的任务,这里我们还是有针对性的谈谈我们这次的毕业设计项目cLucene在这一部分的情况。

 

在实现这部分的时候,为了将一些java语法中比较特殊的部分,比如内隐类、同步函数、同步对象等等,我们不得不采用了一些比较晦涩和艰深的C++语法,在OpenTop这个类库所提供的类似于java语言的设施上来实现。这个尤其体现在实现Segment相关类时,为了处理原来java源代码中用内隐类实现的Lock文件创建机制的时候,我们不得不定义了大量的cLucene::store::With的子类,并为之传入调用类的指针,设置它为调用类的友元,才得以精确的模拟了原有的语义。陷于我们这次的重写以移植为主,系统结构基本上没有大的变化,不得不产生这种重复而且大量的工作。如果需要改进这中状况,我们应该考虑按照C++语言的特点来设计索引构建部分的类库继承结构,但是很可惜在本文成文之前,时间不允许我们这样做。

 

来自java语法的特殊性只是我们解决问题的一个方面,我们还需要处理引用的调用方式。由于java语言拥有了垃圾收集机制,因此得以将一切的参数形式看作为引用,而不考虑其分配与消亡的问题。C++语言并不具备这种机制,它需要程序员自行管理分配空间与销毁对象的问题。在这里,我们使用的是来自OpenTop中所引入的计数指针RefPtr<>模板,它能够模拟指针的语义,并且计算指针被引用的次数,在引用次数为0时就自动释放资源:这是一种类似于java语言中引用的方式,不过它显得更加高效率。我们在cLucene的实现中大量的使用了计数指针模板。

 

    除此之外,我们没有改变Lucene所定义的索引构建逻辑的结构和语义,我们实现的是一个完全和java版本Lucene兼容的版本。

from:    http://www.chedong.com/tech/weblucene.html

基于Lucene/XML的站内全文检索解决方案

作者: 车东 Email: chedongATbigfoot.com/chedongATchedong.com

写于:2003/05 最后更新: 02/22/2006 14:42:55
Feed Back >> (Read this before you ask question)
Creative Commons License

版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
http://www.chedong.com/tech/weblucene.html

关键词:Lucene xml xslt web site search engine

内容摘要:
为Lucene做一个通用XML接口一直是我最大的心愿:更方便的在WEB应用中嵌入全文检索功能

  • 提供了XML的数据输入接口:适合将原有基于各种数据库的数据源导入到全文索引中,保证了数据源的平台无关性;
  • 通过了基于XML的搜索结果输出:方便了通过XSLT进行前台的结果显示;

MySQL \ / JSP
Oracle - DB - ==> XML ==> (Lucene Index) ==> XML - ASP
MSSQL / - PHP
MS Word / \ / XHTML
PDF / =XSLT=> - TEXT
\ XML
\_________WebLucene__________/
使用过程如下:
  1. 将数据用脚本导出成XML格式;
  2. 将XML数据源导入LUCENE索引;
  3. 从WEB界面得到XML结果输出,并通过XSLT生成HTML页面

站内全文检索的必要性

虽然大型搜索引擎的功能已经越来越强大了,很多站点都使用了Google的站内检索site:domain.com代替了自己的站内数据库“全文”检索。但依靠GOOGLE这样的大型搜索引擎做站内检索会有以下弊端:

  • 数量有限:搜索引擎并不会深度遍历一个网站,而将网站所有的内容都索引进去,比如Google就喜欢静态网页,而且是最新更新的,而不喜欢带?的动态网页,Google甚至会定期将缺少入口的网站内容逐渐抛弃;
  • 更新慢:搜索引擎针对站点的更新频率也是有一定周期的,很多内容需要一定时间后才能进入GOOGLE的索引:目前Google Dance的周期是21天左右;
  • 内容不精确:搜索引擎需要通过页面内容提取技术将导航条,页头页尾等内容过滤掉,反而不如直接从后台数据库提取数据来得直接,这种摘要和排重机制是很难实现的;
  • 无法控制输出:也许有更多的输出需求,按时间排序,按价格,按点击量,按类目过滤等

系统的搭建

下载:
http://sourceforge.net/projects/weblucene/

XML数据源的导入:

只要数据源可以导出成3层的XML结构,就都可以用IndexRunner这个命令行工具导入:

比如从数据库导出:news_dump.xml
<?xml version="1.0" encoding="GB2312"?>
<Table>
    <Record>
        <Title>标题</Title>
        <Author>作者</Author>
        <Content>内容</Content>
        <PubTime>2003-06-29</PubTime>      
    </Record>
    <Record>
        <Title>My Title</Title>
        <Author>chedong</Author>
        <Content>abc</Content>
        <PubTime>2003-06-30</PubTime>
    </Record>
    …
</Table>

IndexRunner -i news_dump.xml -o c:\index -t Title,Content -n Author
-i news_dump.xml:  以news_dump.xml为数据源
-o c:\index   索引库建立在c:\index目录下
索引建立Title Author Content PubTime这几个字段外,按以下规则建立索引:
-t Title,Content 一个进行分词的全文索引TokenIndex:数据是Title Content这2个字段
-n Author    一个不分词的索引:NoTokenIndex:数据源是Author这个字段。

对于RSS数据源:
<?xml version="1.0"?>
<rss version="0.92">
<channel>
  <title>Amazon: Books Arts &amp; Photography</title>
  <link>http://www.lockergnome.com/</link>
  <description>Amazon RSS Feed</description>
  <lastBuildDate>Sun, 29 Jun 2003 01:05:01 GMT</lastBuildDate>
  <docs>http://www.lockergnome.com/</docs>
  <webMaster>amazonfeed@lockergnome.com (Lockergnome RSS Generator)</webMaster>
  <item>
    <title>The Artist’s Way: A Spiritual Path to Higher Creativity – $11.17</title>
    <link>http://www.amazon.com/exec/obidos/ASIN/1585421464/lockergnomedigit/?ref=nosim&amp;dev-it=D34HUVGKB34YFX</link>
    <description>http://www.lockergnome.com/    </description>
  </item>
  …
</channel>

IndexRunner -i http://www.example.com/rss.xml -o c:\index -t title,description -n link  -l  4
-l 4 表示拿第4层节点作为字段映射,

IndexRunner还提供了-a -m这两个选项:用于增量索引和批量索引优化。
-a  增量索引,表示在原有索引的基础上扩展
-m  mergeFactor 在Lucene中mergeFactor是一个针对批量索引的优化参数,控制多少条处理完多少条记录(Document)后,写入一次索引,写入频率越高,内存使用越少,但索引速度越慢,所以在大批量数据导入时需要增大文件写入的间隔,多让索引在内存中操作。

搜索结果输出:


以下是系统设计过程中一些设计的思路:

做为工业标准的XML

记得以前有关于肯德基的炸薯条断顿的报道。从这个事件报道中我们可以看到一种更高效的管理体系:对于快餐店这样全球性的企业来说,要保证各地提供的薯条品质,成本最低的方法肯定是依靠机器而不是厨师,如果要求薯条机能够处理各种形状不一的土豆,机器的复杂程度和维护成本都会很高。所以土豆必须严格符合工业标准才能让结构比较简单的薯条机生产出符合标准的薯条,因此,薯条的加工机械会严格按照土豆协会的土豆工业标准设计。高质量的原料可以大大降低后期加工设备的成本,因此从总体成本上讲还是合算的。

对于软件应用开发者来说:应用和应用之间,企业和企业之间交换的数据好比就是土豆,白菜,按照严格的XML标准设计的接口作为企业之间后台数据交换的工业标准,虽然不如简单的CSV格式高效,但缺能大大简化下游工序的后期加工成本。

不难想象为什么处理HTML的浏览器:IE和Mozilla等浏览器软件大小都在10M以上,但一般处理XML的解析器一般都在几百K。除了没有界面外,HTML浏览器需要为太多不规范的HTML代码提供大量容错处理也是一个很重要的原因,而语法严格,规则简单的XML处理器就可以做的很简短,高效,体积越“小”就意味着适应性越广:这点在手机这样的硬件配置比较低的设备环境中显得尤其重要。

虽然XML在后台数据交换方面,有着巨大的潜力。在前台表现方面,XML并不会马上代替HTML,很多通过XSLT输出的HTML仍然需要结合CSS来进行表现。XML ==XSLT==> HTML + CSS。但是由于太多的网页都是用HTML做的,相信XML没有必要马上代替这些已有的机制。

此外在应用的国际化支持方面XML和Java简直是绝配:XML数据源用Java解析后是UNICODE,这样无论是日文,繁体中文还是德文的内容我们都可以在一个索引库中同时进行搜索。这样针对其他语言的支持只是设计各种语言界面的问题了。

      GBK          \                                       / BIG5
BIG5 - UNICODE ====> Unicode - GB2312
SJIS - (XML) (XML) - SJIS
ISO-8859-1 / \ ISO-8859-1

使用XML的另外一个额外好处在于:开发人员一般都没有仔细理解Java的字符集(其实上是JVM的缺省file.encoding属性)受系统本地化设置的影响,基于XML的输入使得数据的字符解码过程变得透明:不用再和用户解释需要如何解码,编码数据源。不过,XML的学习成本还是比较高的,假设你HTML的学习成本是1,XML则可能为10,而XSLT的学习成本则可能高达100。

传统数据库应用的全文检索加速

让数据库负责精确匹配,将模糊匹配用独立的系统实现

一个站点内容积累在万级以上,站内全文检索就会是用户定位最主要的手段,而关键词检索是用户最熟悉的方法。因此基于数据库的传统WEB应用在全文检索需求还是很大的。

但是可怕的%like%数据库操作可能会吃掉数据库服务器90%以上的CPU。Oracle MSSQL等数据库服务器中数据库内置的全文检索基本上都不太适合WEB应用。而数据库另外一个的弊端在于对于条件简单的查询返回结果集非常大:数据库并不知道如何面向用户最关心的的头100条结果进行优化。根据以前的统计:头100条结果往往已经可以满足95%以上用户需求。

需要缓存设计:根据我们的经验,在应用设计中没有必要进行内置的结果缓存设计:让前台的应用服务器内置的缓存机制或者反相代理缓存服务器进行缓存就够了。

数据同步策略

总体上讲,全文检索和数据库其实是2种根本不同的应用模式,全文检索系统其实往往也没有必要和数据库那么高的实时同步机制,如果按照:低更新,高缓存的模式进行设计:数据库数据到全文索引的同步过程一般都可以通过脚本定期将数据库的数据导出成XML,然后进入Lucene的全文索引。而针对原有数据记录的更新和删除,其实一般可以通过定期的重建索引解决。WebLucene其中索引部分是一个IndexRunner的命令行程序实现的。

结果排序策略

站内全文索引另外一个很重要的需求是可定制的排序:按时间,按价格,按点击量……Lucene全文索引缺省只提供了根据关键词在原文中的匹配度排序,而任何根据某个字段的值进行排序的都无法避免再次遍历数据,从而导致性能有数量级的下降(等于又是做%Like%检索),而在索引中,除了匹配度SCORE外,唯一能用来排序的就是索引记录的ID,所以一个比较高效率实现定制排序的方法时:在索引时,让进入Lucene全文的顺序对应着一定规则:比如时间,然后在搜索时,让搜索结果按照索引记录的ID进行排序(或倒排)。

搜索结果关键词标引的实现

搜索结果中关键词通过红色或者黑体字标记出来,为了能够更恰当的显示相关上下文的问题,标引是通过限制了一个扫描范围,然后根据一个分析器将指定的词流式的读取出来,然后

全文检索和其他应用的集成

其实核心的是一个Lucene的XML接口:SAX方式的数据导入和DOM方式的结果输出。

XML的数据源定义:
只要是能够映射成表=》记录=》字段这样层次结构的都可以。因此WebLucene索引的设计比较灵活,甚至可以直接用来索引RSS。

XML结果定义:参考了Google的XML接口的设计

如果没有SERVLET界面,提供XML输出的DOMSearcher也可以很方便集成到各种应用系统中。

参考资料:

系统设计中使用的一些模块:
Jakarta Lucene:
http://jakarta.apache.org/lucene/

Xerces / Xalan
http://xml.apache.org/

Log4j
http://jakarta.apache.org/log4j/

Google的XML接口定义:
http://www.google.com/google.dtd

原文出处:<a href="http://www.chedong.com/tech/weblucene.html">http://www.chedong.com/tech/weblucene.html</a>
<<返回

<<返回首页

from;           http://www.chedong.com/tech/lucene.html

在应用中加入全文检索功能
    ——基于Java的全文索引引擎Lucene简介

作者: 车东 Email: chedongATbigfoot.com/chedongATchedong.com

写于:2002/08 最后更新: 02/22/2006 14:42:55
Feed Back >> (Read this before you ask question)
Creative Commons License

版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
http://www.chedong.com/tech/lucene.html

关键词:Lucene java full-text search engine Chinese word segment

内容摘要:

Lucene是一个基于Java的全文索引工具包。

  1. 基于Java的全文索引引擎Lucene简介:关于作者和Lucene的历史
  2. 全文检索的实现:Luene全文索引和数据库索引的比较
  3. 中文切分词机制简介:基于词库和自动切分词算法的比较
  4. 具体的安装和使用简介:系统结构介绍和演示
  5. Hacking Lucene:简化的查询分析器,删除的实现,定制的排序,应用接口的扩展
  6. 从Lucene我们还可以学到什么

基于Java的全文索引/检索引擎——Lucene

Lucene不是一个完整的全文索引应用,而是是一个用Java写的全文索引引擎工具包,它可以方便的嵌入到各种应用中实现针对应用的全文索引/检索功能。

Lucene的作者:Lucene的贡献者Doug Cutting是一位资深全文索引/检索专家,曾经是V-Twin搜索引擎(Apple的Copland操作系统的成就之一)的主要开发者,后在Excite担任高级系统架构设计师,目前从事于一些INTERNET底层架构的研究。他贡献出的Lucene的目标是为各种中小型应用程序加入全文检索功能。

Lucene的发展历程:早先发布在作者自己的www.lucene.com,后来发布在SourceForge,2001年年底成为APACHE基金会jakarta的一个子项目:http://jakarta.apache.org/lucene/

已经有很多Java项目都使用了Lucene作为其后台的全文索引引擎,比较著名的有:

  • Jive:WEB论坛系统;
  • Eyebrows:邮件列表HTML归档/浏览/查询系统,本文的主要参考文档“TheLucene search engine: Powerful, flexible, and free”作者就是EyeBrows系统的主要开发者之一,而EyeBrows已经成为目前APACHE项目的主要邮件列表归档系统。
  • Cocoon:基于XML的web发布框架,全文检索部分使用了Lucene
  • Eclipse:基于Java的开放开发平台,帮助部分的全文索引使用了Lucene

对于中文用户来说,最关心的问题是其是否支持中文的全文检索。但通过后面对于Lucene的结构的介绍,你会了解到由于Lucene良好架构设计,对中文的支持只需对其语言词法分析接口进行扩展就能实现对中文检索的支持。

全文检索的实现机制

Lucene的API接口设计的比较通用,输入输出结构都很像数据库的表==>记录==>字段,所以很多传统的应用的文件、数据库等都可以比较方便的映射到Lucene的存储结构/接口中。总体上看:可以先把Lucene当成一个支持全文索引的数据库系统

比较一下Lucene和数据库:

Lucene 数据库
索引数据源:doc(field1,field2...) doc(field1,field2...)
\ indexer /
_____________
| Lucene Index|
--------------
/ searcher \
结果输出:Hits(doc(field1,field2) doc(field1...))
 索引数据源:record(field1,field2...) record(field1..)
\ SQL: insert/
_____________
| DB Index |
-------------
/ SQL: select \
结果输出:results(record(field1,field2..) record(field1...))
Document:一个需要进行索引的“单元”
一个Document由多个字段组成
Record:记录,包含多个字段
Field:字段 Field:字段
Hits:查询结果集,由匹配的Document组成 RecordSet:查询结果集,由多个Record组成

全文检索 ≠ like "%keyword%"

通常比较厚的书籍后面常常附关键词索引表(比如:北京:12, 34页,上海:3,77页……),它能够帮助读者比较快地找到相关内容的页码。而数据库索引能够大大提高查询的速度原理也是一样,想像一下通过书后面的索引查找的速度要比一页一页地翻内容高多少倍……而索引之所以效率高,另外一个原因是它是排好序的。对于检索系统来说核心是一个排序问题

由于数据库索引不是为全文索引设计的,因此,使用like "%keyword%"时,数据库索引是不起作用的,在使用like查询时,搜索过程又变成类似于一页页翻书的遍历过程了,所以对于含有模糊查询的数据库服务来说,LIKE对性能的危害是极大的。如果是需要对多个关键词进行模糊匹配:like"%keyword1%" and like "%keyword2%" …其效率也就可想而知了。

所以建立一个高效检索系统的关键是建立一个类似于科技索引一样的反向索引机制,将数据源(比如多篇文章)排序顺序存储的同时,有另外一个排好序的关键词列表,用于存储关键词==>文章映射关系,利用这样的映射关系索引:[关键词==>出现关键词的文章编号,出现次数(甚至包括位置:起始偏移量,结束偏移量),出现频率],检索过程就是把模糊查询变成多个可以利用索引的精确查询的逻辑组合的过程。从而大大提高了多关键词查询的效率,所以,全文检索问题归结到最后是一个排序问题。

由此可以看出模糊查询相对数据库的精确查询是一个非常不确定的问题,这也是大部分数据库对全文检索支持有限的原因。Lucene最核心的特征是通过特殊的索引结构实现了传统数据库不擅长的全文索引机制,并提供了扩展接口,以方便针对不同应用的定制。

可以通过一下表格对比一下数据库的模糊查询:

  Lucene全文索引引擎 数据库
索引 将数据源中的数据都通过全文索引一一建立反向索引 对于LIKE查询来说,数据传统的索引是根本用不上的。数据需要逐个便利记录进行GREP式的模糊匹配,比有索引的搜索速度要有多个数量级的下降。
匹配效果 通过词元(term)进行匹配,通过语言分析接口的实现,可以实现对中文等非英语的支持。 使用:like "%net%" 会把netherlands也匹配出来,
多个关键词的模糊匹配:使用like "%com%net%":就不能匹配词序颠倒的xxx.net..xxx.com
匹配度 有匹配度算法,将匹配程度(相似度)比较高的结果排在前面。 没有匹配程度的控制:比如有记录中net出现5词和出现1次的,结果是一样的。
结果输出 通过特别的算法,将最匹配度最高的头100条结果输出,结果集是缓冲式的小批量读取的。 返回所有的结果集,在匹配条目非常多的时候(比如上万条)需要大量的内存存放这些临时结果集。
可定制性 通过不同的语言分析接口实现,可以方便的定制出符合应用需要的索引规则(包括对中文的支持) 没有接口或接口复杂,无法定制
结论 高负载的模糊查询应用,需要负责的模糊查询的规则,索引的资料量比较大 使用率低,模糊匹配规则简单或者需要模糊查询的资料量少

全文检索和数据库应用最大的不同在于:让最相关的头100条结果满足98%以上用户的需求

Lucene的创新之处:

大部分的搜索(数据库)引擎都是用B树结构来维护索引,索引的更新会导致大量的IO操作,Lucene在实现中,对此稍微有所改进:不是维护一个索引文件,而是在扩展索引的时候不断创建新的索引文件,然后定期的把这些新的小索引文件合并到原先的大索引中(针对不同的更新策略,批次的大小可以调整),这样在不影响检索的效率的前提下,提高了索引的效率。

Lucene和其他一些全文检索系统/应用的比较:

  Lucene 其他开源全文检索系统
增量索引和批量索引 可以进行增量的索引(Append),可以对于大量数据进行批量索引,并且接口设计用于优化批量索引和小批量的增量索引。 很多系统只支持批量的索引,有时数据源有一点增加也需要重建索引。
数据源 Lucene没有定义具体的数据源,而是一个文档的结构,因此可以非常灵活的适应各种应用(只要前端有合适的转换器把数据源转换成相应结构), 很多系统只针对网页,缺乏其他格式文档的灵活性。
索引内容抓取 Lucene的文档是由多个字段组成的,甚至可以控制那些字段需要进行索引,那些字段不需要索引,近一步索引的字段也分为需要分词和不需要分词的类型:
   需要进行分词的索引,比如:标题,文章内容字段
   不需要进行分词的索引,比如:作者/日期字段
缺乏通用性,往往将文档整个索引了
语言分析 通过语言分析器的不同扩展实现:
可以过滤掉不需要的词:an the of 等,
西文语法分析:将jumps jumped jumper都归结成jump进行索引/检索
非英文支持:对亚洲语言,阿拉伯语言的索引支持
缺乏通用接口实现
查询分析 通过查询分析接口的实现,可以定制自己的查询语法规则:
比如: 多个关键词之间的 + – and or关系等
 
并发访问 能够支持多用户的使用  

 

关于亚洲语言的的切分词问题(Word Segment)

对于中文来说,全文索引首先还要解决一个语言分析的问题,对于英文来说,语句中单词之间是天然通过空格分开的,但亚洲语言的中日韩文语句中的字是一个字挨一个,所有,首先要把语句中按“词”进行索引的话,这个词如何切分出来就是一个很大的问题。

首先,肯定不能用单个字符作(si-gram)为索引单元,否则查“上海”时,不能让含有“海上”也匹配。

但一句话:“北京天安门”,计算机如何按照中文的语言习惯进行切分呢?
“北京 天安门” 还是“北 京 天安门”?让计算机能够按照语言习惯进行切分,往往需要机器有一个比较丰富的词库才能够比较准确的识别出语句中的单词。

另外一个解决的办法是采用自动切分算法:将单词按照2元语法(bigram)方式切分出来,比如:
"北京天安门" ==> "北京 京天 天安 安门"。

这样,在查询的时候,无论是查询"北京" 还是查询"天安门",将查询词组按同样的规则进行切分:"北京","天安安门",多个关键词之间按与"and"的关系组合,同样能够正确地映射到相应的索引中。这种方式对于其他亚洲语言:韩文,日文都是通用的。

基于自动切分的最大优点是没有词表维护成本,实现简单,缺点是索引效率低,但对于中小型应用来说,基于2元语法的切分还是够用的。基于2元切分后的索引一般大小和源文件差不多,而对于英文,索引文件一般只有原文件的30%-40%不同,


自动切分 词表切分
实现 实现非常简单 实现复杂
查询 增加了查询分析的复杂程度, 适于实现比较复杂的查询语法规则
存储效率 索引冗余大,索引几乎和原文一样大 索引效率高,为原文大小的30%左右
维护成本 无词表维护成本 词表维护成本非常高:中日韩等语言需要分别维护。
还需要包括词频统计等内容
适用领域 嵌入式系统:运行环境资源有限
分布式系统:无词表同步问题
多语言环境:无词表维护成本
对查询和存储效率要求高的专业搜索引擎

目前比较大的搜索引擎的语言分析算法一般是基于以上2个机制的结合。关于中文的语言分析算法,大家可以在Google查关键词"wordsegment search"能找到更多相关的资料。

安装和使用

下载:http://jakarta.apache.org/lucene/

注意:Lucene中的一些比较复杂的词法分析是用JavaCC生成的(JavaCC:JavaCompilerCompiler,纯Java的词法分析生成器),所以如果从源代码编译或需要修改其中的QueryParser、定制自己的词法分析器,还需要从https://javacc.dev.java.net/下载javacc。

lucene的组成结构:对于外部应用来说索引模块(index)和检索模块(search)是主要的外部应用入口

org.apache.Lucene.search/ 搜索入口
org.apache.Lucene.index/ 索引入口
org.apache.Lucene.analysis/ 语言分析器
org.apache.Lucene.queryParser/ 查询分析器
org.apache.Lucene.document/ 存储结构
org.apache.Lucene.store/  底层IO/存储结构
org.apache.Lucene.util/ 一些公用的数据结构

简单的例子演示一下Lucene的使用方法:

索引过程:从命令行读取文件名(多个),将文件分路径(path字段)和内容(body字段)2个字段进行存储,并对内容进行全文索引:索引的单位是Document对象,每个Document对象包含多个字段Field对象,针对不同的字段属性和数据输出的需求,对字段还可以选择不同的索引/存储字段规则,列表如下:

方法 切词 索引 存储 用途
Field.Text(String name, String value) Yes Yes Yes 切分词索引并存储,比如:标题,内容字段
Field.Text(String name, Reader value) Yes Yes No 切分词索引不存储,比如:META信息,
不用于返回显示,但需要进行检索内容
Field.Keyword(String name, String value) No Yes Yes 不切分索引并存储,比如:日期字段
Field.UnIndexed(String name, String value) No No Yes 不索引,只存储,比如:文件路径
Field.UnStored(String name, String value) Yes Yes No 只全文索引,不存储
public class IndexFiles { 
//使用方法:: IndexFiles [索引输出目录] [索引的文件列表] ...
public static void main(String[] args) throws Exception {
String indexPath = args[0];
IndexWriter writer;
//用指定的语言分析器构造一个新的写索引器(第3个参数表示是否为追加索引)
writer = new IndexWriter(indexPath, new SimpleAnalyzer(), false);

for (int i=1; i<args.length; i++) {
System.out.println("Indexing file " + args[i]);
InputStream is = new FileInputStream(args[i]);

//构造包含2个字段Field的Document对象
//一个是路径path字段,不索引,只存储
//一个是内容body字段,进行全文索引,并存储
Document doc = new Document();
doc.add(Field.UnIndexed("path", args[i]));
doc.add(Field.Text("body", (Reader) new InputStreamReader(is)));
//将文档写入索引
writer.addDocument(doc);
is.close();
};
//关闭写索引器
writer.close();
}
}
 

索引过程中可以看到:

  • 语言分析器提供了抽象的接口,因此语言分析(Analyser)是可以定制的,虽然lucene缺省提供了2个比较通用的分析器SimpleAnalyser和StandardAnalyser,这2个分析器缺省都不支持中文,所以要加入对中文语言的切分规则,需要修改这2个分析器。
  • Lucene并没有规定数据源的格式,而只提供了一个通用的结构(Document对象)来接受索引的输入,因此输入的数据源可以是:数据库,WORD文档,PDF文档,HTML文档……只要能够设计相应的解析转换器将数据源构造成成Docuement对象即可进行索引。
  • 对于大批量的数据索引,还可以通过调整IndexerWrite的文件合并频率属性(mergeFactor)来提高批量索引的效率。

检索过程和结果显示:

搜索结果返回的是Hits对象,可以通过它再访问Document==>Field中的内容。

假设根据body字段进行全文检索,可以将查询结果的path字段和相应查询的匹配度(score)打印出来,

public class Search { 
public static void main(String[] args) throws Exception {
String indexPath = args[0], queryString = args[1];
//指向索引目录的搜索器
Searcher searcher = new IndexSearcher(indexPath);
//查询解析器:使用和索引同样的语言分析器
Query query = QueryParser.parse(queryString, "body",
new SimpleAnalyzer());
//搜索结果使用Hits存储
Hits hits = searcher.search(query);
//通过hits可以访问到相应字段的数据和查询的匹配度
for (int i=0; i<hits.length(); i++) {
System.out.println(hits.doc(i).get("path") + "; Score: " +
hits.score(i));
};
}
}

在整个检索过程中,语言分析器,查询分析器,甚至搜索器(Searcher)都是提供了抽象的接口,可以根据需要进行定制。

Hacking Lucene

简化的查询分析器

个人感觉lucene成为JAKARTA项目后,画在了太多的时间用于调试日趋复杂QueryParser,而其中大部分是大多数用户并不很熟悉的,目前LUCENE支持的语法:

Query ::= ( Clause )*
Clause ::= ["+", "-"] [<TERM> ":"] ( <TERM> | "(" Query ")")

中间的逻辑包括:and or + – &&||等符号,而且还有"短语查询"和针对西文的前缀/模糊查询等,个人感觉对于一般应用来说,这些功能有一些华而不实,其实能够实现目前类似于Google的查询语句分析功能其实对于大多数用户来说已经够了。所以,Lucene早期版本的QueryParser仍是比较好的选择。

添加修改删除指定记录(Document)

Lucene提供了索引的扩展机制,因此索引的动态扩展应该是没有问题的,而指定记录的修改也似乎只能通过记录的删除,然后重新加入实现。如何删除指定的记录呢?删除的方法也很简单,只是需要在索引时根据数据源中的记录ID专门另建索引,然后利用IndexReader.delete(Termterm)方法通过这个记录ID删除相应的Document。

根据某个字段值的排序功能

lucene缺省是按照自己的相关度算法(score)进行结果排序的,但能够根据其他字段进行结果排序是一个在LUCENE的开发邮件列表中经常提到的问题,很多原先基于数据库应用都需要除了基于匹配度(score)以外的排序功能。而从全文检索的原理我们可以了解到,任何不基于索引的搜索过程效率都会导致效率非常的低,如果基于其他字段的排序需要在搜索过程中访问存储字段,速度回大大降低,因此非常是不可取的。

但这里也有一个折中的解决方法:在搜索过程中能够影响排序结果的只有索引中已经存储的docID和score这2个参数,所以,基于score以外的排序,其实可以通过将数据源预先排好序,然后根据docID进行排序来实现。这样就避免了在LUCENE搜索结果外对结果再次进行排序和在搜索过程中访问不在索引中的某个字段值。

这里需要修改的是IndexSearcher中的HitCollector过程:

...
 scorer.score(new HitCollector() {
private float minScore = 0.0f;
public final void collect(int doc, float score) {
if (score > 0.0f && // ignore zeroed buckets
(bits==null || bits.get(doc))) { // skip docs not in bits
totalHits[0]++;
if (score >= minScore) {
/* 原先:Lucene将docID和相应的匹配度score例入结果命中列表中:
* hq.put(new ScoreDoc(doc, score)); // update hit queue
* 如果用doc 或 1/doc 代替 score,就实现了根据docID顺排或逆排
* 假设数据源索引时已经按照某个字段排好了序,而结果根据docID排序也就实现了
* 针对某个字段的排序,甚至可以实现更复杂的score和docID的拟合。
*/
hq.put(new ScoreDoc(doc, (float) 1/doc ));
if (hq.size() > nDocs) { // if hit queue overfull
hq.pop(); // remove lowest in hit queue
minScore = ((ScoreDoc)hq.top()).score; // reset minScore
}
}
}
}
}, reader.maxDoc());

更通用的输入输出接口

虽然lucene没有定义一个确定的输入文档格式,但越来越多的人想到使用一个标准的中间格式作为Lucene的数据导入接口,然后其他数据,比如PDF只需要通过解析器转换成标准的中间格式就可以进行数据索引了。这个中间格式主要以XML为主,类似实现已经不下4,5个:

数据源: WORD       PDF     HTML    DB       other
\ | | | /
XML中间格式
|
Lucene INDEX

目前还没有针对MSWord文档的解析器,因为Word文档和基于ASCII的RTF文档不同,需要使用COM对象机制解析。这个是我在Google上查的相关资料:http://www.intrinsyc.com/products/enterprise_applications.asp
另外一个办法就是把Word文档转换成text:http://www.winfield.demon.nl/index.html


索引过程优化

索引一般分2种情况,一种是小批量的索引扩展,一种是大批量的索引重建。在索引过程中,并不是每次新的DOC加入进去索引都重新进行一次索引文件的写入操作(文件I/O是一件非常消耗资源的事情)。

Lucene先在内存中进行索引操作,并根据一定的批量进行文件的写入。这个批次的间隔越大,文件的写入次数越少,但占用内存会很多。反之占用内存少,但文件IO操作频繁,索引速度会很慢。在IndexWriter中有一个MERGE_FACTOR参数可以帮助你在构造索引器后根据应用环境的情况充分利用内存减少文件的操作。根据我的使用经验:缺省Indexer是每20条记录索引后写入一次,每将MERGE_FACTOR增加50倍,索引速度可以提高1倍左右。

搜索过程优化

lucene支持内存索引:这样的搜索比基于文件的I/O有数量级的速度提升。
http://www.onjava.com/lpt/a/3273
而尽可能减少IndexSearcher的创建和对搜索结果的前台的缓存也是必要的。

Lucene面向全文检索的优化在于首次索引检索后,并不把所有的记录(Document)具体内容读取出来,而起只将所有结果中匹配度最高的头100条结果(TopDocs)的ID放到结果集缓存中并返回,这里可以比较一下数据库检索:如果是一个10,000条的数据库检索结果集,数据库是一定要把所有记录内容都取得以后再开始返回给应用结果集的。所以即使检索匹配总数很多,Lucene的结果集占用的内存空间也不会很多。对于一般的模糊检索应用是用不到这么多的结果的,头100条已经可以满足90%以上的检索需求。

如果首批缓存结果数用完后还要读取更后面的结果时Searcher会再次检索并生成一个上次的搜索缓存数大1倍的缓存,并再重新向后抓取。所以如果构造一个Searcher去查1-120条结果,Searcher其实是进行了2次搜索过程:头100条取完后,缓存结果用完,Searcher重新检索再构造一个200条的结果缓存,依此类推,400条缓存,800条缓存。由于每次Searcher对象消失后,这些缓存也访问那不到了,你有可能想将结果记录缓存下来,缓存数尽量保证在100以下以充分利用首次的结果缓存,不让Lucene浪费多次检索,而且可以分级进行结果缓存。

Lucene的另外一个特点是在收集结果的过程中将匹配度低的结果自动过滤掉了。这也是和数据库应用需要将搜索的结果全部返回不同之处。

我的一些尝试

  • 支持中文的Tokenizer:这里有2个版本,一个是通过JavaCC生成的,对CJK部分按一个字符一个TOKEN索引,另外一个是从SimpleTokenizer改写的,对英文支持数字和字母TOKEN,对中文按迭代索引。
  • 基于XML数据源的索引器:XMLIndexer,因此所有数据源只要能够按照DTD转换成指定的XML,就可以用XMLIndxer进行索引了。
  • 根据某个字段排序:按记录索引顺序排序结果的搜索器:IndexOrderSearcher,因此如果需要让搜索结果根据某个字段排序,可以让数据源先按某个字段排好序(比如:PriceField),这样索引后,然后在利用这个按记录的ID顺序检索的搜索器,结果就是相当于是那个字段排序的结果了。

从Lucene学到更多

Luene的确是一个面对对象设计的典范

  • 所有的问题都通过一个额外抽象层来方便以后的扩展和重用:你可以通过重新实现来达到自己的目的,而对其他模块而不需要;
  • 简单的应用入口Searcher, Indexer,并调用底层一系列组件协同的完成搜索任务;
  • 所有的对象的任务都非常专一:比如搜索过程:QueryParser分析将查询语句转换成一系列的精确查询的组合(Query),通过底层的索引读取结构IndexReader进行索引的读取,并用相应的打分器给搜索结果进行打分/排序等。所有的功能模块原子化程度非常高,因此可以通过重新实现而不需要修改其他模块。 
  • 除了灵活的应用接口设计,Lucene还提供了一些适合大多数应用的语言分析器实现(SimpleAnalyser,StandardAnalyser),这也是新用户能够很快上手的重要原因之一。

这些优点都是非常值得在以后的开发中学习借鉴的。作为一个通用工具包,Lunece的确给予了需要将全文检索功能嵌入到应用中的开发者很多的便利。

此外,通过对Lucene的学习和使用,我也更深刻地理解了为什么很多数据库优化设计中要求,比如:

  • 尽可能对字段进行索引来提高查询速度,但过多的索引会对数据库表的更新操作变慢,而对结果过多的排序条件,实际上往往也是性能的杀手之一。
  • 很多商业数据库对大批量的数据插入操作会提供一些优化参数,这个作用和索引器的merge_factor的作用是类似的,
  • 20%/80%原则:查的结果多并不等于质量好,尤其对于返回结果集很大,如何优化这头几十条结果的质量往往才是最重要的。
  • 尽可能让应用从数据库中获得比较小的结果集,因为即使对于大型数据库,对结果集的随机访问也是一个非常消耗资源的操作。

参考资料:

Apache: Lucene Project
http://jakarta.apache.org/lucene/
Lucene开发/用户邮件列表归档
Lucene-dev@jakarta.apache.org
Lucene-user@jakarta.apache.org

The Lucene search engine: Powerful, flexible, and free
http://www.javaworld.com/javaworld/jw-09-2000/jw-0915-Lucene_p.html

Lucene Tutorial
http://www.darksleep.com/puff/lucene/lucene.html

Notes on distributed searching with Lucene
http://home.clara.net/markharwood/lucene/

中文语言的切分词
http://www.google.com/search?sourceid=navclient&hl=zh-CN&q=chinese+word+segment

搜索引擎工具介绍
http://searchtools.com/

Lucene作者Cutting的几篇论文和专利
http://lucene.sourceforge.net/publications.html 

Lucene的.NET实现:dotLucene
http://sourceforge.net/projects/dotlucene/

Lucene作者Cutting的另外一个项目:基于Java的搜索引擎Nutch
http://www.nutch.org/   http://sourceforge.net/projects/nutch/

关于基于词表和N-Gram的切分词比较
http://china.nikkeibp.co.jp/cgi-bin/china/news/int/int200302100112.html

2005-01-08 Cutting在Pisa大学做的关于Lucene的讲座:非常详细的Lucene架构解说

特别感谢:
前网易CTO许良杰(Jack Xu)给我的指导:是您将我带入了搜索引擎这个行业。

原文出处:<ahref="http://www.chedong.com/tech/lucene.html">http://www.chedong.com/tech/lucene.html</a>
<<返回

<<返回首页

from:       http://help.cmsware.com/manual/1137993483d163.html


全文检索系统与Lucene简介

一、 什么是全文检索与全文检索系统?

全文检索是指计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。这个过程类似于通过字典中的检索字表查字的过程。

全文检索的方法主要分为按字检索和按词检索两种。按字检索是指对于文章中的每一个字都建立索引,检索时将词分解为字的组合。对于各种不同的语言而言,字有不同的含义,比如英文中字与词实际上是合一的,而中文中字与词有很大分别。按词检索指对文章中的词,即语义单位建立索引,检索时按词检索,并且可以处理同义项等。英文等西方文字由于按照空白切分词,因此实现上与按字处理类似,添加同义处理也很容易。中文等东方文字则需要切分字词,以达到按词索引的目的,关于这方面的问题,是当前全文检索技术尤其是中文全文检索技术中的难点,在此不做详述。

全文检索系统是按照全文检索理论建立起来的用于提供全文检索服务的软件系统。一般来说,全文检索需要具备建立索引和提供查询的基本功能,此外现代的全文检索系统还需要具有方便的用户接口、面向WWW的开发接口、二次应用开发接口等等。功能上,全文检索系统核心具有建立索引、处理查询返回结果集、增加索引、优化索引结构等等功能,外围则由各种不同应用具有的功能组成。结构上,全文检索系统核心具有索引引擎、查询引擎、文本分析引擎、对外接口等等,加上各种外围应用系统等等共同构成了全文检索系统。图1.1展示了上述全文检索系统的结构与功能。

img

在上图中,我们看到:全文检索系统中最为关键的部分是全文检索引擎,各种应用程序都需要建立在这个引擎之上。一个全文检索应用的优异程度,根本上由全文检索引擎来决定。因此提升全文检索引擎的效率即是我们提升全文检索应用的根本。另一个方面,一个优异的全文检索引擎,在做到效率优化的同时,还需要具有开放的体系结构,以方便程序员对整个系统进行优化改造,或者是添加原有系统没有的功能。比如在当今多语言处理的环境下,有时需要给全文检索系统添加处理某种语言或者文本格式的功能,比如在英文系统中添加中文处理功能,在纯文本系统中添加XML或者HTML格式的文本处理功能,系统的开放性和扩充性就十分的重要。

二、 什么是Lucene

Luceneapache软件基金会的一个子项目,是一个开放源代码的全文检索引擎工具包,即它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎(英文与德文两种西方语言)。Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。

Lucene的原作者是Doug Cutting,他是一位资深全文索引/检索专家,曾经是V-Twin搜索引擎的主要开发者,后在Excite担任高级系统架构设计师,目前从事于一些Internet底层架构的研究。早先发布在作者自己的http://www.lucene.com/,后来发布在SourceForge2001年年底成为apache软件基金会的一个子项目:http://lucene.apache.org/

三、 Lucene的应用、特点及优势

作为一个开放源代码项目,Lucene从问世之后,引发了开放源代码社群的巨大反响,程序员们不仅使用它构建具体的全文检索应用,而且将之集成到各种系统软件中去,以及构建Web应用,甚至某些商业软件也采用了Lucene作为其内部全文检索子系统的核心。apache软件基金会的网站使用了Lucene作为全文检索的引擎,IBM的开源软件eclipse2.1版本中也采用了Lucene作为帮助子系统的全文索引引擎,相应的IBM的商业软件Web Sphere中也采用了LuceneLucene以其开放源代码的特性、优异的索引结构、良好的系统架构获得了越来越多的应用。

Lucene作为一个全文检索引擎,其具有如下突出的优点:

1)索引文件格式独立于应用平台。Lucene定义了一套以8位字节为基础的索引文件格式,使得兼容系统或者不同平台的应用能够共享建立的索引文件。

2)在传统全文检索引擎的倒排索引的基础上,实现了分块索引,能够针对新的文件建立小文件索引,提升索引速度。然后通过与原有索引的合并,达到优化的目的。

3)优秀的面向对象的系统架构,使得对于Lucene扩展的学习难度降低,方便扩充新功能。

4)设计了独立于语言和文件格式的文本分析接口,索引器通过接受Token流完成索引文件的创立,用户扩展新的语言和文件格式,只需要实现文本分析的接口。

5)已经默认实现了一套强大的查询引擎,用户无需自己编写代码即使系统可获得强大的查询能力,Lucene的查询实现中默认实现了布尔操作、模糊查询(Fuzzy Search)、分组查询等等。

面对已经存在的商业全文检索引擎,Lucene也具有相当的优势。首先,它的开发源代码发行方式(遵守Apache Software License),在此基础上程序员不仅仅可以充分的利用Lucene所提供的强大功能,而且可以深入细致的学习到全文检索引擎制作技术和面相对象编程的实践,进而在此基础上根据应用的实际情况编写出更好的更适合当前应用的全文检索引擎。在这一点上,商业软件的灵活性远远不及Lucene。其次,Lucene秉承了开放源代码一贯的架构优良的优势,设计了一个合理而极具扩充能力的面向对象架构,程序员可以在Lucene的基础上扩充各种功能,比如扩充中文处理能力,从文本扩充到HTMLPDF[13]等等文本格式的处理,编写这些扩展的功能不仅仅不复杂,而且由于Lucene恰当合理的对系统设备做了程序上的抽象,扩展的功能也能轻易的达到跨平台的能力。最后,转移到apache软件基金会后,借助于apache软件基金会的网络平台,程序员可以方便的和开发者、其它程序员交流,促成资源的共享,甚至直接获得已经编写完备的扩充功能。最后,虽然Lucene使用Java语言写成,但是开放源代码社区的程序员正在不懈的将之使用各种传统语言实现(例如.net framework),在遵守Lucene索引文件格式的基础上,使得Lucene能够运行在各种各样的平台上,系统管理员可以根据当前的平台适合的语言来合理的选择。

from:

http://www.discuz.net/viewthread.php?tid=139721

到 3.23.23 时,MySQL 开始支持全文索引和搜索。全文索引在 MySQL 中是一个 FULLTEXT 类型索引。FULLTEXT 索引用于 MyISAM 表,可以在 CREATE TABLE 时或之后使用 ALTER TABLE 或 CREATE INDEX 在 CHAR、VARCHAR 或 TEXT 列上创建。对于大的数据库,将数据装载到一个没有 FULLTEXT 索引的表中,然后再使用 ALTER TABLE (或 CREATE INDEX) 创建索引,这将是非常快的。将数据装载到一个已经有 FULLTEXT 索引的表中,将是非常慢的。

全文搜索通过 MATCH() 函数完成。

mysql> CREATE TABLE articles (
    ->   id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
    ->   title VARCHAR(200),
    ->   body TEXT,
    ->   FULLTEXT (title,body)
    -> );
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO articles VALUES
    -> (NULL,’MySQL Tutorial’, ‘DBMS stands for DataBase …’),
    -> (NULL,’How To Use MySQL Efficiently’, ‘After you went through a …’),
    -> (NULL,’Optimising MySQL’,'In this tutorial we will show …’),
    -> (NULL,’1001 MySQL Tricks’,'1. Never run mysqld as root. 2. …’),
    -> (NULL,’MySQL vs. YourSQL’, ‘In the following database comparison …’),
    -> (NULL,’MySQL Security’, ‘When configured properly, MySQL …’);
Query OK, 6 rows affected (0.00 sec)
Records: 6  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM articles
    ->          WHERE MATCH (title,body) AGAINST (‘database’);
+—-+——————-+——————————————+
| id | title             | body                                     |
+—-+——————-+——————————————+
|  5 | MySQL vs. YourSQL | In the following database comparison … |
|  1 | MySQL Tutorial    | DBMS stands for DataBase …             |
+—-+——————-+——————————————+
2 rows in set (0.00 sec)

函数 MATCH() 对照一个文本集(包含在一个 FULLTEXT 索引中的一个或多个列的列集)执行一个自然语言搜索一个字符串。搜索字符串做为 AGAINST() 的参数被给定。搜索以忽略字母大小写的方式执行。对于表中的每个记录行,MATCH() 返回一个相关性值。即,在搜索字符串与记录行在 MATCH() 列表中指定的列的文本之间的相似性尺度。

当 MATCH() 被使用在一个 WHERE 子句中时 (参看上面的例子),返回的记录行被自动地以相关性从高到底的次序排序。相关性值是非负的浮点数字。零相关性意味着不相似。相关性的计算是基于:词在记录行中的数目、在行中唯一词的数目、在集中词的全部数目和包含一个特殊词的文档(记录行)的数目。

它也可以执行一个逻辑模式的搜索。这在下面的章节中被描述。

前面的例子是函数 MATCH() 使用上的一些基本说明。记录行以相似性递减的顺序返回。

下一个示例显示如何检索一个明确的相似性值。如果即没有 WHERE 也没有 ORDER BY 子句,返回行是不排序的。

mysql> SELECT id,MATCH (title,body) AGAINST (‘Tutorial’) FROM articles;
+—-+—————————————–+
| id | MATCH (title,body) AGAINST (‘Tutorial’) |
+—-+—————————————–+
|  1 |                        0.64840710366884 |
|  2 |                                       0 |
|  3 |                        0.66266459031789 |
|  4 |                                       0 |
|  5 |                                       0 |
|  6 |                                       0 |
+—-+—————————————–+
6 rows in set (0.00 sec)

下面的示例更复杂一点。查询返回相似性并依然以相似度递减的次序返回记录行。为了完成这个结果,你应该指定 MATCH() 两次。这不会引起附加的开销,因为 MySQL 优化器会注意到两次同样的 MATCH() 调用,并只调用一次全文搜索代码。

mysql> SELECT id, body, MATCH (title,body) AGAINST
    -> (‘Security implications of running MySQL as root’) AS score
    -> FROM articles WHERE MATCH (title,body) AGAINST
    -> (‘Security implications of running MySQL as root’);
+—-+————————————-+—————–+
| id | body                                | score           |
+—-+————————————-+—————–+
|  4 | 1. Never run mysqld as root. 2. … | 1.5055546709332 |
|  6 | When configured properly, MySQL … |   1.31140957288 |
+—-+————————————-+—————–+
2 rows in set (0.00 sec)

MySQL 使用一个非常简单的剖析器来将文本分隔成词。一个“词”是由文字、数据、“’” 和 “_” 组成的任何字符序列。任何在 stopword 列表上出现的,或太短的(3 个字符或更少的)的 “word” 将被忽略。

在集和查询中的每个合适的词根据其在集与查询中的重要性衡量。这样,一个出现在多个文档中的词将有较低的权重(可能甚至有一个零权重),因为在这个特定的集中,它有较低的语义值。否则,如果词是较少的,它将得到一个较高的权重。然后,词的权重将被结合用于计算记录行的相似性。

这样一个技术工作可很好地工作与大的集(实际上,它会小心地与之谐调)。 对于非常小的表,词分类不足以充份地反应它们的语义值,有时这个模式可能产生奇怪的结果。

mysql> SELECT * FROM articles WHERE MATCH (title,body) AGAINST (‘MySQL’);
Empty set (0.00 sec)

在上面的例子中,搜索词 MySQL 却没有得到任何结果,因为这个词在超过一半的记录行中出现。同样的,它被有效地处理为一个 stopword (即,一个零语义值的词)。这是最理想的行为 — 一个自然语言的查询不应该从一个 1GB 的表中返回每个次行(second row)。

匹配表中一半记录行的词很少可能找到相关文档。实际上,它可能会发现许多不相关的文档。我们都知道,当我们在互联网上通过搜索引擎试图搜索某些东西时,这会经常发生。因为这个原因,在这个特殊的数据集中,这样的行被设置一个低的语义值。

到 4.0.1 时,MySQL 也可以使用 IN BOOLEAN MODE 修饰语来执行一个逻辑全文搜索。

mysql> SELECT * FROM articles WHERE MATCH (title,body)
    ->     AGAINST (‘+MySQL -YourSQL’ IN BOOLEAN MODE);
+—-+——————————+————————————-+
| id | title                        | body                                |
+—-+——————————+————————————-+
|  1 | MySQL Tutorial               | DBMS stands for DataBase …        |
|  2 | How To Use MySQL Efficiently | After you went through a …        |
|  3 | Optimising MySQL             | In this tutorial we will show …   |
|  4 | 1001 MySQL Tricks            | 1. Never run mysqld as root. 2. … |
|  6 | MySQL Security               | When configured properly, MySQL … |
+—-+——————————+————————————-+

这个查询返回所有包含词 MySQL 的记录行(注意: 50% 的阈值没有使用),但是它没有包含词 YourSQL。注意,一个逻辑模式的搜索不会自动地以相似值的降序排序记录行。你可以从上面的结果出看得出来,最高的相似值(包含 MySQL 两次的那个) 最列在最后,而不是第一位。一个逻辑全文搜索即使在没有一个 FULLTEXT 索引的情况下也可以工作,然而它 慢 些。

逻辑全文搜索支持下面的操作符:

+
一个领头的加号表示,该词必须出现在每个返回的记录行中。

-
一个领头的减号表示,该词必须不出现在每个返回的记录行中。

缺省的 (当既没有加号也没有负号被指定时)词是随意的,但是包含它的记录行将被排列地更高一点。这个模仿没有 IN BOOLEAN MODE 修饰词的 MATCH() … AGAINST() 的行为。

< >
这两个操作符用于改变一个词的相似性值的基值。< 操作符减少基值,> 操作符则增加它。参看下面的示例。

( )
圆括号用于对子表达式中的词分组。

~
一个领头的否定号的作用象一个否定操作符,引起行相似性的词的基值为负的。它对标记一个噪声词很有用。一个包含这样的词的记录将被排列得低一点,但是不会被完全的排除,因为这样可以使用 – 操作符。

*
一个星号是截断操作符。不想其它的操作符,它应该被追加到一个词后,不加在前面。

"
短语,被包围在双引号"中,只匹配包含这个短语(字面上的,就好像被键入的)的记录行。
这里是一些示例:

apple banana
找至少包含上面词中的一个的记录行
+apple +juice
… 两个词均在被包含
+apple macintosh
… 包含词 “apple”,但是如果同时包含 “macintosh”,它的排列将更高一些
+apple -macintosh
… 包含 “apple” 但不包含 “macintosh”
+apple +(>pie <strudel)
… 包含 “apple” 和 “pie”,或者包含的是 “apple” 和 “strudel” (以任何次序),但是 “apple pie” 排列得比 “apple strudel” 要高一点
apple*
… 包含 “apple”,“apples”,“applesauce” 和 “applet”
"some words"
… 可以包含 “some words of wisdom”,但不是 “some noise words”
6.8.1 全文的限制
MATCH() 函数的所有参数必须是从来自于同一张表的列,同时必须是同一个FULLTEXT 索引中的一部分,除非 MATCH() 是 IN BOOLEAN MODE 的。

MATCH() 列列表必须确切地匹配表的某一 FULLTEXT 索引中定义的列列表,除非 MATCH() 是 IN BOOLEAN MODE 的。

AGAINST() 的参数必须是一个常量字符串。
6.8.2 微调 MySQL 全文搜索
不幸地,全文搜索仍然只有很少的用户可调参数,虽然增加一些在 TODO 上排列很高。如果你有一个 MySQL 源码发行(查看章节 2.3 安装一个 MySQL 源码发行),你可以发挥对全文搜索的更多控制。

注意,全文搜索为最佳的搜索效果,被仔细地调整了。修改默认值的行为,在大多数情况下,只会使搜索结果更糟。不要修改 MySQL 的源代码,除非你知道你在做什么!

被索引的词的最小长度由 MySQL 变量 ft_min_word_len 指定。查看章节 4.5.6.4 SHOW VARIABLES。将它改为你所希望的值,并重建你的 FULLTEXT 索引。 (这个变量只从 MySQL 4.0 开始被支持)

stopword 列表可以从 ft_stopword_file 变量指定的文件中读取。查看章节 4.5.6.4 SHOW VARIABLES。在修改了 stopword 列表后,重建你的 FULLTEXT 索引。(这个变量只从 MySQL 4.0.10 开始被支持)

50% 阈值选择由所选择的特殊的衡量模式确定。为了禁止它,修改 `myisam/ftdefs.h’ 文件中下面的一行:
#define GWS_IN_USE GWS_PROB

改为:
#define GWS_IN_USE GWS_FREQ

然后重新编译 MySQL。在这种情况下,不需要重建索引。 注意:使用了这个,将严重地减少 MySQL 为 MATCH() 提供足够的相似性值的能力。如果你确实需要搜索这样的公共词,最好使用 IN BOOLEAN MODE 的搜索代替,它不遵守 50% 的阈值。

有时,搜索引擎维护员希望更改使用于逻辑全文搜索的操作符。这些由变量 ft_boolean_syntax 定义。查看章节 4.5.6.4 SHOW VARIABLES。然而,这个变量是只读的,它的值在 `myisam/ft_static.c’ 中被设置。
对于这些更改,要求你重建你的 FULLTEXT 索引,对于一个 MyISAM 表,最容易的重建索引文件的方式如下面的语句:

mysql> REPAIR TABLE tbl_name QUICK;

6.8.3 全文搜索 TODO
使所有对 FULLTEXT 索引的操作更快
邻近(Proximity)操作符
对 "always-index words" 的支持。他们可以是用户希望视为一个词处理的任意字符串,例如 "C++"、"AS/400"、"TCP/IP",等等
支持在 MERGE 表中的全文搜索
对多字节字符的支持
依照数据的语言建立 stopword 列表
Stemming (当然,依赖于数据的语言)
Generic user-suppliable UDF preparser.
使模式更加灵活 (通过为 CREATE/ALTER TABLE 中的 FULLTEXT 增加某些可调整参数)

from;   http://www.vckbase.com/document/viewdoc/?id=1173

全文信息检索介绍及算法分析

作者:杨老师

一、摘要
  
本文主要介绍了全文信息检索的概念、应用领域、算法分类、技术难点和算法比较。及一款实现全文检索的数据结构和算法。

、什么是全文数据库和全文信息检索
  保存在数据库中的记录数据,从类型上可以分为两种。其一是结构化数据,象字符、日期、数值、货币等,这些数据都是具有有限长度或固定格式的数据;其二是非结构化数据,也叫全文数据,象简历、简介、论文等,这些数据都是以不定长、非固定格式保存的字符型数据。
  现有的数据库系统,都是以结构化数据为检索的主要目标,因为实现相对简单。比如数值检索,可以建立一张排序好的索引表,以二分法实现查找,速度很快。但对于非结构化数据,即全文数据,要想实现检索,相对难度要大的很多了。
  当然,你也许会说:“这个多简单呀,把全文数据读到内存,然后进行比较查找不就可以了?”。不错,的确是一个很朴素想法。不过最严重的问题是,如果数据库中有1万条,10万条,100万条记录的话,可以想象一下检索所消耗的时间了吧?!如果一个全文数据库系统,对一条检索命令的响应时间超过了半分钟,那么没有用户是能够容忍的了。
  因此,全文检索的主要目的,就是实现对大容量的非结构化数据的快速查找。

三、应用领域
  现在,随着计算机使用的越来越普及,数据的积累越来越多,全文检索的要求也就越来越迫切了。目前,主要的应用领域是:图书馆数据库、情报数据库、专利数据库、医药数据库、办公自动化、历史资料库、电子出版系统、等等。

四、算法和算法比较
  目前,实现全文信息检索的算法有两大基本方案,词索引字索引
  词索引,以单词为索引单位的检索算法。这个技术是全文检索技术的鼻祖(60年代,就已经有产品问世)。道理很简单,计算机是适合于英语语言环境的,而英语又是以单词为语言要素。说的更通俗一些,就是每个英文单词之间都有一个空格。因此,在对全文数据库建立索引的时候,按照单词划分建立索引,是即简单又自然的。我们国家最开始引入全文检索技术的时候,是汉化英文的数据库系统,因此也就自然使用了词索引技术。但由于中英文环境中语素的不同特点,使得中文必须要解决分词的问题。比如对一句话“我是中国人”,那么必须要切分出“我 是 中国 人”这样的单词形式。如果是人的大脑来进行分词判断,那真是太简单了,只要有小学二年级的中文水平,就足够了。但是,如果想让计算机能够进行分词,却非常困难。计算机分词的大致算法是:由文章切分出段落,由段落切分出句子,由句子切分出短语,然后查找词库,根据动词、连词、形容词再进行切分得到所有的单词。在某些情况下,计算机是根本无法正确进行分词的。下面是计算机自动分词所闹的笑话:

  (1)我们要积极地主动作好计划生育工作
计算机愚蠢的分词结果:我们 要 积极 地主 动作 好 计划 生育 工作
评论:我胡汉三又回来啦
后果:检索“地主”的时候,产生误查结果

  (2)吉林省长春市的人民
计算机愚蠢的分词结果:吉林 省长 春市 的 人民
评论:我知道了,吉林有个省长叫“春市”
后果:检索“吉林省”的时候,产生漏查结果

  因此,词索引的技术难点是分词算法。Oracle 和 Notes 等汉化的数据库系统,虽然也都提供了部分全文检索功能,但都出现了这样或那样的问题。分词算法的提升空间还是有的,需要加入人工智能分析、上下文判断等技术。但还有一个致命的弱点,那就是对地名,人名的判断。

  字索引,以汉语单字为索引单位的检索算法。这个也是我推荐的算法,较词索引更适合于中文环境。这也就是为什么英文汉化版的全文检索软件没有占领中国市场的主要原因。(目前,本土民族化的软件,比如手写板,汉字扫描识别,中文全文信息检索……还是比国外的同类产品领先很多的。)但字索引也不是没有缺点,最主要的问题是:

  (1)、检索“华人”,会误查出“中华人民共和国”
  (2)、检索中药“大黄”,会误查出“大黄缄”,“大黄麻”等完全不同概念的药品。而这些单词在英文中是不会出现错误的,因为根本就是不同的拼写。

  字索引的多查错误,也是可以更正的。比如检索“大黄”的同时,也检索“大黄缄”,然后排除“大黄缄”的检索命中点,但这需要付出检索时间的代价。下表,是字、词索引方案各项性能的比较:

索引方式

索引比

索引速度

检索速度

误查

漏查

词索引

0.8 ~ 2.0

字索引

0.3 ~ 2.0

稍快

稍慢

五、一款中文全文信息检索算法的实现
  这个算法是1993年设计并实现的。十年过去了,新版本中使用了更高级的技术,因此该算法已经废弃,并得以公开给大家。计算机界有一个著名的命题:程序 = 数据结构 + 算法。而公开的这个设计,正是数据结构和算法的完美体现。呵呵,不吹牛了,看看如何实现的吧。

I 基本原理

  图(一) 展示了在数据结构中,最标准的一个单向链表的结构示意图。
  图(二) 在标准的单向链表中,如果结点内容A、B……N 完全相同的时候,那么我们可以进行变换了,用图(二)表示,为叙述方便,称为“单一结点内容链表”。即,只需要在头结点中表示出内容,链表中后续的结点只有 NEXT 指针域,而省略掉内容域。
  图(三) 在图二的“单一结点内容链表”中有一个缺点:如果我们得到了任意的一个非头结点指针的话,由于是单向链表,因此无法回溯得到该结点所表示的内容。因此再次变换,改进为图(三)的方式,称为“接续单向链表”。这样结构的链表有如下的特征:
  (1)从头结点出发,可以得到后续所有结点的地址;
  (2)从任意一个结点出发,当沿着指针跳转到“接续”结点的时候,就可以得到该结点所表示的内容;
  (3)“接续链表”的存储空间,比标准的单向链表的存储空间要小很多。

II 构造头结点
  设计一个数组,用来存储所有汉字链表的头结点。GB2312包容了汉字共7673个,用2个字节来表示一个汉字的内码,第一个字节的范围是A0~F7,第二个字节的范围是A0~FE。恰好的是,7673个汉字,加上一些常用符号,制表符号,再刨除一些未定义的空区,正好完全可以用16K字节的存储空间来全部表示出来。

  和带有接点内容的链表有所不同的是,在这个头结点中,只有指针而没有结点内容。结点内容(汉字)其实是用数组的偏移地址来间接表示的。如上图所示,数组中偏移为 0000 的指针所表示的汉字是 A0A0 (即,全角空格)。

III 链表结构
  由于每个指针只用2个字节表示,因此,链表指针的范围是 0000 – FFFF,64K大小。为了使链表能超越 64K 的范围,那么现在正好可以用“接续链表”构造出来了。下图表现了整个数据库的结构。


IV 实现检索
  在下图的一个数据库结构中,我们实现单字“中”的检索和单词“中国”的检索。三种颜色,分别表示数据库中的三条记录的内容区域。

  检索“中”。根据“中”的内码 D6D0 可以在头指针区域中定位到起点指针,然后按照链表分别在数据库第一条记录找到2个命中点,在第二条记录中找到1个命中点,而第三条记录没有命中。这时,指针到达“接续的头指针”区域,然后就可以继续检索下一个数据区了……直到结束。
  检索“中国”。根据“中”和“国”的内码,定位头指针。这一对指针连续在链表中跳转,如果两个指针在跳转的时候恰好在数据区的地址偏移中相邻,则表示命中这个单词了(第一条记录中,画红圈部分)。然后,这对指针继续向后跳转,直到结束。

V 提取记录内容
  由于记录内容中的汉字,已经用链表的指针表示了,那么如何提取还原出原始的汉字文章那?从数据区记录的开始,按照链表向后跳转,肯定会跳转到“接续头指针”的区域中,那么这时候根据指针所在地址,计算出相对偏移,就得到它表示的汉字了。重复操作,就能把原始的汉字文章全部还原出来。

VI 记录的入库
  不用多说了吧?把指向“接续头指针”的链表断开,连接上新的地址,而该地址的存储的指针指向“接续头指针”。

VII 算法特点
  (1)算法呈现出字索引方式。能够满足“查全”的功能。
  (2)原始文本数据追加入库时,由于入库就是构造指针的过程,并同时建立了索引。入库数据量和处理时间呈线性关系。速度很快。
  (3)文字信息在数据库中全部以指针存储,本身具有加密的功能。
  (4)如果有某个指针在存取过程中发生错误,则该指针的后续必将产生连续错误且无法纠正,这是该算法的主要缺陷。
  (5)数据区48K,头指针(接续)区16K,索引比为33%。这是全世界现有全文算法中,索引比最小的。
  (6)该算法只适合于GB2312字符集。由于GBK,UNICODE,BIG5包含更多的汉字,会导致头指针(接续)区域的扩大,而数据区被压缩,因此失去了算法意义。
  (7)检索时,需要按照64K为单位读取数据库文件(这正好适用于16位操作系统),检索效率会随着数据库的增大而减小。因此该算法适合于小型数据库的全文检索系统(10万条记录以内,总容量200M以下)。

六、结束语
  学习计算机软件设计,最重要的一门课程就是《数据结构》。这套全文检索算法,正是依靠精妙的数据结构构建出来的,其实说起来也很简单,就是链表数组。1993年的时候,PC机大多运行着 DOS + WINDOWS 3.1 + 中文环境(GB2312),这套算法正是适应当时的环境而设计的。现在十年过去了,随着计算机软硬环境的提升和全文数据量的增长,该算法虽然已经废弃,但通过该算法,大家一定能体会出“数据结构”的重要性。1998年,我又设计了新的全文检索算法,使用的是“数据结构”中的“散列表”。不过现在保密,也许10年后再公布吧,嘿嘿。