Lucene的介绍与使用

为什么要学习Lucene?

原来的方式实现搜索功能,我们的搜索流程如下图:

如果用户比较少而且数据库的数据量比较小,那么这种方式实现搜索功能在企业中是比较常见的。但是数据量过多时,数据库的压力就会变得很大,查询速度会变得非常慢。我们需要使用更好的解决方案来分担数据库的压力。现在的方案(使用Lucene),如下图

为了解决数据库压力和速度的问题,我们的数据库就变成了索引库,我们使用Lucene的API来操作服务器上的索引库。这样完全和数据库进行了隔离。

 数据查询方法:

        1.顺序扫描法

所谓顺序扫描,例如要找内容包含一个字符串的文件,就是一个文档一个文档的看,对于每一个文档,从头看到尾,如果此文档包含此字符串,则此文档为我们要找的文件,接着看下一个文件,直到扫描完所有的文件。

这种方法是顺序扫描方法,数据量大就搜索慢。

        2.倒排索引

例如我们使用新华字典查询汉字,新华字典有偏旁部首的目录(索引),我们查字首先查这个目录,找到这个目录中对应的偏旁部首,就可以通过这个目录中的偏旁部首找到这个字所在的位置(文档)。

 

 

Lucene的概述

什么是lucene?

Lucene是apache软件基金会4 jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,但它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎(英文与德文两种西方语言)。

Lucene提供了一个简单却强大的应用程式接口,能够做全文索引和搜寻,  在Java开发环境里Lucene是一个成熟的免费开放源代码工具

 Lucene并不是现成的搜索引擎产品,但可以用来制作搜索引擎产品

Lucene和搜索引擎不同,Lucene是一套用java或其它语言写的全文检索的工具包,为应用程序提供了很多个api接口去调用,可以简单理解为是一套实现全文检索的类库,搜索引擎是一个全文检索系统,它是一个单独运行的软件系统

官网地址:http://lucene.apache.org/

什么是全文索引(全文检索)

计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。

 

Lucene版本下载

目前官网最新的版本是8.x系列,现在大部分公司还是用4.x比较多,本人用的也是4.x版本的。

老版本下载地址:http://archive.apache.org/dist/lucene/java/

 Lucene、Solr、Elasticsearch关系

Lucene:底层的API,工具包

Solr:基于Lucene开发的企业级的搜索引擎产品

Elasticsearch:基于Lucene开发的企业级的搜索引擎产品

Lucene的入门:

开发者使用Lucene的API就是实现对索引的增(创建索引)、删(删除索引)、改(修改索引)、查(搜索数据)。

创建索引的流程,如下图(在网上一篇博客中找到的,在此谢谢各位大神的总结):

 

本人用的是lucene4.10.3版本进行学习演示的,大家可以在上面链接进行自由下载选择:

本项目是基于maven来进行jar的管理:

<!-- lucene的核心库 -->
  	<dependency>
		<groupId>org.apache.lucene</groupId>
		<artifactId>lucene-core</artifactId>
		<version>4.10.3</version>
	</dependency>
	<!-- lucene的查询解析器 -->
	<dependency>
	<groupId>org.apache.lucene</groupId>
		<artifactId>lucene-queryparser</artifactId>
		<version>4.10.3</version>
	</dependency>
	<!-- lucene的默认分词-->
	<dependency>
		<groupId>org.apache.lucene</groupId>
		<artifactId>lucene-analyzers-common</artifactId>
		<version>4.10.3</version>
	</dependency>
	<!-- lucene的高亮-->
	<dependency>
        <groupId>org.apache.lucene</groupId>
        <artifactId>lucene-highlighter</artifactId>
        <version>4.10.3</version>
    </dependency>
    <dependency>
      	<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<version>4.12</version>
    </dependency>

索引实现步骤:

1 创建文档对象
2 创建存储目录
3 创建分词器
4 创建索引写入器的配置对象
5 创建索引写入器对象
6 将文档交给索引写入器
7 提交
8 关闭
 

案例:把指定文件夹中的文件名称-路径索引到索引库中,并查看添加时耗时多久

在E:\\lucene\\file文件夹下有7个文件,我们利用lucene进行索引:

public class IndexTest01 {
	// 创建写索引实例
	private IndexWriter writer; 
	
	/**
	 * 构造方法 实例化IndexWriter
	 * @param indexDir
	 * @throws Exception
	 */
	public IndexTest01(String indexDir)throws Exception{
		//索引目录类  指定索引在硬盘中的位置
		Directory dir=FSDirectory.open(new File(indexDir));
		// 创建分词器对象      标准分词器
		Analyzer analyzer=new StandardAnalyzer();
		//索引写出工具的配置对象
		IndexWriterConfig iwc=new IndexWriterConfig(Version.LATEST,analyzer);
		//创建索引的写出工具类
		writer=new IndexWriter(dir, iwc);
	}
	
	/**
	 * 关闭写索引
	 * @throws Exception
	 */
	public void close()throws Exception{
		//关闭
		writer.close();
	}
	
	/**
	 * 索引指定目录的所有文件
	 * @param dataDir
	 * @throws Exception
	 */
	public int index(String dataDir)throws Exception{
		//列出指定文件夹中所有的文件
		File []files=new File(dataDir).listFiles();
		for(File f:files){
			indexFile(f);
		}
		//返回多少个文件
		return writer.numDocs();
	}

	/**
	 * 索引指定文件
	 * @param f
	 */
	private void indexFile(File f) throws Exception{
		System.out.println("索引文件:"+f.getCanonicalPath());
		//得到每一个文档对象
		Document doc=getDocument(f);
		//吧文档交给indexWriter   
		writer.addDocument(doc);
		writer.commit();//提交
	}

	/**
	 * 获取文档,文档里再设置每个字段
	 * @param f
	 */
	private Document getDocument(File f)throws Exception {
		// 创建文档对象
		Document doc=new Document();
		//这里用TextField,即创建索引又会被分词。StringField会创建索引,但是不会被分词
		//创建并添加字段信息    参数   字段名称   字段的值    是否存储Field.Store.YES    存储    no  不存储   
		doc.add(new TextField("contents",new FileReader(f)));
		doc.add(new TextField("fileName", f.getName(),Field.Store.YES));
		doc.add(new TextField("fullPath",f.getCanonicalPath(),Field.Store.YES));
		return doc;
	}
	
	public static void main(String[] args) {
		String indexDir="E:\\lucene";
		String dataDir="E:\\lucene\\file";
		IndexTest01 indexer=null;
		int numIndexed=0;
		//开始时间
		long start=System.currentTimeMillis();
		try {
			//new对象
			indexer = new IndexTest01(indexDir);
			//调用index方法
			numIndexed=indexer.index(dataDir);
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			try {
				indexer.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		//结束时间
		long end=System.currentTimeMillis();
		System.out.println("索引:"+numIndexed+" 个文件 花费了"+(end-start)+" 毫秒");
	}
}

运行结果:

生成的索引文件:

 

上面案例我们演示了添加索引的功能,那么既然有添加,肯定就有搜索了,接下来,我们接着上个案例演示搜索功能;

public class SearchTest {
	public static void search(String indexDir,String q)throws Exception{
		Directory dir=FSDirectory.open(new File(indexDir));
		IndexReader reader=DirectoryReader.open(dir);
		//索引搜索工具
		IndexSearcher is=new IndexSearcher(reader);
		// 标准分词器
		Analyzer analyzer=new StandardAnalyzer(); 
		//创建查询解析器   默认要查询的字段的名称    分词器
		QueryParser parser=new QueryParser("contents", analyzer);
		//创建查询对象
		Query query=parser.parse(q);
		//开始计时
		long start=System.currentTimeMillis();
		//// 搜索数据,两个参数:查询条件对象要查询的最大结果条数
        // 返回的结果是 按照匹配度排名得分前N名的文档信息(包含查询到的总条数信息、所有符合条件的文档的编号信息)。
		TopDocs hits=is.search(query, 10);
		//计时结束
		long end=System.currentTimeMillis();
		System.out.println("匹配 "+q+" ,总共花费"+(end-start)+"毫秒"+"查询到"+hits.totalHits+"个记录");
		
		 // 获取得分文档对象(ScoreDoc)数组.SocreDoc中包含:文档的编号、文档的得分
		ScoreDoc[] docs = hits.scoreDocs;
		for(ScoreDoc scoreDoc:docs){
			//取得文档编号
			int docId = scoreDoc.doc;
			//根据编号去找文档
			Document doc=is.doc(docId);
			//根据字段查找指定的内容
			System.out.println("符合提交的路径:"+doc.get("fullPath"));
			System.out.println("得分:"+scoreDoc.score);
		}
		reader.close();
	}
	
	public static void main(String[] args) {
		String indexDir="E:\\lucene";
		String q="Zygmunt Saloni";
		try {
			search(indexDir,q);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

结果:

以上就算是简单的lucene入门了,小白一枚,如果以上有什么不足,请多多指教!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章