Lucene 分词原理

Lucene 分词原理

Lucene是一个高性能的java全文检索工具包,它使用的是倒排文件索引结构。该结构及相应的生成算法如下:


0)设有两篇文章1和2
文章1的内容为:Tom lives in Guangzhou,I live in Guangzhou too.
文章2的内容为:He once lived in Shanghai.


1)由于lucene是基于关键词索引和查询的,首先我们要取得这两篇文章的关键词,通常我们需要如下处理措施
a.我们现在有的是文章内容,即一个字符串,我们先要找出字符串中的所有单词,即分词。英文单词由于用空格分隔,比较好处理。中文单词间是连在一起的需要特殊的分词处理。
b.文章中的”in”, “once” “too”等词没有什么实际意义,中文中的“的”“是”等字通常也无具体含义,这些不代表概念的词可以过滤掉
c.用户通常希望查“He”时能把含“he”,“HE”的文章也找出来,所以所有单词需要统一大小写。
d.用户通常希望查“live”时能把含“lives”,“lived”的文章也找出来,所以需要把“lives”,“lived”还原成“live”
e.文章中的标点符号通常不表示某种概念,也可以过滤掉
在lucene中以上措施由Analyzer类完成


经过上面处理后
文章1的所有关键词为:[tom] [live] [guangzhou] [live] [guangzhou]
文章2的所有关键词为:[he] [live] [shanghai]


2) 有了关键词后,我们就可以建立倒排索引了。上面的对应关系是:“文章号”对“文章中所有关键词”。倒排索引把这个关系倒过来,变成:“关键词”对“拥有该关键词的所有文章号”。文章1,2经过倒排后变成
关键词 文章号
guangzhou 1
he 2
i 1
live 1,2
shanghai 2
tom 1


通常仅知道关键词在哪些文章中出现还不够,我们还需要知道关键词在文章中出现次数和出现的位置,通常有两种位置:a)字符位置,即记录该词是文章中第几个字符(优点是关键词亮显时定位快);b)关键词位置,即记录该词是文章中第几个关键词(优点是节约索引空间、词组(phase)查询快),lucene中记录的就是这种位置。


加上“出现频率”和“出现位置”信息后,我们的索引结构变为:
关键词 文章号[出现频率] 出现位置
guangzhou 1[2] 3,6
he 2[1] 1
i 1[1] 4
live 1[2],2[1] 2,5,2
shanghai 2[1] 3
tom 1[1] 1


以live 这行为例我们说明一下该结构:live在文章1中出现了2次,文章2中出现了一次,它的出现位置为“2,5,2”这表示什么呢?我们需要结合文章号和出现频率来分析,文章1中出现了2次,那么“2,5”就表示live在文章1中出现的两个位置,文章2中出现了一次,剩下的“2”就表示live是文章2中第 2个关键字。


以上就是lucene索引结构中最核心的部分。我们注意到关键字是按字符顺序排列的(lucene没有使用B树结构),因此lucene可以用二元搜索算法快速定位关键词。


实现时 lucene将上面三列分别作为词典文件(Term Dictionary)、频率文件(frequencies)、位置文件 (positions)保存。其中词典文件不仅保存有每个关键词,还保留了指向频率文件和位置文件的指针,通过指针可以找到该关键字的频率信息和位置信息。


Lucene中使用了field的概念,用于表达信息所在位置(如标题中,文章中,url中),在建索引中,该field信息也记录在词典文件中,每个关键词都有一个field信息(因为每个关键字一定属于一个或多个field)。


为了减小索引文件的大小,Lucene对索引还使用了压缩技术。首先,对词典文件中的关键词进行了压缩,关键词压缩为<前缀长度,后缀>,例如:当前词为“阿拉伯语”,上一个词为“阿拉伯”,那么“阿拉伯语”压缩为<3,语>。其次大量用到的是对数字的压缩,数字只保存与上一个值的差值(这样可以减小数字的长度,进而减少保存该数字需要的字节数)。例如当前文章号是16389(不压缩要用3个字节保存),上一文章号是16382,压缩后保存7(只用一个字节)。


下面我们可以通过对该索引的查询来解释一下为什么要建立索引。
假设要查询单词 “live”,lucene先对词典二元查找、找到该词,通过指向频率文件的指针读出所有文章号,然后返回结果。词典通常非常小,因而,整个过程的时间是毫秒级的。
而用普通的顺序匹配算法,不建索引,而是对所有文章的内容进行字符串匹配,这个过程将会相当缓慢,当文章数目很大时,时间往往是无法忍受的。
 
 
 
lucene提供的demo程序中只支持英文的索引,下文将介绍如何在demo中添加中文索引
1.下载lucene的源码和中文解析器源码
其中中文解析器的下载地址是:http://svn.apache.org/repos/asf/lucene/java/trunk/contrib/
2.重新打包lucene-1.4.3.jar使其包含中文解析器
修改demo程序中语言解析器的调用:
...
try {
      IndexWriter writer = new IndexWriter("index", new StandardAnalyzer(),
true);
      indexDocs(writer, new File(args[0]));
...
改为
...
try {
      IndexWriter writer = new IndexWriter("index", new ChineseAnalyzer(),
true);
      indexDocs(writer, new File(args[0]));
...
重新打包lucene-demos-1.4.3.jar
3.按照lucene帮助建立索引,之后我们就可以进行中文检索了
由于lucene提供的中文解析器没有配合字典使用,并且采用的是多元分词,效率可能会比较低,
但是为二次开发提供了比较好的基础。
 
 
Lucene的索引接口


 在学习索引的时候,首先需要熟悉几个接口:


4.1.1分析器Analyzer


        分析器主要工作是筛选,一段文档进来以后,经过它,出去的时候只剩下那些有用的部分,其他则剔除。而这个分析器也可以自己根据需要而编写。
        org.apache.lucene.analysis.Analyzer:这是一个虚构类,以下两个借口均继承它而来。


        org.apache.lucene.analysis.SimpleAnalyzer:分析器,支持最简单拉丁语言。


        org.apache.lucene.analysis.standard.StandardAnalyzer:标准分析器,除了拉丁语言还支持亚洲语言,并在一些匹配功能上进行完善。在这个接口中还有一个很重要的构造函数:StandardAnalyzer(String[] stopWords),可以对分析器定义一些使用词语,这不仅可以免除检索一些无用信息,而且还可以在检索中定义禁止的政治性、非法性的检索关键词。


4.1.2 IndexWriter


        IndexWriter的构造函数有三种接口,针对目录Directory、文件File、文件路径String三种情况。
例如IndexWriter(String path, Analyzer a, boolean create),path为文件路径,a为分析器,create标志是否重建索引(true:建立或者覆盖已存在的索引,false:扩展已存在的索引。)
       一些重要的方法:
 
接口名
备注
addDocument(Document doc)
索引添加一个文档
addIndexes(Directory[] dirs)
将目录中已存在索引添加到这个索引
addIndexes(IndexReader[] readers)
将提供的索引添加到这个索引
optimize()
合并索引并优化
close()
关闭
 


       IndexWriter为了减少大量的io维护操作,在每得到一定量的索引后建立新的小索引文件(笔者测试索引批量的最小单位为10),然后再定期将它们整合到一个索引文件中,因此在索引结束时必须进行wirter. optimize(),以便将所有索引合并优化。


4.1.3 org.apache.lucene.document


 以下介绍两种主要的类:
 a)org.apache.lucene.document.Document:
        Document文档类似数据库中的一条记录,可以由好几个字段(Field)组成,并且字段可以套用不同的类型(详细见b)。Document的几种接口:
 
 
接口名
备注
add(Field field)
添加一个字段(Field)到Document中
String get(String name)
从文档中获得一个字段对应的文本
Field getField(String name)
由字段名获得字段值
Field[] getFields(String name)
由字段名获得字段值的集
 


 b)org.apache.lucene.document.Field
        即上文所说的“字段”,它是Document的片段section。
        Field的构造函数:
       Field(String name, String string, boolean store, boolean index, boolean token)。
        Indexed:如果字段是Indexed的,表示这个字段是可检索的。
        Stored:如果字段是Stored的,表示这个字段的值可以从检索结果中得到。
        Tokenized:如果一个字段是Tokenized的,表示它是有经过Analyzer转变后成为一个tokens序列,在这个转变过程tokenization中,Analyzer提取出需要进行索引的文本,而剔除一些冗余的词句(例如:a,the,they等,详见org.apache.lucene.analysis.StopAnalyzer.ENGLISH_STOP_WORDS和org.apache.lucene.analysis.standard.StandardAnalyzer(String[] stopWords)的API)。Token是索引时候的基本单元,代表一个被索引的词,例如一个英文单词,或者一个汉字。因此,所有包含中文的文本都必须是Tokenized的。
     Field的几种接口:
 
Name
Stored
Indexed
Tokenized
use
Keyword(String name,
        String value)
Y
Y
N
date,url
Text(String name, Reader value)
N
Y
Y
short text fields:
title,subject
Text(String name, String value)
Y
Y
Y
longer text fields,
like “body”
UnIndexed(String name,
String value)
Y
N
N
 
UnStored(String name,
         String value)
N
Y
Y
 
?
 


5. 利用Lucene进行检索


5.1 一段简单的检索代码
 
    //需要捕捉IOException,ParseException异常
    //处理检索条件
    Query query = QueryParser.parse("入门", "text", analyzer);
    //检索
    Searcher searcher = new IndexSearcher("./index");//"index"指定索引文件位置
Hits hits = searcher.search(query);
    //打印结果值集
    for (int i = 0; i < hits.length(); i++) {
      doc = hits.doc(i);
      String id = doc.get("id");
      System.out.println("found " + "入门" + " on the id:" + id);
}
 
5.2 利用Lucene的检索接口


5.2.1 Query与QueryParser


        主要使用方法:
QueryParser .parse(String query, String field, Analyzer analyzer),例如:
Query query = QueryParser.parse("入门", "text", analyzer);
"入门"为检索词, "text"为检索的字段名, analyzer为分析器


5.2.2 Hits与Searcher


       Hits的主要使用接口:
 
 
接口名
备注
Doc(int n)
返回第n个的文档的所有字段
length()
返回这个集中的可用个数
 
 


6. Lucene的其他使用


6.1 Lucene 的索引修改


        下面给出一段修改索引的代码,请根据Lucene的API解读:


 
  public void addIndex(String idStr, String valueStr) {
    StandardAnalyzer analyzer = new StandardAnalyzer();
    IndexWriter writer = null;
    try {
      writer = new IndexWriter(indexPath, analyzer, false);
      writer.mergeFactor = 2; //修正lucene 1.4.2 bug,否则不能准确反映修改
          Document doc = new Document();
          doc.add(Field.UnIndexed("id", idStr));//“id”为字段名,“1”为字段值
          doc.add(Field.Text("text", valueStr));
      writer.addDocument(doc);
      writer.optimize();
      writer.close();
    }
    catch (IOException ioe) {
      ioe.printStackTrace();
    }
  }
 
  public void deleteIndex(String idStr) {
    try {
      Directory dirt = FSDirectory.getDirectory(indexPath, false);
      IndexReader reader = IndexReader.open(dirt);
      IndexXML.deleteIndex(idStr, reader);
      reader.close();
      dirt.close();
    }
    catch (IOException ioe) {
      ioe.printStackTrace();
    }
  }


6.2 Lucene 的检索结果排序


        Lucene的排序主要是对org.apache.lucene.search.Sort的使用。Sort可以直接根据字段Field生成,也可以根据标准的SortField生成,但是作为Sort的字段,必须符合以下的条件:唯一值以及Indexed。可以对Integers, Floats, Strings三种类型排序。
        对整数型的ID检索结果排序只要进行以下的简单操作:
 Sort sort = new Sort("id");
Hits hits = searcher.search(query, sort);
       用户还可以根据自己定义更加复杂的排序,详细请参考API。


7 总结


        Lucene给java的全文索引检索带来了非常强大的力量,以上仅对Lucene进行简单的入门说明。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值