一、什么是全文检索
1.1 常见的数据类型
结构化数据:类型固定并且是有限长度。例如数据库中的数据。
非结构化数据:类型不固定,格式不固定,长度不固定。例如磁盘上的文档,word、excel、pdf。
1.2 结构化数据的查询
可以使用标准的sql语句查询。查询简单。
1.3 非结构化数据的查询
实现方法:
1. 使用windows检索
2. 目测
3. 顺序扫描文件内容。(适用少量文档)
4. 大量文档的情况下
非结构化数据变成结构化数据。先把文件的内容根据空格进行分词,然后在词汇列表中搜索关键词,根据关键词找到对应的文档。得到查询结果。这种先分词然后再查询的过程就叫做全文检索。
1.4 全文检索的概念
全文检索是一种将文件中所有文本与检索项匹配的文字资料检索方法。全文检索首先将要查询的目标文档中的词提取出来,组成索引,通过查询索引达到搜索目标文档的目的。
这种先建立索引,再对索引进行搜索的过程就叫全文检索(Full-text Search)。
索引可以一次创建多次查询。可以提高查询的效率。
1.5 全文检索的应用领域
搜索引擎:搜索的是互联网的网页。例如谷歌、百度等。
站内搜索:只搜索网站自己的内容。例如论坛搜索。微博搜索。
电商搜索:搜索的是商品信息。例如:京东、淘宝的搜索。
二、Lucene实现全文检索的流程
2.1 索引和搜索流程图
2.2创建索引
2.2.1 获得文档
原始文档:要查询的目标文档,目前暂用磁盘上的文件。可以使用文件流读取文件的内容,从而获得原始文档。
搜索引擎的原始文档:互联网上所有的网页。可以使用爬虫程序获得原始文档。
2.2.2 创建文档对象
在Lucene中为每一个文件创建一个Document对象。存储文件相关属性。比如说文件名、文件内容、文件的大小、文件的路径。把每个属性存储到一个field中,每个field叫做域。一个document中包含多个域,不同的document的域可以不同。每个document都有一个唯一的ID。
2.2.3 分析文档
分析文档的过程就是提取关键词的过程,要对文件名和文件内容这两个域的内容进行分析。可以先根据空格进行分词得到一个基础的词汇列表。统一转换大小写,可以统一转换成小写。去除标点符号,去除停用词(停用词就是无意义的词语)。最终得到一个关键词列表。每一个词叫做一个Term。*Term存储两部分内容,一部分是关键词所属的域,另一部分是关键词的内容。不同的域拆分出来的相同的词语是不同的term。*例如文件名中的Java和问价内容中的Java不是同一个term。
2.2.4 创建索引
索引就是一个快速查询的数据结构。是基于第三步得到的词汇列表创建索引。
关键词和文档对应关系的存储:
最终得到倒排索引的结构。
对所有文档分析得出的语汇单元进行索引,索引的目的是为了搜索,最终要实现只搜索被索引的语汇单元从而找到Document(文档)。
注意:创建索引是对语汇单元索引,通过词语找文档,这种索引的结构叫倒排索引结构。
索引库的结构:索引库就是磁盘上的一堆文件
2.3 查询索引
2.3.1 用户接口
用户输入查询条件的地方。例如,百度搜索的搜索框就是用户接口
2.3.2 创建查询对象
后台接收到用户输入的搜索条件,根据查询条件创建一个Query对象。在Lucene中也有查询语法。
最基础的查询语法是指定关键词,还要指定要查询的域。
文件名:关键词
2.3.3 执行查询
根据查询对象到索引中进行查询,查询到关键词得到一个documentId的列表。根据文档的id取出对应的document对象。
2.3.4 渲染结果
从document对象中取出域的内容,展示给用户。
包括分页、排序、高亮显示。
三、Lucene的入门程序
3.1 开发环境
创建一个Java工程,导入jar包。
- 核心包 lucene-core-4.10.3.jar
- 分析器的jar包lucene-analyzers-common-4.10.3.jar
- 文件读取的jar包commons-io-2.4.jar
- Jdk的版本:要求是1.7以上
3.2 创建索引
3.2.1 实现步骤
- 第一步:指定索引库存放的位置。可以是文件系统也可以是内存。
- 第二步:创建一个IndexWriter对象,需要一个索引库存放路径,还需要一个分析器对象。
- 第三步:为每个文件创建一个document对象。把文件的属性存储到field中。
field有三个属性:
1) 是否分词
2) 是否索引,判断标准是是否在此域上进行搜索。
3) 是否存储,判断标准是是否要把域中的内容展示给用户。如果不展示给用户就不存储,可以节省空间。
Field类 | 数据类型 | Analyzed是否分析 | Indexed是否索引 | Stored是否存储 | 说明 |
---|---|---|---|---|---|
短文本 | 中等文本 | 稍微长一点的文本 | |||
稍微长一点的文本 | 短文本 | 中等文本 |
3.2.2 代码实现
//创建索引
public void createIndex() throws Exception {
//指定索引库存放的位置
//放到内存中
//Directory directory = new RAMDirectory();
//放到文件系统中,存放到磁盘上
Directory directory = FSDirectory.open(new File("D:\\temp\\jee15\\index"));
//创建Indexwriter对象
//创建分析器对象
Analyzer analyzer = new StandardAnalyzer();
//创建indexwriterConfig
//第一个参数匹配Lucene的版本
//第二个参数分析器对象
IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, analyzer);
//第一个参数:索引存放的路径
//第二个参数:是indexWriter的配置信息
IndexWriter indexWriter = new IndexWriter(directory, config);
//读取文件
File dir = new File("D:\\lucene\\01.参考资料\\searchsource");
for (File file : dir.listFiles()) {
//取文件名称
String fileName = file.getName();
//文件内容
String fileContent = FileUtils.readFileToString(file);
//文件的路径
String filePath = file.getPath();
//文件的大小
Long fileSize = FileUtils.sizeOf(file);
//创建文档对象
Document document = new Document();
//向文档对象中添加域
//第一个参数:域的名称
//第二个参数:域的值
//第三个参数:是否存储。存储:Store.YES 不存储:Store.NO
Field nameField = new TextField("name", fileName, Store.YES);
Field contentField = new TextField("content", fileContent, Store.YES);
Field sizeField = new LongField("size", fileSize, Store.YES);
//StoredField默认是只存储
Field pathField = new StoredField("path", filePath);
//把域添加到document对象中
document.add(nameField);
document.add(contentField);
document.add(sizeField);
document.add(pathField);
//把文档对象写入索引库
indexWriter.addDocument(document);
}
//关闭indexwriter
indexWriter.close();
}
3.3 查询索引
3.3.1 查询步骤
第一步:指定索引库存放的位置。
第二步:打开索引库使用IndexReader对象打开,创建一个indexreader对象。
第三步:创建一个Indexsearcher对象,需要一个indexreader对象作为参数。
第四步:创建一查询对象,需要指定要搜索的域以及要搜索的内容。
第五步:执行查询返回查询结果。
第六步:遍历查询结果。
第七步:关闭indexreader对象。
3.3.2 代码实现
//查询索引
public void searchIndex() throws Exception {
//指定索引库存放 的位置
Directory directory = FSDirectory.open(new File("D:\\temp\\jee15\\index"));
//创建indexreader打开索引库
IndexReader indexReader = DirectoryReader.open(directory);
//创建indexsearcher对象
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
//创建查询
//Term类的构造方法两个参数1:要搜索的域 2:要搜索的关键词
Query query = new TermQuery(new Term("content", "lucene"));
//执行查询
//第一个参数:query对象
//第二个参数:指定返回结果的最大值
TopDocs topDocs = indexSearcher.search(query, 10);
//取查询的总结果
System.out.println("查询结果的总数量:" + topDocs.totalHits);
for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
//scoreDoc.doc对应的都是document的ID
//可以根据document的id取document对象。
Document document = indexSearcher.doc(scoreDoc.doc);
//取document对象中域的内容
System.out.println(document.get("name"));
System.out.println(document.get("content"));
System.out.println(document.get("size"));
System.out.println(document.get("path"));
}
//关闭indexreader
indexReader.close();
}
四、索引库的查询
4.1 使用Query的子类查询
4.1.1 TermQuery
根据关键词查询,需要指定要查询的域以及要查询的关键词。
4.1.2 NumericRangeQuery
@Test
public void testNumericRangeQuery() throws Exception {
//以读的方式打开索引库
Directory directory = FSDirectory.open(new File("D:\\temp\\jee15\\index"));
IndexReader indexReader = DirectoryReader.open(directory);
//创建一个indexsearcher对象
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
//创建查询
//参数:1:域的名称 2:最小值 3:最大值 4:是否包含最小值 5:是否包含最大值
Query query = NumericRangeQuery.newLongRange("size", 1l, 1000l, true, true);
//执行查询
TopDocs topDocs = indexSearcher.search(query, 10);
System.out.println("查询结果的总数量:" + topDocs.totalHits);
for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
//取document对象
Document document = indexSearcher.doc(scoreDoc.doc);
System.out.println(document.get("name"));
System.out.println(document.get("content"));
System.out.println(document.get("size"));
System.out.println(document.get("path"));
}
//关闭indexReader
indexReader.close();
}
4.1.3 MatchAllDocsQuery
查询索引库中的所有文档
//查询所有文档
@Test
public void testMatchAllDocsQuery() throws Exception {
//获得indexsearcher对象
IndexSearcher indexSearcher = getIndexSearcher();
//创建查询
Query query = new MatchAllDocsQuery();
//执行查询
searchExec(query, indexSearcher);
}
4.1.4 BooleanQuery
多条件组合查询
//多条件组合查询
@Test
public void testBooleanQuery() throws Exception {
IndexSearcher indexSearcher = getIndexSearcher();
//创建一个booleanQuery对象
BooleanQuery query = new BooleanQuery();
//查询条件
Query query1 = new TermQuery(new Term("name", "apache"));
Query query2 = new TermQuery(new Term("name", "lucene"));
query.add(query1, Occur.MUST);
query.add(query2, Occur.MUST);
searchExec(query, indexSearcher);
}
Occur.MUST:相当于 and
Occur.SHOULD:相当于or
Occur.MUST_NOT:相当于not
4.2 使用QueryParser查询
4.2.1 queryParser
//使用Queryparser查询索引
@Test
public void testQueryParser() throws Exception {
IndexSearcher indexSearcher = getIndexSearcher();
//创建一个QueryParser对象
//第一个参数:默认搜索的域
//第二个参数:分析器对象。
QueryParser queryParser = new QueryParser("content", new IKAnalyzer());
Query query = queryParser.parse("lucene is a apache project");
//执行查询
searchExec(query, indexSearcher);
}
还可以使用Lucene的查询语法进行查询。
1、最基础的查询语法:
搜索域:关键字
Content:java
2、范围查询语法:
搜素域:[最小值 TO 最大值]
size:[1 TO 1000]
范围查询语句在lucene中只支持字符串类型,在solr中这个语法就支持数值类型。在lucene中如果是数值类型的范围查询还需要使用NumericRangeQuery。
3、匹配所有文档
:
4、组合条件查询
1)两个条件直接是And关系
+name:apache +name:lucene = name:apache AND name:lucene
2)两个条件直接是or的关系
name:apache name:lucene = name:apache OR name:lucene
3)其中一个条件是非的关系
name:apache -name:lucene = name:apache NOT name:lucene
其中“-”就代表非的关系
如果在queryparser中指定了查询的域,默认搜索域不生效。
4.2.2 MultFieldQueryparser
可以指定多个默认搜索域
@Test
public void testMultiFiledQueryParser() throws Exception {
IndexSearcher indexSearcher = getIndexSearcher();
//创建一个MultFiledQueryParser对象
//第一个参数默认搜索的域,多个域
//分析器对象
String[] fields = {"name","content"};
MultiFieldQueryParser queryParser = new MultiFieldQueryParser(fields, new IKAnalyzer());
Query query = queryParser.parse("lucene is a apache project");
System.out.println(query);
//执行查询
searchExec(query, indexSearcher);
}
getIndexSearcher(); searchExec();
private IndexSearcher getIndexSearcher() throws Exception{
//以读的方式打开索引库
Directory directory = FSDirectory.open(new File("D:\\Lucene\\index"));
IndexReader indexReader = DirectoryReader.open(directory);
//创建一个indexsearcher对象
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
return indexSearcher;
}
private void searchExec(Query query,IndexSearcher indexSearcher) throws Exception{
//执行查询
TopDocs topDocs = indexSearcher.search(query, 10);
System.out.println("查询结果的总数量:" + topDocs.totalHits);
for(ScoreDoc scoreDoc : topDocs.scoreDocs){
//取document对象
Document document = indexSearcher.doc(scoreDoc.doc);
System.out.println(document.get("name"));
System.out.println(document.get("size"));
System.out.println(document.get("path"));
}
//关闭indexReader
indexSearcher.getIndexReader().close();
}