Lucene与Solr学习总结一

记录:花了几天时间断断续续的学习了lucene与solr的基础知识,首先说明学习的视频比较旧了,现在的lucene和solr的版本已经到了8.0.0版本,和自己学的4.10版本有很大变化,自己也下载了最新版本和老版本比较,还是跟着视频学了老版本的一些,相信懂了基础知识也会对以后的学习有所帮助。再结合在网上所查的知识与自己在配置操作过程中遇到的问题,借此半天记录整理一波。
使用到的资料都将在下篇文章中。

Lucene基础

数据分为:结构化数据与非结构化数据
结构化数据:有固定的格式或有限长度的数据
非结构化数据:与之相反

非结构化数据的查询一般有:顺序扫描 与 全文检索

顺序扫描:如查找多个文件中含有某个字符串的文件,便从头到尾一个一个的遍历查找,需要遍历所有的文件。如果文件较大则效率低下。

全文检索:将非结构化的数据中的一部分提取出来,重新对它组织,使得其有一定的结构,再对他进行检索,从而可以依据结构对应所要查找的数据,不需要查找所有文件,效率较高。

从非结构化中提取出来并重新组织的数据称为索引。 如字典中的 拼音表或部首表相当于字典的索引。

先建立索引,再根据索引经行搜索的过程就称为全文检索。需要说明的是也许创建索引的过程比较繁琐,但一旦索引创建完毕后,之后对数据的查询等操作将会十分便利。

那么lucene的出现便是为了在系统中方便的经行全文检索而开发的开源项目。应用场景为大数据量,结构不固定的数据。

Lucene实现全文检索的流程

  1. 获得原始文档
    原始文档即需要经行全文检索的原始文件
  2. 创建文档对象
    获得原始文件后需要对其分析从而得到索引,在此之前则需要将原始文件转换为一个文档对象document。document存放在索引库index中,索引库便是存放索引和文档对象的地方。那么一个原始文件是如何转换为一个document对象的呢?
    首先一个document对象中有多个域field,每个域有个name和name对应的value,注意每个document可以有多个field,不同的document可以有不同的field,同一个document也可以有相同的field(即域名与域值相同)。
    例如一个a.txt文件, 为一个document,可以设置其有多个域,分别为文件名称域,文件大小域,文件路径域和文件内容域,再将这些与add到document对象便完成了将原始文件转换为一个document对象的过程。之后一个个的document便添加到索引库中。
  3. 分析文档
    当document添加都索引库之后,便需要对document中的每个域的内容经行分析,那么又如何分析的呢?
    首先需要清楚的是此时document中的每个域的内容是你原始文件对应的各个信息,需要将这些内容经行关键词的提取。
    例如一个域的域名为"content",对应的value为"我是中国人,我为之自豪。“此时就需要用到分析器,所谓分析器即是将一段内容分析成一个个的关键词的分析工具,lucene有默认的分析器,中文支持较好的为IK Analyzer 2012。这里先提一下,之后的配置与使用还会具体讲到。
    分析后便得到一个个的词汇 如:我 中国人 自傲。此时域名"content” 加上一个个的词汇便产生了一个个的语汇单元term。
    content:我 、content:中国人、 content:自豪
    注意:不同的域即使对应相同的值也是不同的term。即域名不同则一定是不同的term。
  4. 创建索引
    语汇单元term创建完成后,便是一个个的索引。
    注意: 多个document时,他们的域名可能是相同的(一般情况是相同的),他们中的内容分析时也便会产生相同的term。则一个索引(term)应该对应着多个document,如我们需要查询索引"content:中国人",即域名为content,域值经过分析含有"中国人"的document便全部查询出来了。这种通过词汇找文档,便是倒排索引结构。通过较小量的索引,查找较大量的document,效率便提升了很多。
  5. 查询索引
    Lucene与Solr学习总结一
    如输入关键词查询内容
    首先需要根据自己的需求开发一个搜索界面。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条记录
  1. 根据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();
	}
  1. 使用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的时候补上。