为什么要学习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入门了,小白一枚,如果以上有什么不足,请多多指教!