Lucene与Solr学习总结一
记录:花了几天时间断断续续的学习了lucene与solr的基础知识,首先说明学习的视频比较旧了,现在的lucene和solr的版本已经到了8.0.0版本,和自己学的4.10版本有很大变化,自己也下载了最新版本和老版本比较,还是跟着视频学了老版本的一些,相信懂了基础知识也会对以后的学习有所帮助。再结合在网上所查的知识与自己在配置操作过程中遇到的问题,借此半天记录整理一波。
使用到的资料都将在下篇文章中。
Lucene基础
数据分为:结构化数据与非结构化数据
结构化数据:有固定的格式或有限长度的数据
非结构化数据:与之相反
非结构化数据的查询一般有:顺序扫描 与 全文检索
顺序扫描:如查找多个文件中含有某个字符串的文件,便从头到尾一个一个的遍历查找,需要遍历所有的文件。如果文件较大则效率低下。
全文检索:将非结构化的数据中的一部分提取出来,重新对它组织,使得其有一定的结构,再对他进行检索,从而可以依据结构对应所要查找的数据,不需要查找所有文件,效率较高。
从非结构化中提取出来并重新组织的数据称为索引。 如字典中的 拼音表或部首表相当于字典的索引。
先建立索引,再根据索引经行搜索的过程就称为全文检索。需要说明的是也许创建索引的过程比较繁琐,但一旦索引创建完毕后,之后对数据的查询等操作将会十分便利。
那么lucene的出现便是为了在系统中方便的经行全文检索而开发的开源项目。应用场景为大数据量,结构不固定的数据。
Lucene实现全文检索的流程
- 获得原始文档
原始文档即需要经行全文检索的原始文件 - 创建文档对象
获得原始文件后需要对其分析从而得到索引,在此之前则需要将原始文件转换为一个文档对象document。document存放在索引库index中,索引库便是存放索引和文档对象的地方。那么一个原始文件是如何转换为一个document对象的呢?
首先一个document对象中有多个域field,每个域有个name和name对应的value,注意每个document可以有多个field,不同的document可以有不同的field,同一个document也可以有相同的field(即域名与域值相同)。
例如一个a.txt文件, 为一个document,可以设置其有多个域,分别为文件名称域,文件大小域,文件路径域和文件内容域,再将这些与add到document对象便完成了将原始文件转换为一个document对象的过程。之后一个个的document便添加到索引库中。 - 分析文档
当document添加都索引库之后,便需要对document中的每个域的内容经行分析,那么又如何分析的呢?
首先需要清楚的是此时document中的每个域的内容是你原始文件对应的各个信息,需要将这些内容经行关键词的提取。
例如一个域的域名为"content",对应的value为"我是中国人,我为之自豪。“此时就需要用到分析器,所谓分析器即是将一段内容分析成一个个的关键词的分析工具,lucene有默认的分析器,中文支持较好的为IK Analyzer 2012。这里先提一下,之后的配置与使用还会具体讲到。
分析后便得到一个个的词汇 如:我 中国人 自傲。此时域名"content” 加上一个个的词汇便产生了一个个的语汇单元term。
content:我 、content:中国人、 content:自豪
注意:不同的域即使对应相同的值也是不同的term。即域名不同则一定是不同的term。 - 创建索引
语汇单元term创建完成后,便是一个个的索引。
注意: 多个document时,他们的域名可能是相同的(一般情况是相同的),他们中的内容分析时也便会产生相同的term。则一个索引(term)应该对应着多个document,如我们需要查询索引"content:中国人",即域名为content,域值经过分析含有"中国人"的document便全部查询出来了。这种通过词汇找文档,便是倒排索引结构。通过较小量的索引,查找较大量的document,效率便提升了很多。 - 查询索引
如输入关键词查询内容
首先需要根据自己的需求开发一个搜索界面。lucene不提供制作用户界面的功能。
当用户输入一段关键词后,后台根据关键词找到对应的搜索索引,索引再找到对应的document,用户根据需求提取document中的内容。
具体的步骤
第一步,创建查询对象
根据用户输入的关键词内容创建一个查询对象,对象中包含了域名和域值。如"name:zhangsan"表示要搜索的域名为name,域值为zhangsan。
第二步,执行查询
根据查询对象找到对应的索引,从而找到索引对应的document的id集合,之前说过一个索引是对应多个document的,是通过一个索引对应多个document的id。
第三步,渲染结果
得到了对应的document后便可以提取其中的所需内容,可以对内容经行高亮的显示渲染。
Lucene实操
我所用的jdk 8,lucene 4.10
导包
lucene-core 核心包
lucene-analyzers-common 分析器包
lucene-queryparser 查询解析包
IKAnalyzer2012FF_u1 中文分析器包
commons-io io流
junit 测试
API
下面要写一些api了,本人其实是比较不太喜欢记特意这些东西的,因为比较懒,用到的时候能查到就行了,但想想这样不行滴,开始吧。从哪开始呢…
创建索引
首先,需要创建索引库,向库中写入数据。之前讲过索引库包含索引和文档,
1.指定索引库的位置Directory dir = FSDirectory.open(new File("H:\\notes\\LuceneSolr\\IndexPool"));
2.既然需要写入数据,则因为创建索引时需要分析docuemnt中的内容,必须需要有分析器IKAnalyzer ikAnalyzer = new IKAnalyzer();
此处用到了ik分词器,可以有效分析中文,需要在src下创建IKAnalyzer.cfg.xml配置文件、stopword.dic文件(分析时去掉不需要保留的关键字)和ext.dic文件(分析时指定的一些关键词)。此处便显示出了ik分析器的优点,不仅可以有效分析中文,还可以自行扩展词汇。
注意1.stopword.dic文件和ext.dic文件 需要为UTF-8无BOM格式,可在notepad++下设置。2.在经行索引(索引的创建和修改)和搜索时,分词器应为同一个种类。
分词器的种类有很多,具体想了解可查询。
3.创建IndexWriter索引库操作类,对索引库中的文件进行操作,增删改。创建IndexWriter时需先创建其配置类IndexWriterConfig。IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LATEST, ikAnalyzer);
IndexWriter indexWriter = new IndexWriter(dir, indexWriterConfig);//指定索引库和相关配置信息
4.提取方法getIndexWriter
public IndexWriter getIndexWriter() throws IOException {
Directory dir = FSDirectory.open(new File("H:\\notes\\LuceneSolr\\IndexPool"));
IKAnalyzer ikAnalyzer = new IKAnalyzer();
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LATEST, ikAnalyzer);
IndexWriter indexWriter = new IndexWriter(dir, indexWriterConfig);
return indexWriter;
}
5.创建源文件,将源文件转为document对象,写入索引库,同时在创建document对象时,同时创建了索引。
源文件转为document对象需要有不同的域,先创建域对象,根据域将源文件的各个内容存储进去。再将域添加到document。
了解一些lucene中的field对象:
首先域对象 有三个属性:
是否分析,是否对域中的内容经行分词处理,一般我们需要对域中的内容进行查询时都需要分析。
是否做索引,是否将分析后的词做索引;或者将不需要分析的内容做索引,如订单号。
是否存储,将域中的内容存储到document中,将要从document中取出的内容都应存储。
Field类 | 数据类型 | 是否分析 | 是否做索引 | 是否存储 | 说明 |
---|---|---|---|---|---|
TextField | 字符串或流 | Y | Y | Y/N | 如果是流,lucene会认为内容过多,采用Unstored |
LongField | Long | Y | Y | Y/N | 价格一般使用 |
StringField | 字符串 | N | Y | Y/N | 订单号、姓名 |
StoredField | 支持多种类型 | N | N | Y/N | 只做存储 |
注:新版lucene中已经没有了LongField类,使用LongPoint代替。还有DoublePoint与IntPoint
@Test
public void Create() throws IOException {
IndexWriter indexWriter = getIndexWriter();
File file = new File("H:\\notes\\LuceneSolr\\case\\searchsource");//源文件路径
Document doc = null;
File[] listFiles = file.listFiles();
for (File file2 : listFiles) {
doc = new Document();
doc.add(new TextField("name", file2.getName(), Store.YES));
doc.add(new LongField("size", FileUtils.sizeOf(file2), Store.YES));
doc.add(new StoredField("path", file2.getPath()));
doc.add(new TextField("content", FileUtils.readFileToString(file2), Store.YES));
indexWriter.addDocument(doc);
}
indexWriter.close();
}
执行Junit后在索引库中便产生了库文件。我们可以使Luke工具经行查看索引库。工具在资料中,直接打开luke/start.bat便可查看。
删除索引
删除所有
注:删除所有后无法恢复,谨慎使用
@Test
public void delAll() throws IOException {
IndexWriter indexWriter = getIndexWriter();
indexWriter.deleteAll();
indexWriter.close();
}
根据查询条件删除,删除索引"filename:apache"对应的所有document
@Test
public void deleteIndexByQuery() throws Exception {
IndexWriter indexWriter = getIndexWriter();
//创建一个查询条件
Query query = new TermQuery(new Term("filename", "apache"));
//根据查询条件删除
indexWriter.deleteDocuments(query);
//关闭indexwriter
indexWriter.close();
}
更新索引
即先删除再添加
// 修改索引库document
// 将含有域名为docAdd1 域值为1 的document(索引term name为docAdd1 value为1 指向的document)
// 更新为自定义的新的document
@Test
public void updateIndex() throws Exception {
IndexWriter indexWriter = getIndexWriter();
// 创建一个Document对象
Document document = new Document();
// 向document对象中添加域。
// 不同的document可以有不同的域,同一个document可以有相同的域。
document.add(new TextField("filename", "要更新的文档", Store.YES));
document.add(new TextField("content", " Lucene 是一个基于 Java 的全文信息检索工具包,它不是一个完整的搜索应用程序,而是为你的应用程序提供索引和搜索功能。", Store.YES));
indexWriter.updateDocument(new Term("docAdd1", "1"), document);
// 关闭indexWriter
indexWriter.close();
}
查询索引
创建查询对象,IndexReader 和 IndexSearcher
public IndexReader getIndexReader() throws IOException {
Directory dir = FSDirectory.open(new File("H:\\notes\\LuceneSolr\\IndexPool"));
IndexReader indexReader = DirectoryReader.open(dir);
return indexReader;
}
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
IndexSearcher的查询方法:
方法 | 说明 |
---|---|
indexSearcher.search(query, n) | 根据Query搜索,返回评分最高的n条记录 |
indexSearcher.search(query, filter, n) | 根据Query搜索,添加过滤策略,返回评分最高的n条记录 |
indexSearcher.search(query, n, sort) | 根据Query搜索,添加排序策略,返回评分最高的n条记录 |
indexSearcher.search(booleanQuery, filter, n, sort) | 根据Query搜索,添加过滤策略,添加排序策略,返回评分最高的n条记录 |
- 根据Query子类查询
Query是一个抽象类,lucene提供了很多查询对象,比如TermQuery项精确查询,NumericRangeQuery数字范围查询等。
注:新版本提供的查询对象有所变化。
/**
* 根据TermQuery 精准查询
*/
@Test
public void query() throws IOException {
IndexReader indexReader = getIndexReader();
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
TermQuery termQuery = new TermQuery(new Term("name", "web"));
TopDocs topDocs = indexSearcher.search(termQuery, 5);
printResult(topDocs, indexSearcher);
indexReader.close();
}
Lucene搜索结果可通过TopDocs遍历,TopDocs类提供了少量的属性。
Search方法需要指定匹配记录数量n:indexSearcher.search(query, n)
TopDocs.totalHits:是匹配索引库中所有记录的数量
TopDocs.scoreDocs:匹配相关度高的前边记录数组,scoreDocs的长度小于等于search方法指定的参数n
// 打印查询结果
public void printResult(TopDocs docs, IndexSearcher indexSearcher) throws IOException {
ScoreDoc[] scoreDocs = docs.scoreDocs;
Document doc = null;
for (ScoreDoc scoreDoc : scoreDocs) {
int i = scoreDoc.doc;//获得匹配的document的id
doc = indexSearcher.doc(i);//根据id获得document
//获得document中对应域的值
System.out.println("name为" + doc.get("name"));
System.out.println("path为" + doc.get("path"));
System.out.println("size为" + doc.get("size"));
System.out.println("content为" + doc.get("content"));
System.out.println("------");
}
}
// 查询所有 MatchAllDocsQuery
@Test
public void queryAll() throws IOException {
IndexReader indexReader = getIndexReader();
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
MatchAllDocsQuery matchAllDocsQuery = new MatchAllDocsQuery();
TopDocs topDocs = indexSearcher.search(matchAllDocsQuery, 5);
printResult(topDocs, indexSearcher);
indexReader.close();
}
// 可以组合查询条件
@Test
public void testBooleanQuery() throws IOException {
IndexReader indexReader = getIndexReader();
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
BooleanQuery booleanQuery = new BooleanQuery();
TermQuery termQuery1 = new TermQuery(new Term("name", "lucene"));
TermQuery termQuery2 = new TermQuery(new Term("content", "java"));
booleanQuery.add(termQuery1, Occur.MUST);
booleanQuery.add(termQuery2, Occur.MUST);
TopDocs topDocs = indexSearcher.search(booleanQuery, 1);
printResult(topDocs, indexSearcher);
indexReader.close();
}
Occur.MUST:必须满足此条件,相当于and
Occur.SHOULD:应该满足,但是不满足也可以,相当于or
Occur.MUST_NOT:必须不满足。相当于not
// 查询范围 NumericRangeQuery
@Test
public void queryNumRange() throws IOException {
IndexReader indexReader = getIndexReader();
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
NumericRangeQuery<Long> longRange = NumericRangeQuery.newLongRange("size", 1l, 1000l, true, true);
TopDocs topDocs = indexSearcher.search(longRange, 10);
printResult(topDocs, indexSearcher);
indexReader.close();
}
- 使用QueryParse解析查询表达式
QueryParse会将用户输入的查询表达式解析成Query对象实例。
通过QueryParser也可以创建Query,QueryParser提供一个Parse方法,此方法可以直接根据查询语法来查询。Query对象执行的查询语法可通过System.out.println(query);查询。
需要使用到分析器。建议创建索引时使用的分析器和查询索引时使用的分析器要一致。
@Test
public void testQueryParse() throws IOException, ParseException {
IndexReader indexReader = getIndexReader();
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
QueryParser queryParser = new QueryParser("content", new IKAnalyzer());
Query query = queryParser.parse("Java 8");
TopDocs topDocs = indexSearcher.search(query, 1);
printResult(topDocs, indexSearcher);
indexReader.close();
}
// 可以指定多个默认搜索域
@Test
public void testMultiFiledQueryParser() throws Exception {
IndexReader indexReader = getIndexReader();
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
// 可以指定默认搜索的域是多个
String[] fields = { "name", "content" };
// 创建一个MulitFiledQueryParser对象
MultiFieldQueryParser queryParser = new MultiFieldQueryParser(fields, new IKAnalyzer());
Query query = queryParser.parse("java AND apache");
System.out.println(query);
TopDocs topDocs = indexSearcher.search(query, 2);
// 执行查询
printResult(topDocs, indexSearcher);
indexReader.close();
}
QueryParse查询语法
1、基础的查询语法,关键词查询:
域名+“:”+搜索的关键字
例如:content:java
2、范围查询
域名+“:”+[最小值 TO 最大值]
例如:size:[1 TO 1000]
范围查询在lucene中支持数值类型,不支持字符串类型。在solr中支持字符串类型。
3、组合条件查询
1)+条件1 +条件2:两个条件之间是并且的关系and
例如:+filename:apache +content:apache
2)+条件1 条件2:必须满足第一个条件,应该满足第二个条件
例如:+filename:apache content:apache
3)条件1 条件2:两个条件满足其一即可。
例如:filename:apache content:apache
4)-条件1 条件2:必须不满足条件1,要满足条件2
例如:-filename:apache content:apache
官网地址:http://lucene.apache.org/
流:大概先写这么多,明天写solr基础。很多地方觉得需要画图来更好的表达,以后继续学习lucene的时候补上。