全文检索之Lucene入门

在学习Lucene时,我们先提出两个问题:

  1. 在数据库中我们是如何根据关键词查询到我们需要的数据?
  2. 在文档中如word,网页等文件中如何根据关键词查询到我们需要的数据?

1.分析问题

  • 问题1中,我们是通过select * from table where column like '%关键词%'就可以查询出我们需要的内容。像数据库存储的数据,有固定类型或者有固定长度的数据,为结构化数据。
  • 问题2中,我们通常是通过ctrl + f 来搜索即可。像这样没有固定类型和固定长度的数据,为非结构化数据。
    • ctrl + f 的查询叫顺序扫描法:即拿到搜索的关键字,去文档中,逐字匹配,直到找到和关键字一致的内容为止。
      • 优点: 如果文档中存在要找的关键字就一定能找到想要的内容
      • 缺点: 慢, 效率低

2.思路

如何解决问题2中效率低的问题?可以类比我们很熟悉的新华字典,我们根据目录,根据字母排序,我们可以快速找到我们需要的字。我们需要类似字典一样的目录来找到文章的内容。如何做呢?

2.1 全文检索算法(倒排索引算法)

将文件中的内容提取出来, 将文字拆封成一个一个的词(分词), 将这些词组成索引(字典中的目录), 搜索的时候先搜索索引,通过索引找文档,这个过程就叫做全文检索.

分词: 去掉停用词(a, an, the ,的, 地, 得, 啊, 嗯 ,呵呵),因为搜索的时候搜索这些词没有意义,将句子拆分成词,去掉标点符号和空格

  • 优点: 搜索速度快
  • 缺点: 因为创建的索引需要占用磁盘空间,所以这个算法会使用掉更多的磁盘空间,这是用空间换时间
    那么如何实现分词呢?实现索引呢?实现全文检索呢?

3.Lucene

百度搜索:Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。Lucene是一套用于全文检索和搜寻的开源程式库,由Apache软件基金会支持和提供。Lucene提供了一个简单却强大的应用程式接口,能够做全文索引和搜寻。

4.Lucene应用领域

  1. 互联网全文检索引擎(比如百度, 谷歌, 必应)
  2. 站内全文检索引擎(淘宝, 京东搜索功能)
  3. 优化数据库查询(因为数据库中使用like关键字是全表扫描也就是顺序扫描算法,查询慢)

5.Lucene结构

由索引和document组成,每个document包含多个域,每个域key,value键值形式。通过对内容进行分词,确定实际业务建立索引。每个索引可以对应到document,可以根据索引查询到对应的document。
在这里插入图片描述

6.Lucene的一些名词
  • 索引:域名:词 这样的形式,它里面有指针执行这个词来源的文档
  • 索引库: 放索引的文件夹(这个文件夹可以自己随意创建,在里面放索引就是索引库)
  • Term词元: 就是一个词, 是lucene中词的最小单位
  • 文档:Document对象,一个Document中可以有多个Field域对象,Field域对象中是key value键值对的形式:有域名和域值,
    一个document就是数据库表中的一条记录, 一个Filed域对象就是数据库表中的一行一列,这是一个通用的存储结构.
    注意:创建索引和所有时所用的分词器必须一致

6.域的详细介绍

6.1 分词、索引、存储

我们知道,一篇文章有很多内容,有些内容,我们分词后就没有意义了,有些词我们不会去进行搜索,有些内容我们不需要马上就要看到详细内容。那么怎么确定我们是否需要对内容进行分词、索引、存储呢?
分词:

  • 是否分词:分词的作用是为了索引
  • 不需要分词: 不需要索引的域不需要分词,还有就是分词后无意义的域不需要分词。比如: id, 身份证号

索引:

  • 是否索引:索引的的目的是为了搜索,需要搜索的域就一定要创建索引,只有创建了索引才能被搜索出来,不需要搜索的域可以不创建索引
  • 需要索引: 文件名称, 文件内容, id, 身份证号等
  • 不需要索引: 比如图片地址不需要创建索引, e:\xxx.jpg,因为根据图片地址搜索无意义

存储:

  • 是否存储:存储的目的是为了显示,是否存储看个人需要,存储就是将内容放入Document文档对象中保存出来,会额外占用磁盘空间, 如果搜索的时候需要马上显示出来可以放入document中也就是要存储,这样查询显示速度快, 如果不是马上立刻需要显示出来,则不需要存储,因为额外占用磁盘空间不划算。
6.2 域的各种类型

在创建域时,通过以下类型来设置域的分词、索引以及存储
在这里插入图片描述

6.3 文档
  1. 更新:更新就是按照传入的Term进行搜索,如果找到结果那么删除,将更新的内容重新生成一个Document对象。如果没有搜索到结果,那么将更新的内容直接添加一个新的Document对象。
  2. 删除:可以根据某个域的内容进行删除,还可以一次删除所有
  3. 搜索:
  • TermQuery:根据词进行搜索(只能从文本中进行搜索)
  • QueryParser:根据域名进行搜索,可以设置默认搜索域,推荐使用. (只能从文本中进行搜索)
  • NumericRangeQuery:从数值范围进行搜索
  • BooleanQuery:组合查询,可以设置组合条件,not and or.从多个域中进行查询
    • must相当于and关键字,是并且的意思
    • should,相当于or关键字或者的意思
    • must_not相当于not关键字, 非的意思
    • 注意:单独使用must_not 或者 独自使用must_not没有任何意义
  • MatchAllDocsQuery:查询出所有文档
  • MultiFieldQueryParser:可以从多个域中进行查询,只有这些域中有关键词的存在就查询出来.

7.解决问题

  • 新建Java工程
  • 引入jar包:
    • commons-io-2.4.jar
    • IKAnalyzer2012FF_u1.jar(中文解析器)
    • junit-4.9.jar
    • lucene-analyzers-common-4.10.3.jar
    • lucene-core-4.10.3.jar
    • lucene-queryparser-4.10.3.jar
  • 中文解析器需要三个配置文件:
    • ext.dic (手动配置一些不需要拆分的词作为一个词),如:编程思想作为一个词
    • IKAnalyzer.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">  
<properties>  
	<comment>IK Analyzer 扩展配置</comment>
	<!--用户可以在这里配置自己的扩展字典 -->
	<entry key="ext_dict">ext.dic;</entry> 
	
	<!--用户可以在这里配置自己的扩展停止词字典-->
	<entry key="ext_stopwords">stopword.dic;</entry> 
	
</properties>
  • stopword.dic(配置忽略的词,没有意义的词如的、地、得等)
7.1 创建索引
public class IndexManagerTest {
	@Test
	public void testIndexCreate() throws Exception{
		//创建文档列表,保存多个document
		List<Document> docList = new ArrayList<>();
		
		//指定文件所在目录
		File dir = new File("searchsource");
		for (File file : dir.listFiles()) {
			//文件名称
			String fileName = file.getName();
			//文件内容
			String fileContext = FileUtils.readFileToString(file);
			//文件大小
			Long fileSize = FileUtils.sizeOf(file);
			
			//文档对象,文件系统中的一个文件就是一个document对象
			Document doc = new Document();
			/**
			 * 第一个参数:域名
			 * 第二个参数:域值
			 * 第三个参数:是否存储,是为yes,不存储:no
			 */
			/**
			 * 是否分词:要,因为它要索引,并且它不是一个整体,分词有意义
			 * 是否索引:要,因为要通过它来进行搜索
			 * 是否存储:要,因为要直接在页面上显示
			 */
			TextField nameFile = new TextField("fileName", fileName, Store.YES);
			
			/**
			 * 是否分词:要,因为要根据内容进行搜索,分词有意义
			 * 是否索引:要,因为要通过它来进行搜索
			 * 是否存储:可以要或者不要
			 */
			TextField contextFile = new TextField("fileContext", fileContext, Store.NO);
			
			/**
			 * 是否分词:要,因为数字要对比,搜索文档的时候可以搜大小,
			 * 是否索引:要,因为要通过它来进行搜索
			 * 是否存储:要,因为要显示文档大小
			 */
			LongField sizeFile = new LongField("fileSize", fileSize, Store.YES);

			//将所有的域都存入文档中
			doc.add(nameFile);
			doc.add(contextFile);
			doc.add(sizeFile);
			
			//将文档存入文档集合中
			docList.add(doc);
		}
		
		//创建分词器,StandardAnalyzer标准分词器,标准分词器对英文分词效果很好,对中文时单字分词
//		Analyzer analyzer = new StandardAnalyzer();
		
		Analyzer analyzer = new IKAnalyzer();
		
		//指定索引和文档存储的目录
		Directory directory = FSDirectory.open(new File("E:\\dic"));
		IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer);
		//创建索引和文档写对象
		IndexWriter indexWriter = new IndexWriter(directory, config);
		
		//将文档加入到索引和文档的写对象中
		for (Document doc : docList) {
			indexWriter.addDocument(doc);
		}
		
		//提交
		indexWriter.commit();
		//关闭流
		indexWriter.close();
	}
}
7.2 查询
7.2.1 TermQuery查询
/**
	 * 查询,词元
	 */
	@Test
	public void testIndexTermQuery() throws Exception{
		Analyzer analyzer = new IKAnalyzer();
		//创建词元:就是词
		Term term = new Term("fileName","apache");
		//使用termQuery查询,根据term对象进行查询
		TermQuery termQuery = new TermQuery(term);
		
		Directory dir = FSDirectory.open(new File("E:\\dic"));
		//索引和文档的读取对象
		IndexReader indexReader = IndexReader.open(dir);
		//创建索引的搜索对象
		IndexSearcher indexSearcher = new IndexSearcher(indexReader);
		//搜索:第一个参数:查询语句对象	第二个参数:指定显示多少条数据
		TopDocs topDocs = indexSearcher.search(termQuery, 5);
		//一共搜索到多少条记录
		System.out.println("==========count======"+ topDocs.totalHits);
	}
7.2.2 QueryParser查询
@Test
	public void testIndexSearch() throws Exception{
		//创建分词器(创建索引和搜索的分词器必须一致)
//		Analyzer analyzer = new StandardAnalyzer();
		
		Analyzer analyzer = new IKAnalyzer();
		
		//创建查询对象,第一个参数:默认搜索域,第二个参数:分词器
		//默认搜索域作用:如果搜索语法中指定域名从指定域中搜索,如果搜索时只写了查询关键字,则从默认搜索域中搜索
		QueryParser queryParser = new QueryParser("fileContext", analyzer);
		//查询语法=域名:搜索的关键字
		Query query = queryParser.parse("fileName:create");
		
		Directory dir = FSDirectory.open(new File("E:\\dic"));
		//索引和文档的读取对象
		IndexReader indexReader = IndexReader.open(dir);
		//创建索引的搜索对象
		IndexSearcher indexSearcher = new IndexSearcher(indexReader);
		//搜索:第一个参数:查询语句对象	第二个参数:指定显示多少条数据
		TopDocs topDocs = indexSearcher.search(query, 5);
		//一共搜索到多少条记录
		System.out.println("==========count======"+ topDocs.totalHits);
		//从搜索结果对象中获取结果集
		ScoreDoc[] scoreDocs = topDocs.scoreDocs;
		for (ScoreDoc scoreDoc : scoreDocs) {
			//获取docid
			int docID = scoreDoc.doc;
			//通过文档ID从磁盘中读取出对应的文档
			Document document = indexReader.document(docID);
			//get域名可以取出值,打印
			System.out.println("fileName:" + document.get("fileName"));
			System.out.println("fileSize:" + document.get("fileSize"));
			System.out.println("============================");
		}
	}
7.2.3 NumericRangeQuery查询
/**
	 *根据数字范围查询
	 */
	@Test
	public void testIndexNumericRangeQuery() throws Exception{
		Analyzer analyzer = new IKAnalyzer();
		//根据数字范围查询
		/**
		 * 查询文件大小,大于100,小于1000的文章
		 * 第一个参数:域名
		 * 第二个参数:最小值
		 * 第三个参数:最大值
		 * 第四个参数:是否包含最小值
		 * 第五个参数:是否包含最大值
		 */
		Query query = NumericRangeQuery.newLongRange("fileSize", 100L, 1000L, true, true);
		Directory dir = FSDirectory.open(new File("E:\\dic"));
		//索引和文档的读取对象
		IndexReader indexReader = IndexReader.open(dir);
		//创建索引的搜索对象
		IndexSearcher indexSearcher = new IndexSearcher(indexReader);
		//搜索:第一个参数:查询语句对象	第二个参数:指定显示多少条数据
		TopDocs topDocs = indexSearcher.search(query, 5);
		//一共搜索到多少条记录
		System.out.println("==========count======"+ topDocs.totalHits);
	}
7.2.4 BooleanQuery查询
/**
	 *多条件查询
	 *布尔查询,就是可以根据多个条件组合进行查询
	 *查询:文件名称包含apache的,并且文件大小大于等于100小于等于1000字节的文章
	 */
	@Test
	public void testIndexBooleranQuery() throws Exception{
		Analyzer analyzer = new IKAnalyzer();
		//布尔查询,就是可以根据多个条件组合进行查询
		BooleanQuery query = new BooleanQuery();
		//根据数字范围查询
		Query numericquery = NumericRangeQuery.newLongRange("fileSize", 100L, 1000L, true, true);
		
		Term term = new Term("fileName","apache");
		//使用termQuery查询,根据term对象进行查询
		TermQuery termQuery = new TermQuery(term);
		/**
		 * Occur是逻辑关系
		 * MUST:相当于and关键字,是并且的意思
		 * MUST_NOT:相当于not关键字
		 * SHOULD:相当于or关键字,或者 
		 * 注意:单独使用MUST_NOT没有意义
		 */
		query.add(termQuery,Occur.MUST);
		query.add(numericquery,Occur.MUST);
		Directory dir = FSDirectory.open(new File("E:\\dic"));
		//索引和文档的读取对象
		IndexReader indexReader = IndexReader.open(dir);
		//创建索引的搜索对象
		IndexSearcher indexSearcher = new IndexSearcher(indexReader);
		//搜索:第一个参数:查询语句对象	第二个参数:指定显示多少条数据
		TopDocs topDocs = indexSearcher.search(query, 5);
		//一共搜索到多少条记录
		System.out.println("==========count======"+ topDocs.totalHits);
	}
7.2.5 MatchAllDocsQuery查询
/**
	 * 查询所有文档
	 */
	@Test
	public void testIndexMathAllQuery() throws Exception{
		Analyzer analyzer = new IKAnalyzer();
		MatchAllDocsQuery query = new MatchAllDocsQuery();
		Directory dir = FSDirectory.open(new File("E:\\dic"));
		//索引和文档的读取对象
		IndexReader indexReader = IndexReader.open(dir);
		//创建索引的搜索对象
		IndexSearcher indexSearcher = new IndexSearcher(indexReader);
		//搜索:第一个参数:查询语句对象	第二个参数:指定显示多少条数据
		TopDocs topDocs = indexSearcher.search(query, 5);
		//一共搜索到多少条记录
		System.out.println("==========count======"+ topDocs.totalHits);
	}
	
7.2.6 MultiFieldQueryParser查询
/**
	 * 多个域搜索
	 * 从文件名称和文件内容中查询,只含有apache的就查出来
	 */
	@Test
	public void testIndexMultiFielQuery() throws Exception{
		Analyzer analyzer = new IKAnalyzer();
		String fields[] = {"fileName","fileContext"};
 		MultiFieldQueryParser multiQuery = new MultiFieldQueryParser(fields, analyzer);
 		Query query = multiQuery.parse("apache");
		
		Directory dir = FSDirectory.open(new File("E:\\dic"));
		//索引和文档的读取对象
		IndexReader indexReader = IndexReader.open(dir);
		//创建索引的搜索对象
		IndexSearcher indexSearcher = new IndexSearcher(indexReader);
		//搜索:第一个参数:查询语句对象	第二个参数:指定显示多少条数据
		TopDocs topDocs = indexSearcher.search(query, 5);
		//一共搜索到多少条记录
		System.out.println("==========count======"+ topDocs.totalHits);
	}
7.3 删除
/**
	 * 删除
	 * @throws Exception
	 */
	@Test
	public void testIndexDel() throws Exception{
		//创建分词器,StandardAnalyzer标准分词器,标准分词器对英文分词效果很好,对中文时单字分词
//		Analyzer analyzer = new StandardAnalyzer();
		
		Analyzer analyzer = new IKAnalyzer();
		
		//指定索引和文档存储的目录
		Directory directory = FSDirectory.open(new File("E:\\dic"));
		IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer);
		//创建索引和文档写对象
		IndexWriter indexWriter = new IndexWriter(directory, config);
		
		//删除所有
//		indexWriter.deleteAll();
		
		//根据名称进行删除	term词元,就是一个词,第一个参数:域名,第二个参数:要删除含有此关键词的数据
		indexWriter.deleteDocuments(new Term("fileName","apache"));
		//提交
		indexWriter.commit();
		//关闭
		indexWriter.close();
	}
7.4 修改
@Test
	public void testIndexUpdate() throws Exception{
		Analyzer analyzer = new IKAnalyzer();
		//指定索引和文档存储的目录
		Directory directory = FSDirectory.open(new File("E:\\dic"));
		IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer);
		//创建索引和文档写对象
		IndexWriter indexWriter = new IndexWriter(directory, config);
			
		Term term = new Term("fileName","create");
		//更新对象
		Document doc = new Document();
		doc.add(new TextField("fileName", "xxxx",Store.YES));
		doc.add(new TextField("fileContext", "think in java xxxx", Store.NO));
		doc.add(new LongField("fileSize", 100L, Store.YES));
		//更新
		indexWriter.updateDocument(term, doc);

		//提交
		indexWriter.commit();
		//关闭
		indexWriter.close();
	}

8.可视化工具lukeall

下载后,将文件解压即可。
cmd运行:
java -jar lukeall-4.10.3.jar
运行后出现以下界面,选择代码中dic的目录:
在这里插入图片描述

在这里插入图片描述
查看document
在这里插入图片描述
程序下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值