一. 索引
1. 索引流程
获取内容(爬虫等) —>
建立文档(获取内容转换成文档:标题、 征文、 摘要、 作者、 链接。 加权), 借助 Tika 框架可以方便地从 PDF、 Word 等中方便提取 —>
文档分析 (文本分割成语汇单元的独立原子元素, 和语言中的单词很像。 如何处理连接一体的各个单词呢? 是否需要进行语法修正? 是否需要插入同义词? 是否需要将单数和复数格式单词合并成同一个语汇单元? 常用词干提取器, running, run, runs 映射到 run) —>
文档索引 (文档加入索引列表)
2. 索引核心类
IndexWriter 是索引过程的核心组件。 可以理解为: 提供对索引文件的写入操作, 但不能用于读取或者搜索索引。 IndexWriter 需要开辟一定空间来存储索引, 该功能可以由 Directory 完成。
Director 类描述了 Lucene 索引的存放位置。
Analyzer 负责从索引文本文件中提取语汇单元, 并提出剩下的无用信息。
Document 对表一些 Field 的集合。 可以理解为虚拟文档 - 比如 Web 页面、 Email 信息或者文本文件, 然后可以从中获取大量数据。
3. 索引调试
使用 IndexWriter 的 setInfoStream(System.out) 该代码能够揭示有关段刷新和段合并的诊断信息, 可以帮助我们调整索引参数。 也可以使用 Luck 第三方工具来监视。
二. 搜索
1. 搜索流程
搜索质量主要由查准率(过滤非相关文档能力)和查全率(查找相关文档的能力)来衡量。
用户搜索界面:直观、 简洁。 最好的例子当然是 Google。 高亮关键词, 排序都至关重要 —>
建立查询(Build Query): Lucene 提供了一个称之为查询解析器(QueryParser)的强大开发包, 用它可以根据通用查询语法(布尔运算、 短语查询、 通配符)将用户输入的文本处理成查询对象。 在这期间, 也可以进行加权或者过滤, 如电商过滤掉脱销产品防止客户流到其他电商。 —>
搜索查询(Search Query): 查询检索索引并返回与查询语句匹配的文档, 结果返回时按照查询请求来排序。
常见搜索理论模型:
纯布尔模型 — 文档不管是否匹配查询请求, 都不会被评分。 即无序的, 仅是匹配文档的一个集合。
向量空间模型 — 查询语句和文档都是高维空间向量模型, 每一个独立项都是一个维度。 查询语句和文档之间的相关性或相似性由各自向量之间的距离计算得到。
概率模型 — 采用全概率方法来计算文档和查询语句的匹配概率。
Lucene 并没有提供有关搜索范围的处理模块。 但 Solr 和 Nutch 都提供了对索引拆分和复制。 Katta 也能提供。 Elastic search 提供了另一个选择。
2. 搜索核心类
IndexSearcher 类用于搜索由 IndexWriter 类创建的索引。 最简单的搜索方法将单个 Query 对象和 int topN 计数作为参数, 返回一个 TopDocs 对象。
Term 搜索功能基本单元。
Query Lucene 含有 Query 的子类。 TermQuery, BooleanQuery、 PhraseQuery、 PrefixQuery、 PhrasePrefixQuery、 TermRangeQuery、 NumericRangeQuery、 FilteredQuery 和 SpanQuery。
三. 代码实战
import java.io.IOException; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.index.*; import org.apache.lucene.search.*; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.store.RAMDirectory; /** * Lucene 检索内存索引 * * @author wenniuwuren * */ public class LuceneTest { public static void main(String[] args) throws IOException { long startTime = System.currentTimeMillis(); // 新建一个内存目录对象 Directory directory = new RAMDirectory(); //Directory diskDirectory = new FSDirectory(); //使用 IKAnalyzer 进行分词 StandardAnalyzer analyzer = new StandardAnalyzer(); IndexWriterConfig config = new IndexWriterConfig(analyzer); /* * 创建索引写入对象,该对象既可以把索引写入到磁盘中也可以写入到内存中。 参数说明: * directory:目录对象,也可以是FSDirectory 磁盘目录对象 * config:分词器,分词器就是将检索的关键字分割成一组组词组, * 例子是 Lucene 自带的, 也可以使用第三方的分词器。 */ IndexWriter writer = new IndexWriter(directory, config); // 创建Document 文档对象,在lucene中创建的索引可以看成数据库中的一张表, // 表中也可以有字段,往里面添加内容之后可以根据字段去匹配查询 // 下面创建的doc对象中添加了三个字段,分别为name,sex,dosomething Document doc = new Document(); /* * 参数说明 public Field(String name, String value, Store store, Index index) * name : 字段名称 * value : 字段的值 * * store : * Field.Store.YES:存储字段值(未分词前的字段值)可以用 IndexReader 恢复。 对于需要展示搜索结果的一些域很有用(URL、 标题) * Field.Store.NO:不存储,存储与索引没有关系。 通常跟 Index.ANALYZED 选项共同来索引大的文本, 通常这些不需要恢复初始格式, 如 Wbe 页面征文 * Field.Store.COMPRESS:压缩存储,用于长文本或二进制,但性能受损。 CPU 计算时间换空间 * * index : 建立索引的方式,是否建立分词等等 * Field.Index.ANALYZED:分词建索引, 使每个分词可以被索引 * Field.Index.NOT_ANALYZED:不分词但是索引。 适用于不能被分解的值: URL、 社保号等。 尤其适用于精确搜索。 * Field.Index.ANALYZED_NO_NORMS:分词建索引,不在索引中存储 norms 信息。 norms 记录了索引中的 index-time boost 信息, 搜索时比较耗费内存 * Field.Index.NOT_ANALYZED_NO_NORMS:与 Field.Index.NOT_ANALYZED 相似, 但也不存储 norms(加权基准)。通常用于搜索期间节省索引空间和减少内存耗费。 * Field.Index.NO -- 使对应的值不能被搜索 */ doc.add(new Field("name", "wenniuwuren", Field.Store.YES,Field.Index.ANALYZED)); doc.add(new Field("sex", "男性", Field.Store.YES,Field.Index.NOT_ANALYZED)); doc.add(new Field("dosometing", "I am learning lucene ",Field.Store.YES, Field.Index.ANALYZED)); // 文档对象加入索引 writer.addDocument(doc); writer.close(); // 这里可以提前关闭,因为dictory 写入内存之后 与IndexWriter 没有任何关系了 // 因为索引放在内存中,所以存放进去之后要立马测试,否则,关闭应用程序之后就检索不到了 // 创建IndexSearcher 检索索引的对象,里面要传递上面写入的内存目录对象directory // 打开索引文件 磁盘方式FSDirectory.open(new File()); IndexReader 开销较大, 建议搜友搜索期间使用同一个 IndexReader reader = DirectoryReader.open(directory); IndexSearcher searcher = new IndexSearcher(reader); // TermQuery 最基本的 Query 实现, 解析查询字符串 Query query = new TermQuery(new Term("dosometing", "lucene")); // Query query = new TermQuery(new Term("sex", "男")); // 未分词, 搜索不到 // Query query = new TermQuery(new Term("name", "wen")); // 虽然分词了, 但是英文需要空格隔开才算一个分词, 搜索不到 // 去索引目录中查询,返回的是 TopDocs 对象,里面存放的就是上面放的 document 文档对象的前(TOP)几个内容 TopDocs result = searcher.search(query, 10); long endTime = System.currentTimeMillis(); System.out.println("总共花费" + (endTime - startTime) + "毫秒,检索到" + result.totalHits + "条记录。"); for (int i = 0; i < result.scoreDocs.length; i++) { Document document = searcher.doc(result.scoreDocs[i].doc); System.out.println("name:" + document.getField("name").stringValue()); System.out.println("sex:" + document.getField("sex").stringValue()); System.out.println("dosomething:" + document.getField("dosometing").stringValue()); // 对搜索结果评分细节 Explanation explanation = searcher.explain(query, result.scoreDocs[i].doc); System.out.println(explanation.toString()); } writer.close(); directory.close(); } }