lucene详细介绍

发布时间:2025-12-09 17:05:22 浏览次数:4

1 lucene简介  
1.1 什么是lucene  

项目地址: https://github.com/apache/lucene-solr
Lucene是一个全文搜索框架,而不是应用产品。它只是提供了一种工具让你能实现这些产品。

它的特点概述起来就是:全Java实现、开源、高性能、功能完整、易拓展,功能完整体现在对分词的支持、各种查询方式(前缀、模糊、正则等)、打分高亮、列式存储(DocValues)等等。

目录结构: PackageDescription
org.apache.lucene

Top-level package.

org.apache.lucene.analysis

Text analysis.

org.apache.lucene.analysis.standard

Fast, general-purpose grammar-based tokenizer StandardTokenizer implements the Word Break rules from the Unicode Text Segmentation algorithm, as specified inUnicode Standard Annex #29.

org.apache.lucene.analysis.tokenattributes

General-purpose attributes for text analysis.

org.apache.lucene.codecs

Codecs API: API for customization of the encoding and structure of the index.

org.apache.lucene.codecs.blocktree

BlockTree terms dictionary.

org.apache.lucene.codecs.compressing

StoredFieldsFormat that allows cross-document and cross-field compression of stored fields.

org.apache.lucene.codecs.lucene50

Components from the Lucene 5.0 index format See org.apache.lucene.codecs.lucene50 for an overview of the index format.

org.apache.lucene.codecs.lucene60

Components from the Lucene 6.0 index format.

org.apache.lucene.codecs.lucene62

Components from the Lucene 6.2 index format See org.apache.lucene.codecs.lucene70 for an overview of the current index format.

org.apache.lucene.codecs.lucene70

Lucene 7.0 file format.

org.apache.lucene.codecs.perfield

Postings format that can delegate to different formats per-field.

org.apache.lucene.document

The logical representation of a Document for indexing and searching.

org.apache.lucene.geo

Geospatial Utility Implementations for Lucene Core

org.apache.lucene.index

Code to maintain and access indices.

org.apache.lucene.search

Code to search indices.

org.apache.lucene.search.similarities

This package contains the various ranking models that can be used in Lucene.

org.apache.lucene.search.spans

The calculus of spans.

org.apache.lucene.store

Binary i/o API, used for all index data.

org.apache.lucene.util

Some utility classes.

org.apache.lucene.util.automaton

Finite-state automaton for regular expressions.

org.apache.lucene.util.bkd

Block KD-tree, implementing the generic spatial data structure described in this paper.

org.apache.lucene.util.fst

Finite state transducers

org.apache.lucene.util.graph

Utility classes for working with token streams as graphs.

org.apache.lucene.util.mutable

Comparable object wrappers

org.apache.lucene.util.packed

Packed integer arrays and streams.

 

Package org.apache.lucene.analysis

  • ClassDescription
    Analyzer

    An Analyzer builds TokenStreams, which analyze text.

    Analyzer.ReuseStrategy

    Strategy defining how TokenStreamComponents are reused per call to Analyzer.tokenStream(String, java.io.Reader).

    Analyzer.TokenStreamComponents

    This class encapsulates the outer components of a token stream.

    AnalyzerWrapper

    Extension to Analyzer suitable for Analyzers which wrap other Analyzers.

    CachingTokenFilter

    This class can be used if the token attributes of a TokenStream are intended to be consumed more than once.

    CharacterUtils

    Utility class to write tokenizers or token filters.

    CharacterUtils.CharacterBuffer

    A simple IO buffer to use with CharacterUtils.fill(CharacterBuffer, Reader).

    CharArrayMap<V>

    A simple class that stores key Strings as char[]'s in a hash table.

    CharArraySet

    A simple class that stores Strings as char[]'s in a hash table.

    CharFilter

    Subclasses of CharFilter can be chained to filter a Reader They can be used as Reader with additional offset correction.

    DelegatingAnalyzerWrapper

    An analyzer wrapper, that doesn't allow to wrap components or readers.

    FilteringTokenFilter

    Abstract base class for TokenFilters that may remove tokens.

    LowerCaseFilter

    Normalizes token text to lower case.

    StopFilter

    Removes stop words from a token stream.

    StopwordAnalyzerBase

    Base class for Analyzers that need to make use of stopword sets.

    TokenFilter

    A TokenFilter is a TokenStream whose input is another TokenStream.

    Tokenizer

    A Tokenizer is a TokenStream whose input is a Reader.

    TokenStream

    A TokenStream enumerates the sequence of tokens, either from Fields of a Document or from query text.

    TokenStreamToAutomaton

    Consumes a TokenStream and creates an Automaton where the transition labels are UTF8 bytes (or Unicode code points if unicodeArcs is true) from the TermToBytesRefAttribute.

    WordlistLoader

    Loader for text files that represent a list of stopwords.

1.2 lucene能做什么  
要回答这个问题,先要了解lucene的本质。实际上lucene的功能很单一,说到底,就是你给它若干个字符串,然后它为你提供一个全文搜索服务,告诉你你要搜索的关键词出现在哪里。知道了这个本质,你就可以发挥想象做任何符合这个条件的事情了。你可以把站内新闻都索引了,做个资料库;你可以把一个数据库表的若干个字段索引起来,那就不用再担心因为“%like%”而锁表了;你也可以写个自己的搜索引擎……

样例:

Analyzer analyzer = new StandardAnalyzer();// Store the index in memory:Directory directory = new RAMDirectory();// To store an index on disk, use this instead://Directory directory = FSDirectory.open("/tmp/testindex");IndexWriterConfig config = new IndexWriterConfig(analyzer);IndexWriter iwriter = new IndexWriter(directory, config);Document doc = new Document();String text = "This is the text to be indexed.";doc.add(new Field("fieldname", text, TextField.TYPE_STORED));iwriter.addDocument(doc);iwriter.close();// Now search the index:DirectoryReader ireader = DirectoryReader.open(directory);IndexSearcher isearcher = new IndexSearcher(ireader);// Parse a simple query that searches for "text":QueryParser parser = new QueryParser("fieldname", analyzer);Query query = parser.parse("text");ScoreDoc[] hits = isearcher.search(query, null, 1000).scoreDocs;assertEquals(1, hits.length);// Iterate through the results:for (int i = 0; i < hits.length; i++) {Document hitDoc = isearcher.doc(hits[i].doc);assertEquals("This is the text to be indexed.", hitDoc.get("fieldname"));}ireader.close();directory.close();

 

1.3 你该不该选择lucene  
下面给出一些测试数据,如果你觉得可以接受,那么可以选择。  
测试一:250万记录,300M左右文本,生成索引380M左右,800线程下平均处理时间300ms。  
测试二:37000记录,索引数据库中的两个varchar字段,索引文件2.6M,800线程下平均处理时间1.5ms。

2 lucene的工作方式  
lucene提供的服务实际包含两部分:一入一出。所谓入是写入,即将你提供的源(本质是字符串)写入索引或者将其从索引中删除;所谓出是读出,即向用户提供全文搜索服务,让用户可以通过关键词定位源。

2.1写入流程  
源字符串首先经过analyzer处理,包括:分词,分成一个个单词;去除stopword(可选)。  
将源中需要的信息加入Document的各个Field中,并把需要索引的Field索引起来,把需要存储的Field存储起来。  
将索引写入存储器,存储器可以是内存或磁盘。

2.2读出流程  
用户提供搜索关键词,经过analyzer处理。  
对处理后的关键词搜索索引找出对应的Document。  
用户根据需要从找到的Document中提取需要的Field。

3 一些需要知道的概念  
lucene用到一些概念,了解它们的含义,有利于下面的讲解。

3.1 analyzer  
Analyzer 是分析器,它的作用是把一个字符串按某种规则划分成一个个词语,并去除其中的无效词语,这里说的无效词语是指英文中的“of”、 “the”,中文中的 “的”、“地”等词语,这些词语在文章中大量出现,但是本身不包含什么关键信息,去掉有利于缩小索引文件、提高效率、提高命中率。  
分词的规则千变万化,但目的只有一个:按语义划分。这点在英文中比较容易实现,因为英文本身就是以单词为单位的,已经用空格分开;而中文则必须以某种方法将连成一片的句子划分成一个个词语。具体划分方法下面再详细介绍,这里只需了解分析器的概念即可。

3.2 document  
用户提供的源是一条条记录,它们可以是文本文件、字符串或者数据库表的一条记录等等。一条记录经过索引之后,就是以一个Document的形式存储在索引文件中的。用户进行搜索,也是以Document列表的形式返回。

3.3 field  
一个Document可以包含多个信息域,例如一篇文章可以包含“标题”、“正文”、“最后修改时间”等信息域,这些信息域就是通过Field在Document中存储的。  
Field有两个属性可选:存储和索引。通过存储属性你可以控制是否对这个Field进行存储;通过索引属性你可以控制是否对该Field进行索引。这看起来似乎有些废话,事实上对这两个属性的正确组合很重要,下面举例说明:  
还是以刚才的文章为例子,我们需要对标题和正文进行全文搜索,所以我们要把索引属性设置为真,同时我们希望能直接从搜索结果中提取文章标题,所以我们把标题域的存储属性设置为真,但是由于正文域太大了,我们为了缩小索引文件大小,将正文域的存储属性设置为假,当需要时再直接读取文件;我们只是希望能从搜索解果中提取最后修改时间,不需要对它进行搜索,所以我们把最后修改时间域的存储属性设置为真,索引属性设置为假。上面的三个域涵盖了两个属性的三种组合,还有一种全为假的没有用到,事实上Field不允许你那么设置,因为既不存储又不索引的域是没有意义的。

3.4 term  
term是搜索的最小单位,它表示文档的一个词语,term由两部分组成:它表示的词语和这个词语所出现的field。

3.5 tocken  
tocken是term的一次出现,它包含trem文本和相应的起止偏移,以及一个类型字符串。一句话中可以出现多次相同的词语,它们都用同一个term表示,但是用不同的tocken,每个tocken标记该词语出现的地方。

3.6 segment  
添加索引时并不是每个document都马上添加到同一个索引文件,它们首先被写入到不同的小文件,然后再合并成一个大索引文件,这里每个小文件都是一个segment。

4 lucene的结构  
lucene包括core和sandbox两部分,其中core是lucene稳定的核心部分,sandbox包含了一些附加功能,例如highlighter、各种分析器。  
Lucene core有七个包:analysis,document,index,queryParser,search,store,util。  
4.1 analysis  
Analysis包含一些内建的分析器,例如按空白字符分词的WhitespaceAnalyzer,添加了stopwrod过滤的StopAnalyzer,最常用的StandardAnalyzer。  
4.2 document  
Document包含文档的数据结构,例如Document类定义了存储文档的数据结构,Field类定义了Document的一个域。  
4.3 index  
Index 包含了索引的读写类,例如对索引文件的segment进行写、合并、优化的IndexWriter类和对索引进行读取和删除操作的 IndexReader类,这里要注意的是不要被IndexReader这个名字误导,以为它是索引文件的读取类,实际上删除索引也是由它完成, IndexWriter只关心如何将索引写入一个个segment,并将它们合并优化;IndexReader则关注索引文件中各个文档的组织形式。  
4.4 queryParser  
QueryParser 包含了解析查询语句的类,lucene的查询语句和sql语句有点类似,有各种保留字,按照一定的语法可以组成各种查询。 Lucene有很多种 Query类,它们都继承自Query,执行各种特殊的查询,QueryParser的作用就是解析查询语句,按顺序调用各种 Query类查找出结果。  
4.5 search  
Search包含了从索引中搜索结果的各种类,例如刚才说的各种Query类,包括TermQuery、BooleanQuery等就在这个包里。  
4.6 store  
Store包含了索引的存储类,例如Directory定义了索引文件的存储结构,FSDirectory为存储在文件中的索引,RAMDirectory为存储在内存中的索引,MmapDirectory为使用内存映射的索引。  
4.7 util  
Util包含一些公共工具类,例如时间和字符串之间的转换工具。

5 如何建索引  
5.1 最简单的能完成索引的代码片断

IndexWriter writer = new IndexWriter(“/data/index/”, new StandardAnalyzer(), true);  Document doc = new Document();  doc.add(new Field("title", "lucene introduction", Field.Store.YES, Field.Index.TOKENIZED));  doc.add(new Field("content", "lucene works well", Field.Store.YES, Field.Index.TOKENIZED));  writer.addDocument(doc);  writer.optimize();  writer.close();

下面我们分析一下这段代码。  
首先我们创建了一个writer,并指定存放索引的目录为“/data/index”,使用的分析器为StandardAnalyzer,第三个参数说明如果已经有索引文件在索引目录下,我们将覆盖它们。  
然后我们新建一个document。  
我们向document添加一个field,名字是“title”,内容是“lucene introduction”,对它进行存储并索引。  
再添加一个名字是“content”的field,内容是“lucene works well”,也是存储并索引。  
然后我们将这个文档添加到索引中,如果有多个文档,可以重复上面的操作,创建document并添加。  
添加完所有document,我们对索引进行优化,优化主要是将多个segment合并到一个,有利于提高索引速度。  
随后将writer关闭,这点很重要。

对,创建索引就这么简单!  
当然你可能修改上面的代码获得更具个性化的服务。

5.2 将索引直接写在内存  
你需要首先创建一个RAMDirectory,并将其传给writer,代码如下:

Directory dir = new RAMDirectory();  IndexWriter writer = new IndexWriter(dir, new StandardAnalyzer(), true);  Document doc = new Document();  doc.add(new Field("title", "lucene introduction", Field.Store.YES, Field.Index.TOKENIZED));  doc.add(new Field("content", "lucene works well", Field.Store.YES, Field.Index.TOKENIZED));  writer.addDocument(doc);  writer.optimize();  writer.close();

5.3 索引文本文件  
如果你想把纯文本文件索引起来,而不想自己将它们读入字符串创建field,你可以用下面的代码创建field:

Field field = new Field("content", new FileReader(file));

这里的file就是该文本文件。该构造函数实际上是读去文件内容,并对其进行索引,但不存储。

6 如何维护索引  
索引的维护操作都是由IndexReader类提供。

6.1 如何删除索引  
lucene提供了两种从索引中删除document的方法,一种是

void deleteDocument(int docNum)

这种方法是根据document在索引中的编号来删除,每个document加进索引后都会有个唯一编号,所以根据编号删除是一种精确删除,但是这个编号是索引的内部结构,一般我们不会知道某个文件的编号到底是几,所以用处不大。另一种是

void deleteDocuments(Term term)

这种方法实际上是首先根据参数term执行一个搜索操作,然后把搜索到的结果批量删除了。我们可以通过这个方法提供一个严格的查询条件,达到删除指定document的目的。  
下面给出一个例子:

Directory dir = FSDirectory.getDirectory(PATH, false);  IndexReader reader = IndexReader.open(dir);  Term term = new Term(field, key);  reader.deleteDocuments(term);  reader.close();

6.2 如何更新索引  
lucene并没有提供专门的索引更新方法,我们需要先将相应的document删除,然后再将新的document加入索引。例如:

Directory dir = FSDirectory.getDirectory(PATH, false);  IndexReader reader = IndexReader.open(dir);  Term term = new Term(“title”, “lucene introduction”);  reader.deleteDocuments(term);  reader.close();IndexWriter writer = new IndexWriter(dir, new StandardAnalyzer(), true);  Document doc = new Document();  doc.add(new Field("title", "lucene introduction", Field.Store.YES, Field.Index.TOKENIZED));  doc.add(new Field("content", "lucene is funny", Field.Store.YES, Field.Index.TOKENIZED));  writer.addDocument(doc);  writer.optimize();  writer.close(); 1) 标准分词技术(StandardAnalyzer):标准分词技术对英文来说是不错的,把单词分成一个一个的词根,但是对于中文来说,只是简单的把中文分成一个一个的汉字。2)IK中文分词器(IKAnalyzer):结合词典分词和文法分析算法的中文分词技术,能够对词典进行扩展,是一个很好的中文分词器。3)空格分词器(WhitespaceAnalyzer):按照空格切分字符串。4)简单分词器(SimpleAnalyzer):根据标点符号分词。5)二分法分词器(CJKAnalyzer):二分法分词技术中每个汉字都会和它前边和后边的汉字组成一个词,也就是说每个汉字都会出现两次,除了首字和末字(前提是纯汉字,没有英文,因为英文会根据词根来分词),这种分词技术会有太多的词,会产生太多冗余。6)关键词分词器(KeywordAnalyzer):不进行分割。7)被忽略词分词器(StopAnalyzer):被忽略词如标点符号,这种分词技术和SimpleAnalyzer结果很像。

Lucene自带多种分词器,其中对中文分词支持比较好的是smartcn。

1. 标准分词器StandardAnalyzer

在演示smartcn中文分词器之前,先来看看Lucene标准分词器对中文分词的效果。需要的jar为\lucene-5.5.5\core\下的lucene-core-5.5.5.jar和\lucene-5.5.5\analysis\common\下的lucene-analyzers-common-5.5.5.jar。新建测试类TestLucene04:

package net.xxpsw.demo.lucene.test;import org.apache.lucene.analysis.Analyzer;import org.apache.lucene.analysis.TokenStream;import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;public class TestLucene04 {private void print(Analyzer analyzer) throws Exception {String text = "Lucene自带多种分词器,其中对中文分词支持比较好的是smartcn。";TokenStream tokenStream = analyzer.tokenStream("content", text);CharTermAttribute attribute = tokenStream.addAttribute(CharTermAttribute.class);tokenStream.reset();while (tokenStream.incrementToken()) {System.out.println(new String(attribute.toString()));}}}
  •  

  • 增加测试方法testStandardAnalyzer:

    /*** @Description: 测试标准分词器* @throws Exception*/@Testpublic void testStandardAnalyzer() throws Exception {StandardAnalyzer standardAnalyzer = new StandardAnalyzer();print(standardAnalyzer);}
  •  

  • 执行测试,控制台打印结果如下:

    由上可见,标准分词器对中文的分词结果是分成了单个汉字,而并没有识别常用的汉语词组。

    2. 中文分词器SmartChineseAnalyzer

    引入analysis\smartcn\下的lucene-analyzers-smartcn-xx.jar,新增测试方法testSmartChineseAnalyzer:

     /*** @Description: 测试中文分词器* @throws Exception*/@Testpublic void testSmartChineseAnalyzer() throws Exception {SmartChineseAnalyzer smartChineseAnalyzer = new SmartChineseAnalyzer();print(smartChineseAnalyzer);}
  •  

  • 执行测试方法,控制台打印结果如下:

    从上述打印结果中可以看到,“多种”、“分词”、“其中”、“中文”、“支持”、“比较”等中文词组都被顺利的识别出来。
    进入SmartChineseAnalyzer源码中可以看到如下代码:

     public SmartChineseAnalyzer() {this(true);}
  •  

  • 查看this(true):

    public SmartChineseAnalyzer(boolean useDefaultStopWords) {stopWords = useDefaultStopWords ? DefaultSetHolder.DEFAULT_STOP_SET: CharArraySet.EMPTY_SET;}
  •  

  • 查看DefaultSetHolder.DEFAULT_STOP_SET:

     private static class DefaultSetHolder {static final CharArraySet DEFAULT_STOP_SET;static {try {DEFAULT_STOP_SET = loadDefaultStopWordSet();} catch (IOException ex) {// default set should always be present as it is part of the// distribution (JAR)throw new RuntimeException("Unable to load default stopword set");}}static CharArraySet loadDefaultStopWordSet() throws IOException {// make sure it is unmodifiable as we expose it in the outer classreturn CharArraySet.unmodifiableSet(WordlistLoader.getWordSet(IOUtils.getDecodingReader(SmartChineseAnalyzer.class, DEFAULT_STOPWORD_FILE,StandardCharsets.UTF_8), STOPWORD_FILE_COMMENT));}}
  •  

  • 查看DEFAULT_STOPWORD_FILE:

    private static final String DEFAULT_STOPWORD_FILE = "stopwords.txt";

    可见SmartChineseAnalyzer的空构造函数实际上默认加载了一个停用词列表文件stopwords.txt,该文件在jar包中的位置如下:

    该文件中包含了53个需要停用的标点符号,分别是,.`-_=?'|"(){}[]<>*#&^$@!~:;+/\《》—-,。、:;!·?“”)(【】[]●及中文空格。

    3. 自定义停用词

    SmartChineseAnalyzer还支持自定义停用词,新建测试方法testMySmartChineseAnalyzer:

     /*** @Description: 测试自定义停用词* @throws Exception*/@Testpublic void testMySmartChineseAnalyzer() throws Exception {CharArraySet charArraySet = new CharArraySet(0, true);// 系统默认停用词Iterator<Object> iterator = SmartChineseAnalyzer.getDefaultStopSet().iterator();while (iterator.hasNext()) {charArraySet.add(iterator.next());}// 自定义停用词String[] myStopWords = { "对", "的", "是", "其中" };for (String stopWord : myStopWords) {charArraySet.add(stopWord);}SmartChineseAnalyzer smartChineseAnalyzer = new SmartChineseAnalyzer(charArraySet);print(smartChineseAnalyzer);}}
  •  

  • 执行测试方法,控制台打印如下:

    对比测试方法testSmartChineseAnalyzer可以看出,"对", "的", "是", "其中"等中文词已被停用而不再出现。

    =============================================================================================

    谈谈架构:

    Lucene简介

    Lucene最初由鼎鼎大名Doug Cutting开发,2000年开源,现在也是开源全文检索方案的不二选择,它的特点概述起来就是:全Java实现、开源、高性能、功能完整、易拓展,功能完整体现在对分词的支持、各种查询方式(前缀、模糊、正则等)、打分高亮、列式存储(DocValues)等等。
    而且Lucene虽已发展10余年,但仍保持着一个活跃的开发度,以适应着日益增长的数据分析需求,最新的6.0版本里引入block k-d trees,全面提升了数字类型和地理位置信息的检索性能,另基于Lucene的Solr和ElasticSearch分布式检索分析系统也发展地如火如荼,ElasticSearch也在我们项目中有所应用。
    Lucene整体使用如图所示:

    结合代码说明一下四个步骤:

    IndexWriter iw=new IndexWriter();//创建IndexWriterDocument doc=new Document( new StringField("name", "Donald Trump", Field.Store.YES)); //构建索引文档iw.addDocument(doc); //做索引库IndexReader reader = DirectoryReader.open(FSDirectory.open(new File(index)));IndexSearcher searcher = new IndexSearcher(reader); //打开索引Query query = parser.parse("name:trump");//解析查询TopDocs results =searcher.search(query, 100);//检索并取回前100个文档号for(ScoreDoc hit:results.hits){Document doc=searcher .doc(hit.doc)//真正取文档}

    使用起来很简单,但只有知道这背后的原理,才能更好地用好Lucene,后面将介绍通用检索原理和Lucene的实现细节。

    1.2 索引原理

    全文检索技术由来已久,绝大多数都基于倒排索引来做,曾经也有过一些其他方案如文件指纹。倒排索引,顾名思义,它相反于一篇文章包含了哪些词,它从词出发,记载了这个词在哪些文档中出现过,由两部分组成——词典和倒排表。

    其中词典结构尤为重要,有很多种词典结构,各有各的优缺点,最简单如排序数组,通过二分查找来检索数据,更快的有哈希表,磁盘查找有B树、B+树,但一个能支持TB级数据的倒排索引结构需要在时间和空间上有个平衡,下图列了一些常见词典的优缺点:

      其中可用的有:B+树、跳跃表、FST
      B+树:
                  mysql的InnoDB B+数结构

     
  • 理论基础:平衡多路查找树

  • 优点:外存索引、可更新

  • 缺点:空间大、速度不够快

  • 跳跃表:
  • 优点:结构简单、跳跃间隔、级数可控,Lucene3.0之前使用的也是跳跃表结构,后换成了FST,但跳跃表在Lucene其他地方还有应用如倒排表合并和文档号索引。

  • 缺点:模糊查询支持不好

  • FST
  •   Lucene现在使用的索引结构

  • 理论基础: 《Direct construction of minimal acyclic subsequential transducers》,通过输入有序字符串构建最小有向无环图。

  • 优点:内存占用率低,压缩率一般在3倍~20倍之间、模糊查询支持好、查询快

  • 缺点:结构复杂、输入要求有序、更新不易

  • Lucene里有个FST的实现,从对外接口上看,它跟Map结构很相似,有查找,有迭代:

  • String inputs={"abc","abd","acf","acg"}; //keyslong outputs={1,3,5,7}; //valuesFST fst=new FST<>();for(int i=0;i iterator=new BytesRefFSTEnum<>(fst);while(iterator.next!=null){...}
  • 100万数据性能测试:

  • 数据结构HashMapTreeMapFST
    构建时间(ms)1855001512
    查询所有key(ms)106218890

      可以看出,FST性能基本跟HaspMap差距不大,但FST有个不可比拟的优势就是占用内存小,只有HashMap10分之一左右,这对大数据规模检索是至关重要的,毕竟速度再快放不进内存也是没用的。
      因此一个合格的词典结构要求有:
      1. 查询速度。
      2. 内存占用。
      3. 内存+磁盘结合。
      后面我们将解析Lucene索引结构,重点从Lucene的FST实现特点来阐述这三点。

    1.3 Lucene索引实现

  • *(本文对Lucene的原理介绍都是基于4.10.3)*

  • Lucene经多年演进优化,现在的一个索引文件结构如图所示,基本可以分为三个部分:词典、倒排表、正向文件。
  • 下面详细介绍各部分结构:

    索引结构

      Lucene现在采用的数据结构为FST,它的特点就是:
      1、词查找复杂度为O(len(str))
      2、共享前缀、节省空间
      3、内存存放前缀索引、磁盘存放后缀词块
      这跟我们前面说到的词典结构三要素是一致的:1. 查询速度。2. 内存占用。3. 内存+磁盘结合。我们往索引库里插入四个单词abd、abe、acf、acg,看看它的索引文件内容。

      tip部分,每列一个FST索引,所以会有多个FST,每个FST存放前缀和后缀块指针,这里前缀就为a、ab、ac。tim里面存放后缀块和词的其他信息如倒排表指针、TFDF等,doc文件里就为每个单词的倒排表。
      所以它的检索过程分为三个步骤:
      1. 内存加载tip文件,通过FST匹配前缀找到后缀词块位置。
      2. 根据词块位置,读取磁盘中tim文件中后缀块并找到后缀和相应的倒排表位置信息。
      3. 根据倒排表位置去doc文件中加载倒排表。
      这里就会有两个问题,第一就是前缀如何计算,第二就是后缀如何写磁盘并通过FST定位,下面将描述下Lucene构建FST过程:
      已知FST要求输入有序,所以Lucene会将解析出来的文档单词预先排序,然后构建FST,我们假设输入为abd,abd,acf,acg,那么整个构建过程如下:

    1. 插入abd时,没有输出。2. 插入abe时,计算出前缀ab,但此时不知道后续还不会有其他以ab为前缀的词,所以此时无输出。3. 插入acf时,因为是有序的,知道不会再有ab前缀的词了,这时就可以写tip和tim了,tim中写入后缀词块d、e和它们的倒排表位置ip_d,ip_e,tip中写入a,b和以ab为前缀的后缀词块位置(真实情况下会写入更多信息如词频等)。4. 插入acg时,计算出和acf共享前缀ac,这时输入已经结束,所有数据写入磁盘。tim中写入后缀词块f、g和相对应的倒排表位置,tip中写入c和以ac为前缀的后缀词块位置。以上是一个简化过程,Lucene的FST实现的主要优化策略有:1. 最小后缀数。Lucene对写入tip的前缀有个最小后缀数要求,默认25,这时为了进一步减少内存使用。如果按照25的后缀数,那么就不存在ab、ac前缀,将只有一个跟节点,abd、abe、acf、acg将都作为后缀存在tim文件中。我们的10g的一个索引库,索引内存消耗只占20M左右。2. 前缀计算基于byte,而不是char,这样可以减少后缀数,防止后缀数太多,影响性能。如对宇(e9 b8 a2)、守(e9 b8 a3)、安(e9 b8 a4)这三个汉字,FST构建出来,不是只有根节点,三个汉字为后缀,而是从unicode码出发,以e9、b8为前缀,a2、a3、a4为后缀,如下图:
  •  

  • 倒排表结构

      倒排表就是文档号集合,但怎么存,怎么取也有很多讲究,Lucene现使用的倒排表结构叫Frame of reference,它主要有两个特点:
      1. 数据压缩,可以看下图怎么将6个数字从原先的24bytes压缩到7bytes。

      2. 跳跃表加速合并,因为布尔查询时,and 和or 操作都需要合并倒排表,这时就需要快速定位相同文档号,所以利用跳跃表来进行相同文档号查找。
      这部分可参考ElasticSearch的一篇博客,里面有一些性能测试:
      ElasticSearch 倒排表

    正向文件

      正向文件指的就是原始文档,Lucene对原始文档也提供了存储功能,它存储特点就是分块+压缩,fdt文件就是存放原始文档的文件,它占了索引库90%的磁盘空间,fdx文件为索引文件,通过文档号(自增数字)快速得到文档位置,它们的文件结构如下:

      

      fnm中为元信息存放了各列类型、列名、存储方式等信息。
      fdt为文档值,里面一个chunk就是一个块,Lucene索引文档时,先缓存文档,缓存大于16KB时,就会把文档压缩存储。一个chunk包含了该chunk起始文档、多少个文档、压缩后的文档内容。
      fdx为文档号索引,倒排表存放的时文档号,通过fdx才能快速定位到文档位置即chunk位置,它的索引结构比较简单,就是跳跃表结构,首先它会把1024个chunk归为一个block,每个block记载了起始文档值,block就相当于一级跳表。
      所以查找文档,就分为三步:
      第一步二分查找block,定位属于哪个block。
      第二步就是根据从block里根据每个chunk的起始文档号,找到属于哪个chunk和chunk位置。
      第三步就是去加载fdt的chunk,找到文档。这里还有一个细节就是存放chunk起始文档值和chunk位置不是简单的数组,而是采用了平均值压缩法。所以第N个chunk的起始文档值由 DocBase + AvgChunkDocs * n + DocBaseDeltas[n]恢复而来,而第N个chunk再fdt中的位置由 StartPointerBase + AvgChunkSize * n + StartPointerDeltas[n]恢复而来。
      从上面分析可以看出,lucene对原始文件的存放是行是存储,并且为了提高空间利用率,是多文档一起压缩,因此取文档时需要读入和解压额外文档,因此取文档过程非常依赖随机IO,以及lucene虽然提供了取特定列,但从存储结构可以看出,并不会减少取文档时间。

    Lucene总的来说是:

    • 一个高效的,可扩展的,全文检索库。
    • 全部用Java实现,无须配置。
    • 仅支持纯文本文件的索引(Indexing)和搜索(Search)。
    • 不负责由其他格式的文件抽取纯文本文件,或从网络中抓取文件的过程。

    在Lucene in action中,Lucene 的构架和过程如下图,

    说明Lucene 是有索引和搜索的两个过程,包含索引创建,索引,搜索三个要点。

    让我们更细一些看Lucene的各组件:

    • 被索引的文档用Document对象 表示。
    • IndexWriter 通过函数addDocument 将文档添加到索引中,实现创建索引的过程。
    • Lucene 的索引是应用反向索引。
    • 当用户有请求时,Query 代表用户的查询语句。
    • IndexSearcher 通过函数search 搜索Lucene Index 。
    • IndexSearcher 计算term weight 和score 并且将结果返回给用户。
    • 返回给用户的文档集合用TopDocsCollector 表示。

     

    那么如何应用这些组件呢?

    让我们再详细到对Lucene API 的调用实现索引和搜索过程。

    • 索引过程如下:
      • 创建一个IndexWriter 用来写索引文件,它有几个参数,INDEX_DIR 就是索引文件所存放的位置,Analyzer 便是用来对文档进行词法分析和语言处理的。
      • 创建一个Document 代表我们要索引的文档。
      • 将不同的Field 加入到文档中。我们知道,一篇文档有多种信息,如题目,作者,修改时间,内容等。不同类型的信息用不同的Field 来表示,在本例子中,一共有两类信息进行了索引,一个是文件路径,一个是文件内容。其中FileReader 的SRC_FILE 就表示要索引的源文件。
      • IndexWriter 调用函数addDocument 将索引写到索引文件夹中。
    • 搜索过程如下:
      • IndexReader 将磁盘上的索引信息读入到内存,INDEX_DIR 就是索引文件存放的位置。
      • 创建IndexSearcher 准备进行搜索。
      • 创建Analyer 用来对查询语句进行词法分析和语言处理。
      • 创建QueryParser 用来对查询语句进行语法分析。
      • QueryParser 调用parser 进行语法分析,形成查询语法树,放到Query 中。
      • IndexSearcher 调用search 对查询语法树Query 进行搜索,得到结果TopScoreDocCollector 。

    然而当进入Lucene的源代码后,发现Lucene有很多包,关系错综复杂。

    然而通过下图,我们不难发现,Lucene的各源码模块,都是对普通索引和搜索过程的一种实现。

    此图是上一节介绍的全文检索的流程对应的Lucene实现的包结构。(参照http://www.lucene.com.cn/about.htm 中文章《开放源代码的全文检索引擎Lucene》)

    • Lucene 的analysis 模块主要负责词法分析及语言处理而形成Term 。
    • Lucene 的index 模块主要负责索引的创建,里面有IndexWriter 。
    • Lucene 的store 模块主要负责索引的读写。
    • Lucene 的QueryParser 主要负责语法分析。
    • Lucene 的search 模块主要负责对索引的搜索。
    • Lucene 的similarity 模块主要负责对相关性打分的实现。

    转载于: http://m.2cto.com/kf/201701/584148.html

    需要做网站?需要网络推广?欢迎咨询客户经理 13272073477