Lucene全文检索
1.什么是全文检索
日常中我们所接触到的数据,可以大概的分为:
结构化数据:指有固定长度、格式的数据。如:数据库数据,元数据等
非结构化数据:没有固定长度、格式的数据。如:word文档、txt文件等。
结构化数据我们可以通过sql语句等进行查询,可以很方便的查询到我们所需要的数据,但是非结构化数据,我们要想从中获取我们想要的词汇或者数据时,就会很麻烦,他们没有规律可以查找。
要想从非结构化数据中查找数据,我们可以:
把非结构化数据按照一定的格式,提取出来,重新组织,形成结构化的数据,这样我们就可以如结构化数据一样,很容易查找到我们想要的数据,这部分从非结构化数据中提取出的然后重新组织的信息,我们称之索引。
例如:字典,每个字的解释都是非结构化的数据,查找起来就会很麻烦,我们可以先把每个字的读音或者偏旁提取出来,形成一个索引,每个都对应一个页码,这样我们在查找时,就可以先通过偏旁或者读音先查找到对应的页码,这样查找单词就可以很方便。
这种先建立索引,再对索引进行搜索的过程就叫全文检索(Full-text Search)。
全文搜索流程:
1、绿色表示索引过程,对要搜索的原始内容进行索引构建一个索引库,索引过程包括:
确定原始内容即要搜索的内容采集文档创建文档分析文档索引文档
2、红色表示搜索过程,从索引库中搜索内容,搜索过程包括:
用户通过搜索界面创建查询执行搜索,从索引库搜索渲染搜索结果
2.lucene中的一些概念
原始文件:
即我们要在那些数据中查找,这些数据即为原始数据。
如:数据库中的数据、磁盘中的txt文件…
文档对象:
获取到原始文件中的内容之后,我们需要创建索引,并把内容添加到document对象中,然后存放在索引库中,而document对象中包含很多的field对象。document对象可以看作数据库的表,而field可以看作数据库中的属性。
注意:每个Document可以有多个Field,不同的Document可以有不同的Field,同一个Document可以有相同的Field(域名和域值都相同)
每个文档都有一个唯一的编号,就是文档id。
3.lucene的自我理解
lucene其实就是读取原始数据,然后把每一个原始数据都封装为一个单独的document对象,添加直索引库时,会自动的根据分词器把每个field进行分词处理,形成一个词典,并对这个词典进行索引处理,然后再索引库中添加的既是索引(词典)和document对象,还有他们之间的联系关系(倒排索引结构)。当用户查找词汇时,会先查找索引(词典),然后根据词典和document对象的关系,查找到对应的document对象,再查找对应的field对象,就可以查出内容了。
倒排索引结构:
左边为词典列表,右边为倒排表,即为每个词存在于那些文档的文档id。也就是查询词,找到对应的文档,查找内容,即为倒排索引结构。
4.lucene的使用步骤
1.导入jar包。
2.创建原始文件
3.创建索引库,把上边的文件全部解析并记载进索引库中
@Test
public void createLucene() throws IOException {
//1.创建directory对象,指定索引库的存放位置
//此方式为在本地磁盘存储索引库
Directory dir = FSDirectory.open(new File("D:\\luncene").toPath());
//此方式为索引库存放于内存中(不建议:每次加载太慢)
//Directory dir1 = new RAMDirectory();
//2.配置设置:默认使用的是StandardAnalyzer解析器,对中文不友好,后期可以在构造参数中指定中文解析器
IndexWriterConfig config = new IndexWriterConfig();
//3.根据directory和indexWriterConfig创建IndexWriter对象,用于向索引库中写数据
IndexWriter writer = new IndexWriter(dir,config);
//4.加载数据,并封装为document对象
File filePaths = new File("D:\\lucenePro");
//获取文件夹下所有的file
File[] files = filePaths.listFiles();
//遍历files数组,获取每一个file
for (File file : files) {
//获得文件名
String fileName = file.getName();
//获取文件的路径
String filePath = file.getPath();
//获取文件的内容
String fileContent = FileUtils.readFileToString(file);
//获取文件的大小
long fileSize = FileUtils.sizeOf(file);
//创建域(属性)对象,并在域对象中存放对应的值
Field fieldName = new TextField("name",fileName, Field.Store.YES);
Field fieldPath = new TextField("path",filePath, Field.Store.YES);
Field fieldContent = new TextField("content",fileContent, Field.Store.YES);
Field fieldSize = new TextField("size",fileSize+"", Field.Store.YES);
//创建document对象,并把域对象赋值
Document document = new Document();
document.add(fieldName);
document.add(fieldPath);
document.add(fieldContent);
document.add(fieldSize);
//写入到索引库
writer.addDocument(document);
}
//释放写资源
writer.close();
}
注:如果使用了FileUtis,需要导入commons.io.jar包
执行完毕,找到创建Directory对象的地方的文件夹位置,即可看到:
就上传成功了。
5.使用luke查看索引库
luke下载地址:
luke下载地址
下载完毕必须要进入目录,在pom.xml文件所在目录下,打开黑窗口,运行命令:mvn package命令,才能使用luke,否则出现
ERROR,unable to access jarfile:.\target\luke-swings-with-deps.jar错误
另外:下载最新的lucene,会自带luke,直接双击就能用。
6.查询索引库
@Test
public void queryLucene() throws IOException {
//创建Directory对象,指向索引库
Directory dir = FSDirectory.open(new File("D:\\luncene").toPath());
//创建reader对象
IndexReader reader = DirectoryReader.open(dir);
//创建sercher查找对象
IndexSearcher searcher = new IndexSearcher(reader);
//封装查询条件对象 term:第一个参数:你要查询那个域的,第二个参数:你要查询的参数
Query query = new TermQuery(new Term("name","apache"));
//查询 第一个参数:你封装的查询条件,第二个参数:最多查询多少条的数据
TopDocs topDocs = searcher.search(query, 10);
//获取查询的总条数
TotalHits totalHits = topDocs.totalHits;
System.out.println(totalHits);
//根据获取的topDocs对象,获得ScoreDoc[],ScoreDoc里面存放的是document的id
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (ScoreDoc scoreDoc : scoreDocs) {
//根据document的id查询document对象
Document doc = searcher.doc(scoreDoc.doc);
//获取内容
String name = doc.get("name");
String path = doc.get("path");
String content = doc.get("content");
String size = doc.get("size");
System.out.println(name);
System.out.println(path);
System.out.println(content);
System.out.println(size);
System.out.println("-----------------------------------");
}
reader.close();
}
7.中文分词器
使用自带的标准分词器,它能够很好的把英文进行分词,但是中文就不太友好,会把每个字作为一个词来分。如果要分词中文,就需要使用中文分词器:IKAnalyzer
使用方法:
1.导入jar包,导入配置
配置:
hotword.dic:表示一些特殊词,在里面添加之后,即可被解析器分词
stopword.dic:停用词、敏感词列表
IkAnalyzer.cfg.xml:IkAnalyzer配置信息
使用案例:
/**
* 使用中文分析器分词
*/
@Test
public void createIndexByIk() throws IOException {
//创建directory对象
Directory dir = FSDirectory.open(new File("D:\\luncene").toPath());
//创建IndexWriterConfig对象,并指定分词器
IndexWriterConfig writerConfig = new IndexWriterConfig(new IKAnalyzer());
//创建IndexWriter对象
IndexWriter writer = new IndexWriter(dir,writerConfig);
//获取数据
String text = "但是老外写的分词器对中文分词一般都是单字分词,分词的效果不好。 国人林良益写的IK Analyzer应该是最好的Lucene中文分词器之一,而且随着Lucene的版本更新而不断更新";
Field field = new TextField("name",text, Field.Store.YES);
Document document = new Document();
document.add(field);
writer.addDocument(document);
writer.close();
}
效果:
如果需要对一些词进行特殊的分词,例如公司名称等,则需要单独在hotword里面进行维护。
8.索引库得维护-添加
同初始索引库相同,只不过这只是单独一个文件而已。
9.索引库删除索引
1.删除全部索引
@Test
public void deleteAllIndex() throws IOException {
IndexWriter writer = new IndexWriter(FSDirectory.open(new File("D:\\luncene").toPath()),new IndexWriterConfig(new IKAnalyzer()));
//删除所有的索引,此方法慎用,一旦删除,无法恢复!!!
writer.deleteAll();
writer.close();
}
2.根据Query删除索引
@Test
public void deleteByQuery() throws IOException {
IndexWriter writer = new IndexWriter(FSDirectory.open(new File("D:\\luncene").toPath()),new IndexWriterConfig(new IKAnalyzer()));
//声明Query
Query query = new TermQuery(new Term("name","web"));
//根据query条件删除document
writer.deleteDocuments(query);
writer.close();
}
10.更新索引库
在这里的更新其实就是先删除索引,在添加索引。
11.查看索引
1.Query:
TermQuery:关键词查询
不演示,同上。
RangeQuery:范围查询
@Test
public void selectQuery() throws IOException {
//Query有两个子类:
//1.TermQuery:关键词查询,需要提供查询的域和要查询的关键词,不演示
//2.RangeQuery:范围查询,一般用于整数
IndexReader indexReader = DirectoryReader.open(FSDirectory.open(new File("D:\\luncene").toPath()));
//获取查询对象
IndexSearcher searcher = new IndexSearcher(indexReader);
//获取查询条件LongPoint:这个要看你存储的时候,使用的是什么类型的,如果是int,就替换为intPoint
Query query = LongPoint.newRangeQuery("size",10l,100l);
TopDocs topDocs = searcher.search(query, 10);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (ScoreDoc scoreDoc : scoreDocs) {
System.out.println(searcher.doc(scoreDoc.doc).get("name"));
}
indexReader.close();
}
2.queryparser:先把查询条件分词,在根据分词结构查询
需要再导入一个jar包:
测试:
@Test
public void QueryParserTest() throws IOException, ParseException {
IndexReader indexReader = DirectoryReader.open(FSDirectory.open(new File("D:\\luncene").toPath()));
IndexSearcher searcher = new IndexSearcher(indexReader);
//创建QueryParser对象 参数1:默认的搜索域,当没有提供搜索域的时候,去这个域搜索,参数2:使用的分析器
QueryParser queryParser = new QueryParser("name",new IKAnalyzer());
//设置查询条件
Query query = queryParser.parse("Lucene是java开发的");
//查询
TopDocs topDocs = searcher.search(query, 10);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (ScoreDoc scoreDoc : scoreDocs) {
Document doc = searcher.doc(scoreDoc.doc);
System.out.println(doc.get("name"));
}
indexReader.close();
}
搜索结果就为:
先把你的查询条件分词,在根据分完词之后的每个词去查询,只要查询到一个相关的,就会查询出来。