先创建索引然后查询索引的过程叫做全文检索
索引一次创建可以多次是哟红,表现为每次每次查询速度很快
站内搜索
论坛搜索、微博、文章搜索
电商搜索
淘宝搜索、京东搜索
什么是Lucene
Lucene是一个基于Java开发全文检索工具包
原始文档:要给予哪些数据来进行检索,那么这些数据就是原始文档
搜索引擎:使用爬虫获得原始文档
站内搜索:数据库中的数据
案例:直接使用io流读取磁盘上的文件
对应每个原始文档创建一个Document对象
每个document对象中包含多个域(field)
域中保存就是原始文档数据
域的名称
注意:每个Document可以有多个Field,不同的Document可以有不同的Field,同一个Document可以有相同的Field(域名和域值都相同)
每个文档都有一个唯一的编号,就是文档id。
就是分词的过程
将原始内容创建为包含域(Field)的文档(document),需要再对域中的内容进行分析,分析的过程是经过对原始文档提取单词
、将字母转为小写
、去除标点符号
、去除停用词
等过程生成最终的语汇单元,可以将语汇单元理解为一个一个的单词。
比如下边的文档经过分析如下:
原文档内容:
Lucene is a Java full-text search engine. Lucene is not a complete
application, but rather a code library and API that can easily be used
to add search capabilities to applications.
分析后得到的语汇单元:
lucene、java、full、search、engine。。。。
每个单词叫做一个Term,不同的域中拆分出来的相同的单词是不同的term。term中包含两部分一部分是文档的域名(Term中包含),另一部分是单词的内容。
例如:文件名中包含apache和文件内容中包含的apache是不同的term
基于关键词列表创建一个索引,保存到索引库中。索引的目的是为了搜索,最终要实现只搜索被索引的语汇单元从而找到Document(文档)。
索引库中包括:
注意:创建索引是对语汇单元索引,通过词语找文档,这种索引的结构叫倒排索引结构。
传统方法是根据文件找到该文件的内容,在文件内容中匹配搜索关键字,这种方法是顺序扫描方法,数据量大、搜索慢。
倒排索引结构是根据内容(词语)找文档,如下图:
倒排索引结构也叫反向索引结构,包括索引和文档两部分,索引即词汇表,它的规模较小,而文档集合较大。
查询索引也是搜索的过程。搜索就是用户输入关键字,从索引(index)中进行搜索的过程。根据关键字搜索索引,根据索引找到对应的文档,从而找到要搜索的内容(这里指磁盘上的文件)。
全文检索系统提供用户搜索的界面供用户提交搜索的关键字,搜索完成展示搜索结果。Lucene不提供制作用户搜索界面的功能,需要根据自己的需求开发搜索界面。
用户输入查询关键字执行搜索之前需要先构建一个查询对象,查询对象中可以指定查询要搜索的Field文档域
、查询关键字
等,查询对象会生成具体的查询语法,
例如:语法
“fileName:lucene”
表示要搜索Field域的内容为“lucene”
的文档
搜索索引过程:
根据查询语法在倒排索引词典表中分别找出对应搜索词的索引,从而找到索引所链接的文档链表。
比如搜索语法为“fileName:lucene”表示搜索出fileName域中包含Lucene的文档。
搜索过程就是在索引上查找域为fileName,并且关键字为Lucene的term,并根据term找到文档id列表。
以一个友好的界面将查询结果展示给用户,用户根据搜索结果(文档id)找自己想要的信息,为了帮助用户很快找到自己的结果,提供了很多展示的效果,比如搜索结果中将关键字高亮显示,百度提供的快照等。
Lucene是开发全文检索功能的工具包,从官方网站下载lucene-7.4.0,并解压。
官方网站:http://lucene.apache.org/
版本:lucene-7.4.0
Jdk要求:1.8以上
使用的jar包
lucene-core-7.4.0.jar
lucene-analyzers-common-7.4.0.jar
commonns-io.jar
代码实现
@Test
public void createIndex() throws Exception {
// 1. 创建一个Director对象,指定索引库保存的地址
//把索引库保存在内存中
//Directory directory = new RAMDirectory();
//把索引库保存在硬盘中
Directory directory = FSDirectory.open(new File("H:\\STUDENT\\IDEA_Project_Menu\\Lucene_project\\index").toPath());
// 2. 基于Directory对象创建一个IndexWriter对象
//使用IKAnalyzer
IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer());
IndexWriter indexWriter = new IndexWriter(directory,config);
// 3. 读取磁盘上文件,对应每个文件创建一个文档对象
File dir = new File("H:\\STUDENT\\IDEA_Project_Menu\\Lucene_project\\searchsource");
File[] files = dir.listFiles(); //得到一个文件列表
for (File f : files){
//取文件名
String fileName = f.getName();
//文件的路径
String filePath = f.getPath();
//文件的内容
String s = FileUtils.readFileToString(f,"UTF-8");
//文件的大小
Long fileSize = FileUtils.sizeOf(f);
//创建Field
//参数1 域的名称,2.域的内容 3,是否存储 //域类型改造
Field fieldName = new TextField("name",fileName,Field.Store.YES);
//Field fieldPath = new TextField("filePath",filePath, Field.Store.YES);
Field fieldPath = new StoredField("filePath",filePath); //只存储
Field fieldContent = new TextField("content",s, Field.Store.YES);
//Field fieldSize = new TextField("size",fileSize+"",Field.Store.YES);
Field fieldSizeValue = new LongPoint("size",fileSize); //只存值
Field fieldSizeStore = new StoredField("size",fileSize); //只存储
//创建文档对象
Document document = new Document();
//4.向文档对象中添加域
document.add(fieldName);
document.add(fieldPath);
document.add(fieldSizeValue);
document.add(fieldSizeStore);
document.add(fieldContent);
// 5. 把文档对象写入索引库
indexWriter.addDocument(document);
}
// 6. 关闭indexwriter对象
indexWriter.close();
}
我们使用的luke的版本是luke-7.4.0,跟lucene的版本对应的。可以打开7.4.0版本的lucene创建的索引库。需要注意的是此版本的Luke是jdk9编译的,所以要想运行此工具还需要jdk9才可以。
查找接口的实现类:
ctrl + alt +B
查看类或接口的继承关系:
ctrl + h
代码实现
@Test
public void searchIndex() throws Exception{
//1. 创建一个Directory对象,指定索引库的位置
Directory directory = FSDirectory.open(new File("H:\\STUDENT\\IDEA_Project_Menu\\Lucene_project\\index").toPath());
//2. 创建一个indexReader对象,需要指定Directory对象
IndexReader indexReader = DirectoryReader.open(directory);
//3. 创建一个indexsearcher对象,需要指定IndexReader对象
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
//4. 创建一个Query对象,TermQuery,指定查询的域和查询的关键词。
Query query = new TermQuery(new Term("name","apache"));
//5. 执行查询,得到一个查询结果TopDocs对象
//参数1、查询对象 2、查询结果返回的最大记录数
TopDocs topDocs = indexSearcher.search(query,10);
//6. 取查询结果的总记录数
System.out.println("查询的总记录数"+topDocs.totalHits);
//7. 取文档列表
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
//8. 打印文档中内容
for (ScoreDoc doc: scoreDocs){
//取文档id
int docId = doc.doc;
//根据id取文档对象
Document document = indexSearcher.doc(docId);
System.out.println(document.get("name"));
System.out.println(document.get("filePath"));
System.out.println(document.get("size"));
System.out.println(document.get("content"));
System.out.println("--------------------------");
}
//9. 关闭IndexReader对象
indexReader.close();
}
默认使用的是标准分析器strandardAnalyzer
使用Analyzer对象的tokenstram方法返回一个TokenStream对象,词对象中包含了最终的分词结果
创建一个Analyzer对象,strandardAnalyzer对象
使用分析器对象的tokenStream方法获取一个TokenStream对象
向TokenStream对象中设置一个引用。相当于是一个指针
调用TokenStream对象的rest方法,如果不调用则抛异常
使用while循环遍历TokenStream对象
关闭TokenStream对象
/**
* 查看分析器的效果
* @throws Exception
*/
@Test
public void testTokenStream() throws Exception{
//- 创建一个Analyzer对象,strandardAnalyzer对象
//Analyzer analyzer = new StandardAnalyzer();
Analyzer analyzer = new IKAnalyzer();
//- 使用分析器对象的tokenStream方法获取一个TokenStream对象
TokenStream tokenStream = analyzer.tokenStream("test","Lucene是apache软件基金会4 jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,但它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构");
//- 向TokenStream对象中设置一个引用。相当于是一个指针
CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);
//- 调用TokenStream对象的rest方法,如果不调用则抛异常
tokenStream.reset();
//- 使用while循环遍历TokenStream对象
while (tokenStream.incrementToken()){
System.out.println(charTermAttribute.toString());
}
//- 关闭TokenStream对象
tokenStream.close();
}
StandardAnalyzer:
单字分词:就是按照中文一个字一个字地进行分词。如:“我爱中国”,
效果:“我”、“爱”、“中”、“国”。
SmartChineseAnalyzer
对中文支持较好,但扩展性差,扩展词库,禁用词库和同义词库等不好处理
注意:扩展词典严禁使用Windows记事本编辑,保证扩展词典的编码格式是utf-8
扩展词典:添加一些新词
停用词词典:无意义的词或者敏感词
@Test
public void addDocument() throws Exception {
//索引库存放路径
Directory directory = FSDirectory.open(new File("D:\\temp\\index").toPath());
IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer());
//创建一个indexwriter对象
IndexWriter indexWriter = new IndexWriter(directory, config);
//...
}
是否分析:是否对域的内容进行分词处理。前提是我们要对域的内容进行查询。
是否索引:将Field分析后的词或整个Field值进行索引,只有索引方可搜索到。
比如:商品名称、商品简介分析后进行索引,订单号、身份证号不用分析但也要索引,这些将来都要作为查询条件。
是否存储:将Field值存储在文档中,存储在文档中的Field才可以从Document中获取
比如:商品名称、订单号,凡是将来要从Document中获取的Field都要存储。
是否存储的标准:是否要将内容展示给用户
Field类 | 数据类型 | Analyzed 是否分析 | Indexed 是否索引 | Stored 是否存储 | 说明 |
---|---|---|---|---|---|
StringField(FieldName, FieldValue,Store.YES)) | 字符串 | N | Y | Y或N | 这个Field用来构建一个字符串Field,但是不会进行分析,会将整个串存储在索引中,比如(订单号,姓名等) 是否存储在文档中用Store.YES或Store.NO决定 |
LongPoint(String name, long… point) | Long型 | Y | Y | N | 可以使用LongPoint、IntPoint等类型存储数值类型的数据。让数值类型可以进行索引。但是不能存储数据,如果想存储数据还需要使用StoredField。 |
StoredField(FieldName, FieldValue) | 重载方法,支持多种类型 | N | N | Y | 这个Field用来构建不同类型Field 不分析,不索引,但要Field存储在文档中 |
TextField(FieldName, FieldValue, Store.NO) 或 TextField(FieldName, reader) | 字符串 或 流 | Y | Y | Y或N | 如果是一个Reader, lucene猜测内容比较多,会采用Unstored的策略. |
/**
* 新添加的文档
* @throws Exception
*/
@Test
public void addDocument() throws Exception{
//创建一个IndexWriter对象,需要使用IKAnalyzer作为分析器
/*IndexWriter indexWriter = new IndexWriter(
FSDirectory.open(new File("H:\\STUDENT\\IDEA_Project_Menu\\Lucene_project\\index").toPath()),
new IndexWriterConfig(new IKAnalyzer())
);*/
//创建一个Document对象
Document document = new Document();
//向document对象中添加域
document.add(new TextField("name","新添加的文件", Field.Store.YES));
document.add(new TextField("content","新添加文件的内容",Field.Store.NO));
document.add(new StoredField("path","H:\\STUDENT\\IDEA_Project_Menu\\Lucene_project\\hello"));
//把文档写入索引库
indexWriter.addDocument(document);
//关闭索引库
indexWriter.close();
}
@Test
public void deleteAllDocument() throws Exception{
//删除全部文档
indexWriter.deleteAll();
//关闭索引库
indexWriter.close();
}
@Test
public void deleteDocumentByQuery() throws Exception{
indexWriter.deleteDocuments(new Term("name","apache"));
indexWriter.close();
}
@Test
public void updateDocument() throws Exception{
//创建一个新的文档对象
Document document = new Document();
//向文档对象中添加域
document.add(new TextField("name","这是更新够的文档",Field.Store.YES));
document.add(new TextField("name1","这是更新够的文档1",Field.Store.YES));
document.add(new TextField("name2","这是更新够的文档2",Field.Store.YES));
//更新操作
indexWriter.updateDocument(new Term("name","spring"),document);
//关闭索引
indexWriter.close();
}
更新的原理就是先删除然后再添加
根据关键词进行查询
需要指定查询的域以及要查询的关键词
/**
* 范围区间查询
* @throws Exception
*/
@Test
public void testRangeQuery() throws Exception{
//创建一个Query对象
Query query = LongPoint.newRangeQuery("size",0l,100l);
//执行查询
printResult(query);
}
private void printResult(Query query) throws Exception{
TopDocs topDocs = indexSearcher.search(query,10);
System.out.println("命中数据:"+topDocs.totalHits);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (ScoreDoc doc : scoreDocs){
//取文档id
int docId = doc.doc;
//根据id取文档对象
Document document = indexSearcher.doc(docId);
System.out.println(document.get("name"));
System.out.println(document.get("filePath"));
System.out.println(document.get("size"));
System.out.println(document.get("content"));
System.out.println("--------------------------");
}
indexReader.close();
}
可以对要查询的语句内容先分词,根据分词结果进行查询
通过QueryParser也可以创建Query,QueryParser提供一个Parse方法,此方法可以直接根据查询语法来查询。Query对象执行的查询语法可通过System.out.println(query);查询。
需要使用到分析器。建议创建索引时使用的分析器和查询索引时使用的分析器要一致。
需要加入queryParser依赖的jar包:lucene-queryparser-7.4.0.jar
/**
* 执行QueryPaser对象查询
*/
@Test
public void testQueryParser() throws Exception{
//创建一个QueryPaser对象 两个参数
//参数1:默认搜索域,2.分析器对象
QueryParser queryParser = new QueryParser("name",new IKAnalyzer());
//使用QueryPaser对象创建一个Query对象
Query query = queryParser.parse("Lucene是apache软件基金会4 jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包");
//执行查询
printResult(query);
}
private void printResult(Query query) throws Exception{
TopDocs topDocs = indexSearcher.search(query,10);
System.out.println("命中数据:"+topDocs.totalHits);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (ScoreDoc doc : scoreDocs){
//取文档id
int docId = doc.doc;
//根据id取文档对象
Document document = indexSearcher.doc(docId);
System.out.println(document.get("name"));
System.out.println(document.get("filePath"));
System.out.println(document.get("size"));
System.out.println(document.get("content"));
System.out.println("--------------------------");
}
indexReader.close();
}