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...)) Record:记录,包含多个字段 Field:字段 RecordSet:查询结果集,由多个Record组成 Document:一个需要进行索引的“单元” 一个Document由多个字段组成 Field:字段 Hits:查询结果集,由匹配的Document组成 全文检索 ≠ like \"%keyword%\" 通常比较厚的书籍后面常常附关键词索引表(比如:北京:12, 34页,上海:3,77页……),它能够帮助读者比较快地找到相关内容的页码。而数据库索引能够大大提高查询的速度原理也是一样,想像一下通过书后面的索引查找的速度要比一页一页地翻内容高多少倍……而索引之所以效率高,另外一个原因是它是排好序的。对于检索系统来说核心是一个排序问题。 由于数据库索引不是为全文索引设计的,因此,使用like \"%keyword%\"时,数据库索引是不起作用的,在使用like查询时,搜索过程又变成类似于一页页翻书的遍历过程了,所以对于含有模糊查询的数据库服务来说,LIKE对性能的危害是极大的。如果是需要对多个关键词进行模糊匹配:like\"%keyword1%\" and like \"%keyword2%\" ...其效率也就可想而知了。 所以建立一个高效检索系统的关键是建立一个类似于科技索引一样的反向索引机制,将数据源(比如多篇文章)排序顺序存储的同时,有另外一个排好序的关键词列表,用于存储关键词==>文章映射关系,利用这样的映射关系索引:[关键词==>出现关键词的文章编号,出现次数(甚至包括位置:起始偏移量,结束偏移量),出现频率],检索过程就是把模糊查询变成多个可以利用索引的精确查询的逻辑组合的过程。从而大大提高了多关键词查询的效率,所以,全文检索问题归结到最后是一个排序问题。 由此可以看出模糊查询相对数据库的精确查询是一个非常不确定的问题,这也是大部分数据库对全文检索支持有限的原因。Lucene最核心的特征是通过特殊的索引结构实现了传统数据库不擅长的全文索引机制,并提供了扩展接口,以方便针对不同应用的定制。 可以通过一下表格对比一下数据库的模糊查询: Lucene全文索引引擎 数据库 对于LIKE查询来说,数据传统的索引将数据源中的数据都通过全文索引一一建立是根本用不上的。数据需要逐个便利索引 反向索引 记录进行GREP式的模糊匹配,比有索引的搜索速度要有多个数量级的下降。 使用:like \"%net%\" 会把netherlands也匹配出来, 匹配效通过词元(term)进行匹配,通过语言分析接多个关键词的模糊匹配:使用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/ org.apache.Lucene.util/ 简单的例子演示一下Lucene的使用方法: 索引过程:从命令行读取文件名(多个),将文件分路径(path字段)和内容(body字段)2个字段进行存储,并对内容进行全文索引:索引的单位是Document对象,每个Document对象包含多个字段Field对象,针对不同的字段属性和数据输出的需求,对字段还可以选择不同的索引/存储字段规则,列表如下: 方法 切索词 引 存储 用途 切分词索引并存储,比如:标题,内容字段 搜索入口 索引入口 语言分析器 查询分析器 存储结构 底层IO/存储结构 一些公用的数据结构 Field.Text(String name, String value) Yes Yes Yes 切分词索引不存储,比如:METAField.Text(String name, Reader value) Yes Yes No 信息, 不用于返回显示,但需要进行检索内容 Field.Keyword(String name, String value) No Yes Yes 不切分索引并存储,比如:日期字段 Field.UnIndexed(String name, String 不索引,只存储,比如:文件路径 No No Yes value) 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 //构造包含2个字段Field的Document对象 //一个是路径path字段,不索引,只存储 //一个是内容body字段,进行全文索引,并存储 Document doc = new Document(); doc.add(Field.UnIndexed(\"path\ 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 在整个检索过程中,语言分析器,查询分析器,甚至搜索器(Searcher)都是提供了抽象的接口,可以根据需要进行定制。 Hacking Lucene 简化的查询分析器 个人感觉lucene成为JAKARTA项目后,画在了太多的时间用于调试日趋复杂QueryParser,而其中大部分是大多数用户并不很熟悉的,目前LUCENE支持的语法: Query ::= ( Clause )* Clause ::= [\"+\ 中间的逻辑包括: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)给我的指导:是您将我带入了搜索引擎这个行业。 基于Lucene/XML的站内全文检索解决方案:WebLucene 内容摘要: 为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 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数据源: http://www.amazon.com/exec/obidos/ASIN/158214/lockergnomedigit/?ref=nosim&dev-it=D34HUVGKB34YFX 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 其他开发人员的一些反馈和改进 将WebLucene中的lucene部分升级到2.1 WebLucene安装实习篇 WebLucene的安装经验 WebLogic Portal 8.1 平台上运行Lucene搜索引擎 Lucene是Apache.org的一个开源搜索引擎框架,它包含在Jakarta开源项目家族中。Lucene提供了用于搜索和索引不同类型文件和数据提供者的库。 因为Lucene是一个搜索框架,所以创建并运行它需要做一些工作。没有一个安装程序或是GUI工具让您配置运行Lucene搜索引擎,但是,Lucene通过简单的配置就可以直接手工安装,并且提供一系列功能强大的搜索API。 可下载的软件包提供了一些模块用来索引本地存储的文本文件和html内容。另外,定制模块可以从网络上创建或是下载。LARM插件是一个很好的例子,它里面允许Lucene与crawler功能的集成。 Lucene有两个主要的服务:索引和搜索。索引和搜索的任务是相互的。索引和搜索服务都可用,这样开发人员就可以扩展他们来满足自己的需求。Lucene完全由Java编写,并且十分注重性能。 文本索引是 Lucene 重点构造的一个可搜寻的索引区域。索引是为高性能内容查询而创建的知识库。Lucene提供丰富的API可以与存储在索引中的信息交互。您可以简单地指定索引作为文档名称列表和它的摘要;也可以复杂地指定索引作为整个文档存储内容和相关的附加元数据。例如:可以按附加的元数据信息排队,这样,查询结果中就可以区分出优先级较高的一些文档。 文本检索在索引中创建一个包含一系列用户搜索条件的查询。索引知识库的建立可以提高查询速度,并且可以返回按相关性排序的查询结果。Lucene提供多种类型的行业中常用的查询。几种主要的查询如下表: 通配符查询:Lucene支持单个或多个通配符查询。 模糊查询:模糊查询是基于Levenshtein Distance或者Edit Distance算法的。 类似查询:Lucene支持在一定的具体范围内查找词组。 区间查询:区间查询允许按照介于最大至于最小值之间来匹配文档。 条件查询:Lucene提供基于条件发现的文档匹配级别。 布尔运算符:布尔运算符允许多个检索条件用逻辑运算符连接,Lucene支持 AND、“+”、OR、NOT和“-”作为布尔运算符。 开始 要开始使用Lucene,首先要下载Lucene的JAR文件和搜索模块示例。它们可以从下面网址获取 http://jakarta.apache.org/lucene/docs/index.html 或者 http://cvs.apache.org/dist/jakarta/lucene/v1.3-final/ 本文撰写时使用的版本是Lucene1.3,也可以下载最新发布的版本。您可以通过阅读changes.txt文件来了解新版本比1.3版有哪些更新。 首先下载与本文相关的示例代码压缩文件,包括: Lucene 搜索的Java页面流 Lucene 搜索的portlet文件 Lucene库 示例内容l 示例索引 下载作者提供的与本文相关的文件: LuceneSearch lucene_libs 在WebLogic Portal 8.1 sp2平台的Sampleportal中添加Lucene 下面分步讲解如何安装Lucene和Lucene示例。 1、设置JAVA_HOME指向您的Java安装目录,这样可以针对例子内容运行索引引擎。 2、 您可以使用示例包中的Lucene库,也可以从Lucene站点获得最新的包。获得了Lucene jar文件(lucene-1.3-final.jar,lucene-demos-1.3-final.jar)后,把它们放到以下目录中: \\bea\\weblogic81\\samples\\portal\\portalApp\\sampleportal\\WEB-INF\\lib 3、要运行索引程序,把步骤1中的两个jar文件放到Java CLASSPATH中以便进行索引。 4、放置文件LuceneSearch 到Sampleportal的根目录下。得到类似如下的目录结构: \\bea\\weblogic81\\samples\\portal\\portalApp\\sampleportal\\LuceneSearch LuceneSearch文件夹中有一个Content目录,它里面有具体的一些示例内容文本文件。 \\bea\\weblogic81\\samples\\portal\\portalApp\\sampleportal\\LuceneSearch\\Content 索引最好创建完成,以防万一您不想在下一步中花时间来运行索引。 5、在Sampleportal webapp目录下执行如下命令行,运行内容目录下的索引。 <应用程序路径> java org.apache.lucene.demo.IndexFiles <内容路径> 将得到如下界面: \\bea\\weblogic81\\samples\\portal\\portalApp\\sampleportal java org.apache.lucene.demo.IndexFiles \\bea\\weblogic81b\\samples\\portal\\portalApp\\sampleportal\\ LuceneSearch\\Content 这个例子中,示例包里有一系列简单的内容文本文件,这些文件与BEA的产品系列相关。 6、索引任务完成后,结果应该是在您的内容目录下创建一个索引目录,如下: \\bea\\weblogic81\\samples\\portal\\portalApp\\sampleportal\\ LuceneSearch\\Content\\index 7、索引创建后,再返回配置一个portlet以便与索引通信。示例包中有一个简单的java页面流例子,它和Lucene引擎通信,并且假定索引在Sampleportal目录中。启动WebLogic Workshop,从菜单栏中选择File>>Open Application>>Pick the PortalApp应用程序,它包含了Sampleportal。 8、您可能要调整Java页面流和Jsp页面中的代码来适用您的环境。查询结果页面是由results.jsp页面显示的,这??癨??个jsp可以从Lucene站点上获取。所有的调整都完成后,下一步就是把Lucene Search portlet添加到Sampleportal上。这一步可以直接在WebLogic Workshop 上把portlet拖到Portal Designer中,或者右击Portal designer,然后从portlets列表中选择。 然后从Portal菜单中选择View this Portal。 9、在portal出现后,您可以测试查询portlet,看看查询结果。 如何在BEA WebLogic Portal 8.1平台使用Lucene? WebLogic Portal 8.1 在Administration Portal帮助系统中使用Lucene的标准功能。帮助系统的内容是基于HTML的文档,索引被置于Administration Portal Web应用程序中。由于Administration Portal被部署在扩展名为.war的压缩包文件中,还需要其他操作使Lucene与索引文件协调。 可以随时通过点击Administration Portal右上方的帮助图标进入上下文帮助系统。从下图中您可以看到帮助系统返回一系列查询结果,这些查询结果是分页显示的。除此之外,还有很多搜索技巧可以帮助您找到想要的结果。 结束语 Lucene 搜索引擎是完全基于Java的搜索框架,它可以很方便地集成到您的Web应用程序中。此外,Internet上的一些插件可以提高搜索库的性能,增强从不同数据源获取数据的能力。 您可以从以下链接获取Lucene库和一些其他的相关信息: Lucene主页: http://jakarta.apache.org/lucene/docs/index.html 性能基准:http://jakarta.apache.org/lucene/docs/benchmarks.html FAQ:http://lucene.sourceforge.net/cgi-bin/faq/faqmanager.cgi 运行Lucene的站点:http://wiki.apache.org/jakarta-lucene/PoweredBy 原文出处:http://dev2dev.bea.com/products/wlportal81/articles/lucene_search_wlp81.jsp 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] [i] [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,语>。其次大量用到的是对数字的压缩,数字只保存与上一个值的差值(这样可以减小数字的长度,进而减少保存该数字需要的字节数)。例如当前文章号是163(不压缩要用3个字节保存),上一文章号是 16382,压缩后保存7(只用一个字节)。 下面我们可以通过对该索引的查询来解释一下为什么要建立索引。 假设要查询单词 “live”,lucene先对词典二元查找、找到该词,通过指向频率文件的指针读出所有文章号,然后返回结果。词典通常非常小,因而,整个过程的时间是毫秒级的。 而用普通的顺序匹配算法,不建索引,而是对所有文章的内容进行字符串匹配,这个过程将会相当缓慢,当文章数目很大时,时间往往是无法忍受的。 深入 Lucene 索引机制 Lucene 是一个基于 Java 的全文检索工具包,你可以利用它来为你的应用程序加入索引和检索功能。Lucene 目前是著名的 Apache Jakarta 家族中的一个开源项目,下面我们即将学习 Lucene 的索引机制以及它的索引文件的结构。 在这篇文章中,我们首先演示如何使用 Lucene 来索引文档,接着讨论如何提高索引的性能。最后我们来分析 Lucene 的索引文件结构。需要记住的是,Lucene 不是一个完整的应用程序,而是一个信息检索包,它方便你为你的应用程序添加索引和搜索功能。 架构概览 图一显示了 Lucene 的索引机制的架构。Lucene 使用各种解析器对各种不同类型的文档进行解析。比如对于 HTML 文档,HTML 解析器会做一些预处理的工作,比如过滤文档中的 HTML 标签等等。HTML 解析器的输出的是文本内容,接着 Lucene 的分词器(Analyzer)从文本内容中提取出索引项以及相关信息,比如索引项的出现频率。接着 Lucene 的分词器把这些信息写到索引文件中。 图一:Lucene 索引机制架构 回页首 用Lucene索引文档 接下来我将一步一步的来演示如何利用 Lucene 为你的文档创建索引。只要你能将要索引的文件转化成文本格式,Lucene 就能为你的文档建立索引。比如,如果你想为 HTML 文档或者 PDF 文档建立索引,那么首先你就需要从这些文档中提取出文本信息,然后把文本信息交给 Lucene 建立索引。我们接下来的例子用来演示如何利用 Lucene 为后缀名为 txt 的文件建立索引。 1. 准备文本文件 首先把一些以 txt 为后缀名的文本文件放到一个目录中,比如在 Windows 平台上,你可以放到 C:\\\\files_to_index 下面。 2. 创建索引 清单1是为我们所准备的文档创建索引的代码。 清单1:用 Lucene 索引你的文档 package lucene.index; import java.io.File; import java.io.FileReader; import java.io.Reader; import java.util.Date; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.index.IndexWriter; /** * This class demonstrates the process of creating an index with Lucene * for text files in a directory. */ public class TextFileIndexer { public static void main(String[] args) throws Exception{ //fileDir is the directory that contains the text files to be indexed File fileDir = new File(\"C:\\\\files_to_index \"); //indexDir is the directory that hosts Lucene's index files File indexDir = new File(\"C:\\\\luceneIndex\"); Analyzer luceneAnalyzer = new StandardAnalyzer(); IndexWriter indexWriter = new IndexWriter(indexDir,luceneAnalyzer,true); File[] textFiles = fileDir.listFiles(); long startTime = new Date().getTime(); //Add documents to the index for(int i = 0; i < textFiles.length; i++){ if(textFiles[i].isFile() >> textFiles[i].getName().endsWith(\".txt\")){ System.out.println(\"File \" + textFiles[i].getCanonicalPath() + \" is being indexed\"); Reader textReader = new FileReader(textFiles[i]); Document document = new Document(); document.add(Field.Text(\"content\ document.add(Field.Text(\"path\ indexWriter.addDocument(document); } } indexWriter.optimize(); indexWriter.close(); long endTime = new Date().getTime(); System.out.println(\"It took \" + (endTime - startTime) + \" milliseconds to create an index for the files in the directory \" + fileDir.getPath()); } } 正如清单1所示,你可以利用 Lucene 非常方便的为文档创建索引。接下来我们分析一下清单1中的比较关键的代码,我们先从下面的一条语句开始看起。 Analyzer luceneAnalyzer = new StandardAnalyzer(); 这条语句创建了类 StandardAnalyzer 的一个实例,这个类是用来从文本中提取出索引项的。它只是抽象类 Analyzer 的其中一个实现。Analyzer 也有一些其它的子类,比如 SimpleAnalyzer 等。 我们接着看另外一条语句: IndexWriter indexWriter = new IndexWriter(indexDir,luceneAnalyzer,true); 这条语句创建了类 IndexWriter 的一个实例,该类也是 Lucene 索引机制里面的一个关键类。这个类能创建一个新的索引或者打开一个已存在的索引并为该所引添加文档。我们注意到该类的构造函数接受三个参数,第一个参数指定了存储索引文件的路径。第二个参数指定了在索引过程中使用什么样的分词器。最后一个参数是个布尔变量,如果值为真,那么就表示要创建一个新的索引,如果值为假,就表示打开一个已经存在的索引。 接下来的代码演示了如何添加一个文档到索引文件中。 Document document = new Document(); document.add(Field.Text(\"content\ document.add(Field.Text(\"path\ indexWriter.addDocument(document); 首先第一行创建了类 Document 的一个实例,它由一个或者多个的域(Field)组成。你可以把这个类想象成代表了一个实际的文档,比如一个 HTML 页面,一个 PDF 文档,或者一个文本文件。而类 Document 中的域一般就是实际文档的一些属性。比如对于一个 HTML 页面,它的域可能包括标题,内容,URL 等。我们可以用不同类型的 Field 来控制文档的哪些内容应该索引,哪些内容应该存储。如果想获取更多的关于 Lucene 的域的信息,可以参考 Lucene 的帮助文档。代码的第二行和第三行为文档添加了两个域,每个域包含两个属性,分别是域的名字和域的内容。在我们的例子中两个域的名字分别是 \"content\"和\"path\"。分别存储了我们需要索引的文本文件的内容和路径。最后一行把准备好的文档添加到了索引当中。 当我们把文档添加到索引中后,不要忘记关闭索引,这样才保证 Lucene 把添加的文档写回到硬盘上。下面的一句代码演示了如何关闭索引。 indexWriter.close(); 利用清单1中的代码,你就可以成功的将文本文档添加到索引中去。接下来我们看看对索引进行的另外一种重要的操作,从索引中删除文档。 回页首 从索引中删除文档 类IndexReader负责从一个已经存在的索引中删除文档,如清单2所示。 清单2:从索引中删除文档 File indexDir = new File(\"C:\\\\luceneIndex\"); IndexReader ir = IndexReader.open(indexDir); ir.delete(1); ir.delete(new Term(\"path\ 在清单2中,第二行用静态方法 IndexReader.open(indexDir) 初始化了类 IndexReader 的一个实例,这个方法的参数指定了索引的存储路径。类 IndexReader 提供了两种方法去删除一个文档,如程序中的第三行和第四行所示。第三行利用文档的编号来删除文档。每个文档都有一个系统自动生成的编号。第四行删除了路径为\"C:\\\\file_to_index\\lucene.txt\"的文档。你可以通过指定文件路径来方便的删除一个文档。值得注意的是虽然利用上述代码删除文档使得该文档不能被检索到,但是并没有物理上删除该文档。Lucene 只是通过一个后缀名为 .delete 的文件来标记哪些文档已经被删除。既然没有物理上删除,我们可以方便的把这些标记为删除的文档恢复过来,如清单 3 所示,首先打开一个索引,然后调用方法 ir.undeleteAll() 来完成恢复工作。 清单3:恢复已删除文档 ir.close(); File indexDir = new File(\"C:\\\\luceneIndex\"); 你现在也许想知道如何物理上删除索引中的文档,方法也非常简单。清单 4 演示了这个过程。 清单4:如何物理上删除文档 IndexReader ir = IndexReader.open(indexDir); ir.undeleteAll(); ir.close(); File indexDir = new File(\"C:\\\\luceneIndex\"); Analyzer luceneAnalyzer = new StandardAnalyzer(); IndexWriter indexWriter = new IndexWriter(indexDir,luceneAnalyzer,false); 在清单 4 中,第三行创建了类 IndexWriter 的一个实例,并且打开了一个已经存在的索引。第 4 行对索引进行清理,清理过程中将把所有标记为删除的文档物理删除。 Lucene 没有直接提供方法对文档进行更新,如果你需要更新一个文档,那么你首先需要把这个文档从索引中删除,然后把新版本的文档加入到索引中去。 indexWriter.optimize(); indexWriter.close(); 回页首 提高索引性能 利用 Lucene,在创建索引的工程中你可以充分利用机器的硬件资源来提高索引的效率。当你需要索引大量的文件时,你会注意到索引过程的瓶颈是在往磁盘上写索引文件的过程中。为了解决这个问题, Lucene 在内存中持有一块缓冲区。但我们如何控制 Lucene 的缓冲区呢?幸运的是,Lucene 的类 IndexWriter 提供了三个参数用来调整缓冲区的大小以及往磁盘上写索引文件的频率。 1.合并因子(mergeFactor) 这个参数决定了在 Lucene 的一个索引块中可以存放多少文档以及把磁盘上的索引块合并成一个大的索引块的频率。比如,如果合并因子的值是 10,那么当内存中的文档数达到 10 的时候所有的文档都必须写到磁盘上的一个新的索引块中。并且,如果磁盘上的索引块的隔数达到 10 的话,这 10 个索引块会被合并成一个新的索引块。这个参数的默认值是 10,如果需要索引的文档数非常多的话这个值将是非常不合适的。对批处理的索引来讲,为这个参数赋一个比较大的值会得到比较好的索引效果。 2.最小合并文档数 这个参数也会影响索引的性能。它决定了内存中的文档数至少达到多少才能将它们写回磁盘。这个参数的默认值是10,如果你有足够的内存,那么将这个值尽量设的比较大一些将会显著的提高索引性能。 3.最大合并文档数 这个参数决定了一个索引块中的最大的文档数。它的默认值是 Integer.MAX_VALUE,将这个参数设置为比较大的值可以提高索引效率和检索速度,由于该参数的默认值是整型的最大值,所以我们一般不需要改动这个参数。 清单 5 列出了这个三个参数用法,清单 5 和清单 1 非常相似,除了清单 5 中会设置刚才提到的三个参数。 清单5:提高索引性能 /** * This class demonstrates how to improve the indexing performance * by adjusting the parameters provided by IndexWriter. */ public class AdvancedTextFileIndexer { public static void main(String[] args) throws Exception{ //fileDir is the directory that contains the text files to be indexed File fileDir = new File(\"C:\\\\files_to_index\"); //indexDir is the directory that hosts Lucene's index files File indexDir = new File(\"C:\\\\luceneIndex\"); Analyzer luceneAnalyzer = new StandardAnalyzer(); File[] textFiles = fileDir.listFiles(); long startTime = new Date().getTime(); int mergeFactor = 10; int minMergeDocs = 10; int maxMergeDocs = Integer.MAX_VALUE; IndexWriter indexWriter = new IndexWriter(indexDir,luceneAnalyzer,true); indexWriter.mergeFactor = mergeFactor; indexWriter.minMergeDocs = minMergeDocs; indexWriter.maxMergeDocs = maxMergeDocs; //Add documents to the index for(int i = 0; i < textFiles.length; i++){ if(textFiles[i].isFile() >> textFiles[i].getName().endsWith(\".txt\")){ Reader textReader = new FileReader(textFiles[i]); Document document = new Document(); document.add(Field.Text(\"content\ document.add(Field.Keyword(\"path\ indexWriter.addDocument(document); } } indexWriter.optimize(); indexWriter.close(); long endTime = new Date().getTime(); System.out.println(\"MergeFactor: \" + indexWriter.mergeFactor); System.out.println(\"MinMergeDocs: \" + indexWriter.minMergeDocs); System.out.println(\"MaxMergeDocs: \" + indexWriter.maxMergeDocs); System.out.println(\"Document number: \" + textFiles.length); System.out.println(\"Time consumed: \" + (endTime - startTime) + \" milliseconds\"); 通过这个例子,我们注意到在调整缓冲区的大小以及写磁盘的频率上面 Lucene 给我们提供了非常大的灵活性。现在我们来看一下代码中的关键语句。如下的代码首先创建了类 IndexWriter 的一个实例,然后对它的三个参数进行赋值。 } } int mergeFactor = 10; int minMergeDocs = 10; int maxMergeDocs = Integer.MAX_VALUE; IndexWriter indexWriter = new IndexWriter(indexDir,luceneAnalyzer,true); 下面我们来看一下这三个参数取不同的值对索引时间的影响,注意参数值的不同和索引之间的关系。我们为这个实验准备了 10000 个测试文档。表 1 显示了测试结果。 表1:测试结果 indexWriter.mergeFactor = mergeFactor; indexWriter.minMergeDocs = minMergeDocs; indexWriter.maxMergeDocs = maxMergeDocs; 通过表 1,你可以清楚地看到三个参数对索引时间的影响。在实践中,你会经常的改变合并因子和最小合并文档数的值来提高索引性能。只要你有足够大的内存,你可以为合并因子和最小合并文档数这两个参数赋尽量大的值以提高索引效率,另外我们一般无需更改最大合并文档数这个参数的值,因为系统已经默认将它设置成了最大。 回页首 Lucene 索引文件结构分析 在分析 Lucene 的索引文件结构之前,我们先要理解反向索引(Inverted index)这个概念,反向索引是一种以索引项为中心来组织文档的方式,每个索引项指向一个文档序列,这个序列中的文档都包含该索引项。相反,在正向索引中,文档占据了中心的位置,每个文档指向了一个它所包含的索引项的序列。你可以利用反向索引轻松的找到那些文档包含了特定的索引项。Lucene正是使用了反向索引作为其基本的索引结构。 回页首 索引文件的逻辑视图 在Lucene 中有索引块的概念,每个索引块包含了一定数目的文档。我们能够对单独的索引块进行检索。图 2 显示了 Lucene 索引结构的逻辑视图。索引块的个数由索引的文档的总数以及每个索引块所能包含的最大文档数来决定。 图2:索引文件的逻辑视图 回页首 Lucene 中的关键索引文件 下面的部分将会分析Lucene中的主要的索引文件,可能分析有些索引文件的时候没有包含文件的所有的字段,但不会影响到对索引文件的理解。 1.索引块文件 这个文件包含了索引中的索引块信息,这个文件包含了每个索引块的名字以及大小等信息。表 2 显示了这个文件的结构信息。 表2:索引块文件结构 2.域信息文件 我们知道,索引中的文档由一个或者多个域组成,这个文件包含了每个索引块中的域的信息。表 3 显示了这个文件的结构。 表3:域信息文件结构 3.索引项信息文件 这是索引文件里面最核心的一个文件,它存储了所有的索引项的值以及相关信息,并且以索引项来排序。表 4 显示了这个文件的结构。 表4:索引项信息文件结构 4.频率文件 这个文件包含了包含索引项的文档的列表,以及索引项在每个文档中出现的频率信息。如果Lucene在索引项信息文件中发现有索引项和搜索词相匹配。那么 Lucene 就会在频率文件中找有哪些文件包含了该索引项。表5显示了这个文件的一个大致的结构,并没有包含这个文件的所有字段。 表5:频率文件的结构 5.位置文件 这个文件包含了索引项在每个文档中出现的位置信息,你可以利用这些信息来参与对索引结果的排序。表 6 显示了这个文件的结构 表6:位置文件的结构 到目前为止我们介绍了 Lucene 中的主要的索引文件结构,希望能对你理解 Lucene 的物理的存储结构有所帮助。 回页首 总结 目前已经有非常多的知名的组织正在使用 Lucene,比如,Lucene 为 Eclipse 的帮助系统,麻省理工学院的 OpenCourseWare 提供了搜索功能。通过阅读这篇文章,希望你能对 Lucene 的索引机制有所了 解,并且你会发现利用 Lucene 创建索引是非常简单的事情。 参考资料 学习 您可以参阅本文在 developerWorks 全球站点上的 英文原文 。 实战 Lucene: 初识 Lucene 介绍了 Lucene 的一些基本概念,然后开发了一个应用程序演示了利用 Lucene 建立索引并在该索引上进行搜索的过程。 Parsing, indexing, and searching XML with Digester and Lucene 是 Otis Gospodnetic 在 developerWorks 上发表的一篇关于利用 Lucene 和 Digester 来操作 XML 文档的文章。 IBM Search and Index APIs (SIAPI) for WebSphere Information Integrator OmniFind Edition 是 Srinivas Varma Chitiveli 在 developerWorks 上发表的一篇关于如何用 SIAPI 来构建搜索解决方案的文章。 Lucene的官方网站:上面有大量的 Lucene 帮助文档。 一个关于 Lucene 的演讲:是由 Lucene 最初的作者 Doug Cutting 在 Pisa 大学所作。 现代信息检索是由 Ricardo Baeza-Yates 和 Berthier Ribeiro-Neto 所写的关于信息检索方 面的一本著作。 developerWorks Web Architecture 专区:上面有很多关于如何构建网站的技术文章 《开发自己的搜索引擎:Lucene 2.0+Heritrix》 开发自己的搜索引擎:Lucene 2.0+Heritrix》 本书详细介绍了如何应用Lucene进行搜索引擎开发,通过学习本书,读者可以完成构建一个企业级的搜索引擎网站。.全书共分为14章,内容包括搜索引擎与信息检索基础,Lucene入门实例,Lucene索引的建立,使用Lucene构建搜索,Lucene的排序,Lucene的分析器,对Word、Excel和PDF格式文档的解析,Compass搜索引擎框架,Lucene分布式和Google Search API,爬虫Heritrix,综合实例之准备篇,综合实例之HTMLParser篇,综合实例之DWR篇,综合实例之Web编。..本书是国内第一本使用Lucene和Heritrix来讲解搜索引擎构建的书,通过详细的对API和源代码的分析,力求使读者在应用的基础上,能够深入其核心,自行扩展和开发相应组件,发挥想象力,开发出更具有创意的搜索引擎产品。本书适合Java程序员和从事计算机软件开发的其他编程人员阅读,同时也可以作为搜索引擎爱好者的入门书籍。由于目前市面上从技术层面介绍搜索引擎的书并不多,即使有,也大多停留在理论阶段,而非搜索引擎的开发过程。因此,可以说本书是国内第一本详细介绍搜索引擎 开发过程的图书。(1)采用最新的Lucene 2.0。以前大家用的1.4.3版本,而最新的Lucene 2.0重写了很多API,内部的实现方法也有了很大优化。本书的代码都是在2.0版本下调试通过的,这样可以帮助读者了解Lucene的更多新功能。(2)配有一个完整的搜索引擎案例。这个案例有很强的实用价值,只需稍加修改,就能应用于实际项目,市场价值在30000元以上!(3)着重解决开发人员头痛的问题。本书的目的是指导项目实践,因此没有罗列各个API的用法,而是对常见的开发问题进行深入探讨,比如本书的第7章,是专门为解决“Word,Excel和PDF文件如何解析”这个问题而设置的。(4)内容新颖,前卫实用。本书介绍了Compass、Heritrix、DWR和HTMLParser等内容。在搜索引擎开发的过程中,这些均为相当重要且实用的技术,笔者经过自身实践将它们展现给读者,希望能让读者在学习Lucene的同时开拓视野。光盘特色:配有一个完整的搜索引擎案例。这个案例有很强的实用价值,只需稍加修改,就能应用于实际项目,市场价值在30000元以上!... 1. Lucene如何为数据库建索引 问这个问题的读者应该还没有搞懂Lucene到底能干嘛。Lucene本身不能为任何格式的物理文件或数据库建索,它只能为Document类型建索。你要做的就是把物理文件中的各种信息提取出来做成数据源放在一个Document中,然后让Lucene为其建立索引。 数据库也是一样的,首先把数据库的信息提取出来,然后再次建立索引。不过数据库本身也带有FullTextSearch的功能,所以要权衡一下这样做是不是有必要。不过数据库建的索引好像是基于B树的。。。或者是其它的啥,效率应该不如Lucene的高。 2. Lucene如何为WORD或EXCEL或PDF建索引 WORD&EXCEL: POI PDF:PDFBOX OR XPDFBOX 3. 有读者有问题,可以发邮件到qqunlimited@gmail.com交流。 如果是一些比较宽范的问题,比如有的读者问我“怎么用Lucene开发一个搜索引擎网站”。。。我也不知道该如何才能回答。。。这种问题就不要问我了。。。我得写上几万字也不一定解答的了。。 如果是讨论一些源代码级别的算法问题,举双手欢迎,呵呵。 4. Lucene2已经出了,这和书中Lucene1.4.3的API有很大不同,希望大家研究一下可以自行解决,解决不了可以发邮件,呵呵。新书中把大量API的变化都讲了。。。 好久没更新这个BLOG了,因为最近工作了,实在是太忙。。。于是就把这个BLOG给忘了。看到这么多朋友留下邮箱想要分词包,感到实在是有些不好意思。。 Lucene出了2.0了,所以我写的书中的内容很多已经不太适用了。最近我正在写一些关于Lucene2.0的东西,其中包括了一个完整的搜索引擎的例子,是用DWR和Spring一起做的,用了Lucene和JE分词。希望很快可以和读者朋友见面。 另外,有一个叫Compass的东西不错,把Lucene和Spring结合了,也就是加了些Transaction的东西到Lucene上,这下就可以构造JDBCDirectory了,甚至可以实现分布式的索引了。。。 大家可以研究一下。 另外,我们公司最近做了个小产品,www.cheefei.com 在这里做个小广告,大家可以上去试用一下,多提些意见。 www.eskalate.com是我们公司的网址。如果有Java程序员在北京的或者不在北京的也可以,想要找一份Java开发工程师的工作,可以联系我,我的邮箱是qqunlimited@gmail.com。最近公司在发展,想招些人,呵呵。 谢谢大家。希望能尽快完成新书,新书的质量会比这本书有质的提升。。。相信大家对原先这本书的质量肯定不太满意。。。唉。。。我的失误。 我把带JAR包和中科院分词和CJK的工程传到公共邮箱了 ajaxlucene@126.com 12345678 有不少读者朋友要,所以就放了。 --------------------------------------- 发现不知道哪位读者和我们开了个玩笑,把邮箱清空了还把密码改了。这里是公共的地方,我不想骂人,希望读者朋友也不要骂人了。希望和我们开这个玩笑的哥儿识点趣,把邮箱密码改回来,再把删掉的东西放好。 如果有朋友需要,请在这里留下邮箱。我会及时把东西用邮件发给你 邮件大小在4M多。 有的朋友说现在已经没有地方下载lucene1.4.3的包了,我申请了个邮箱,lucene1.4.3的相关包放在里面了,有兴趣的朋友可以去下载 ajaxlucene@126.com 密码是12345678 lucene2.0已经出来了,呵呵,抓紧研究中 附录一 常用格式文件的文本抽取工具 本附录主要列举了一些常用格式文件的文件抽取工具,以供读者在使用Lucene时使用 1. PDF格式 PDF格式的文档相当常见,但是如果要想要为其中的内容建立索引,就需要专门的工具来对其中的文本进行抽取。最常见的一种PDF文本抽取工具就是PDFBOX了,它的下载网址为: http://sourceforge.net/projects/pdfbox/ 当然,PDFBOX对于中文的支持很差,如果想抽取中文的PDF文件中的文本,可以使用一个称之为xpdf的工具。以下是它的下载网址: http://www.foolabs.com/xpdf/download.html 2. 微软格式(WORD,EXCEL) 如要要对微软的WORD和EXCEL格式进行本文抽取,通常的方式有两种: 1. 使用COM-Java 我们知道,微软的WORD和Excel程序是以一种COM组件形式存在。如果能够在Java中调用其COM组件就能使用它的相当方法来获取其中的文本信息。目前网上有许多提供这种通过Java访问COM组件的工具。不过它们中的很多都是收费的,以下是一个称之为Jacob的工具的下载地址:http://danadler.com/jacob/ 2. 使用Apache的POI POI是Apache的开源工具,它可以直接访问微软的WORD和Excel文件格式,它的下载地址为: http://www.apache.org/dyn/closer.cgi/jakarta/poi/ 因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- sarr.cn 版权所有 赣ICP备2024042794号-1
违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务