Lucene
- Lucene是一套用于全文检索和搜寻的开源程序库,由Apache软件基金会支持和提供
- Lucene提供了一个简单却强大的应用程序接口(API),能够做全文索引和搜寻,在Java开发环境里Lucene是一个成熟的免费开放源代码工具
- Lucene并不是现成的搜索引擎产品,但可以用来制作搜索引擎产品
全文检索
全文检索大体分两个过程,索引创建(Indexing) 和搜索索引(Search) 。
- 索引创建:将现实世界中所有的结构化和非结构化数据提取信息,创建索引的过程。
- 搜索索引:就是得到用户的查询请求,搜索创建的索引,然后返回结果的过程
全文检索的应用场景
- 搜索引擎 - 百度、谷歌、搜索等等
- 站内搜索 - 论坛搜索,微博搜索,文章搜索
- 电商搜索 - jd,tb搜索
索引
索引:一个为了提高查询速度,创建某种数据结构的集合
1、数据的分类:
1、结构化数据:格式、长度、数据类型固定 例如数据库的数据, 2、非结构化数据:格式、长度、数据类型不固定 word文档、pdf文档等等
2、数据的查询
1、结构化数据的查询:sql语句
简单、查询速度快
2、非结构化数据:查询某个关键字
条件复杂,查询难度大
顺序扫描法
字符串匹配(顺序扫描)
使非结构化的数据变为结构化的数据便于查询
Lucene、Solr、Elasticsearch
- 公司中使用的搜索技术是solr和Elasticsearch,而在这里介绍lucene是因为其简单对于新手来说入门容易
- solr 分词索引的数据库(有服务器的概念) 分词索引,能够支撑大数据量的索引
- Elasticsearch分词索引的数据库(有服务器的概念) 分词索引,能够支撑更大数据量的索引 能够搭建ES集群
三者关系
Lucene:底层的API,工具包
Solr/Elasticsearch:基于Lucene开发的企业级的搜索引擎产品
Lucene的基本使用
使用Lucene的API来实现对索引的增(创建索引)、删(删除索引)、改(修改索引)、查(搜索数据)
lucene工作流程
构建索引
把数据库中的数据用IndexWriter(索引写出器类)生成Document对象,存到指定的文件夹或硬盘下,生成的文件就是Lucene文件
查询索引
先获取IndexSearcher对象,从索引文件夹快速检索出想要查询的数据===》查询索引
最终读到去TopDocs中
注:
- IndexSearcher 借助于Query对象和IndexReader(索引读取类)对象
- Query(查询对象,包含要查询的关键词信息) 借助于QueryParser查询解析器对象
- QueryParser查询解析对象又要借助于Analyzer解析器
- Analyzer解析器默认只能解析英文需要导入jar包才能解析中文
创建索引的详细流程
1、文档Document:数据库中一条具体的记录(在这里我使用的是读取本地的text文件,实际中使用的是数据库中查询出的数据添加到Document对象中)
** 2、字段Field**:数据库中的每个字段
一个Document中可以有很多个不同的字段,每一个字段都是一个Field类的对象。
- DoubleField、FloatField、IntField、LongField、StringField这些子类一定会被创建索引,但是不会被分词,而且不一定会被存储到文档列表。要通过构造函数中的参数Store来指定:如果Store.YES代表存储,Store.NO代表不存储
- TextField即创建索引,又会被分词。StringField会创建索引,但是不会被分词,如果不分词,会造成整个字段作为一个词条,除非用户完全匹配,否则搜索不到
3、目录对象Directory:指定索引要存储的位置
索引存储方式有两种
1、FSDirectory:文件系统目录,会把索引库指向本地磁盘。
2、RAMDirectory:内存目录,会把索引库保存在内存
区别:
**FSDirectory: **速度略慢,但是比较安全
RAMDirectory:速度快,但是不安全
4、Analyzer(分词器类):提供分词算法,可以把文档中的数据按照算法分词
在这里使用的是StandardAnalyzer分词器,默认英文分词,不可以中文分词,需要引入中文分词依赖,才可以进行中文分词,一般我们用IK分词器
案例演示
在以下案例演示中,确保索引文件在测试之前删掉上一次生成的索引文件
主要代码:
public static void main(String[] args) {
// 索引文件将要存放的位置
String indexDir = "D:\\software\\mysql\\data";
// 数据源地址
String dataDir = "D:\\software\\mysql\\data\\d";
IndexCreate ic = null;
try {
ic = new IndexCreate(indexDir);
long start = System.currentTimeMillis();
int num = ic.index(dataDir);
long end = System.currentTimeMillis();
System.out.println("检索指定路径下"+num+"个文件,一共花费了"+(end-start)+"毫秒");
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
ic.closeIndexWriter();
} catch (Exception e) {
e.printStackTrace();
}
}
}
封装方法:
/**
* 1、构造方法 实例化IndexWriter
* @param indexDir
* @throws Exception
*/
public IndexCreate(String indexDir) throws Exception{
// 获取索引文件的存放地址对象
FSDirectory dir = FSDirectory.open(Paths.get(indexDir));
// 标准分词器(针对英文)
Analyzer analyzer = new StandardAnalyzer();
// 索引输出流配置对象
IndexWriterConfig conf = new IndexWriterConfig(analyzer);
indexWriter = new IndexWriter(dir, conf);
}
/**
* 2、关闭索引输出流
* @throws Exception
*/
public void closeIndexWriter() throws Exception{
indexWriter.close();
}
/**
* 3、索引指定路径下的所有文件
* @param dataDir
* @return
* @throws Exception
*/
public int index(String dataDir) throws Exception{
File[] files = new File(dataDir).listFiles();
//检索文件夹中源文件,并已生成Document对象存储到索引文件中
for (File file : files) {
indexFile(file);
}
return indexWriter.numDocs();
}
/**
* 4、索引指定的文件
* @param file
* @throws Exception
*/
private void indexFile(File file) throws Exception{
System.out.println("被索引文件的全路径:"+file.getCanonicalPath());
Document doc = getDocument(file);
indexWriter.addDocument(doc);
}
/**
* 5、获取文档(索引文件中包含的重要信息,key-value的形式)
* @param file
* @return
* @throws Exception
*/
private Document getDocument(File file) throws Exception{
Document doc = new Document();
doc.add(new TextField("contents", new FileReader(file)));
// Field. Store.YES是否存储到硬盘
doc.add(new TextField("fullPath", file.getCanonicalPath(),Field.Store.YES));
doc.add(new TextField("fileName", file.getName(),Field.Store.YES));
return doc;
}
生成索引:
元数据文件:(注:此案例使用以下文件,实际中使用的是数据库查询的数据)
索引文件:
使用工具查看索引文件 luke-5.3.0-luke-release
中文分词索引
以上是默认的英文分词,也只能英文分词,不支持中文分词。实际中我们不可能只对英文分词,这样我们需要引入第三方的分词器。
在这里我使用的是SmartChineseAnalyzer 中文分词器(org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer.SmartChineseAnalyzer())
private Integer ids[] = { 1, 2, 3 };
private String citys[] = { "青岛", "南京", "上海" };
// private String descs[]={
// "青岛是个美丽的城市。",
// "南京是个有文化的城市。",
// "上海市个繁华的城市。"
// };
private String descs[] = { "青岛是个美丽的城市。",
"南京是一个文化的城市南京,简称宁,是江苏省会,地处中国东部地区,长江下游,濒江近海。全市下辖11个区,总面积6597平方公里,2013年建成区面积752.83平方公里,常住人口818.78万,其中城镇人口659.1万人。[1-4] “江南佳丽地,金陵帝王州”,南京拥有着6000多年文明史、近2600年建城史和近500年的建都史,是中国四大古都之一,有“六朝古都”、“十朝都会”之称,是中华文明的重要发祥地,历史上曾数次庇佑华夏之正朔,长期是中国南方的政治、经济、文化中心,拥有厚重的文化底蕴和丰富的历史遗存。[5-7] 南京是国家重要的科教中心,自古以来就是一座崇文重教的城市,有“天下文枢”、“东南第一学”的美誉。截至2013年,南京有高等院校75所,其中211高校8所,仅次于北京上海;国家重点实验室25所、国家重点学科169个、两院院士83人,均居中国第三。[8-10]",
"上海市个繁华的城市。" };
private FSDirectory dir;
/**
* 每次都生成索引文件
*
* @throws Exception
*/
@Before
public void setUp() throws Exception {
dir = FSDirectory.open(Paths.get("D:\\software\\mysql\\data"));
IndexWriter indexWriter = getIndexWriter();
for (int i = 0; i < ids.length; i++) {
Document doc = new Document();
doc.add(new IntField("id", ids[i], Field.Store.YES));
doc.add(new StringField("city", citys[i], Field.Store.YES));
doc.add(new TextField("desc", descs[i], Field.Store.YES));
indexWriter.addDocument(doc);
}
indexWriter.close();
}
/**
* 获取索引输出流
*
* @return
* @throws Exception
*/
private IndexWriter getIndexWriter() throws Exception {
// Analyzer analyzer = new StandardAnalyzer();
Analyzer analyzer = new SmartChineseAnalyzer();
IndexWriterConfig conf = new IndexWriterConfig(analyzer);
return new IndexWriter(dir, conf);
}
/**
* luke查看索引生成
*
* @throws Exception
*/
@Test
public void testIndexCreate() throws Exception {
}
/**
* 测试高亮
*
* @throws Exception
*/
@Test
public void testHeight() throws Exception {
IndexReader reader = DirectoryReader.open(dir);
IndexSearcher searcher = new IndexSearcher(reader);
SmartChineseAnalyzer analyzer = new SmartChineseAnalyzer();
QueryParser parser = new QueryParser("desc", analyzer);
// Query query = parser.parse("南京文化");
Query query = parser.parse("南京文明");
TopDocs hits = searcher.search(query, 100);
// 查询得分项
QueryScorer queryScorer = new QueryScorer(query);
// 得分项对应的内容片段
SimpleSpanFragmenter fragmenter = new SimpleSpanFragmenter(queryScorer);
// 高亮显示的样式
SimpleHTMLFormatter htmlFormatter = new SimpleHTMLFormatter("<span color='red'><b>", "</b></span>");
// 高亮显示对象
Highlighter highlighter = new Highlighter(htmlFormatter, queryScorer);
// 设置需要高亮显示对应的内容片段
highlighter.setTextFragmenter(fragmenter);
for (ScoreDoc scoreDoc : hits.scoreDocs) {
Document doc = searcher.doc(scoreDoc.doc);
String desc = doc.get("desc");
if (desc != null) {
// tokenstream是从doucment的域(field)中抽取的一个个分词而组成的一个数据流,用于分词。
TokenStream tokenStream = analyzer.tokenStream("desc", new StringReader(desc));
System.out.println("高亮显示的片段:" + highlighter.getBestFragment(tokenStream, desc));
}
System.out.println("所有内容:" + desc);
}
}
未使用之前
** 使用之后**
这就是中文分词器的作用,不仅仅只对中文分词,还可以进行英文分词
高亮显示
在中文分词下,有出现了一个高亮显示的一个概念。
高亮显示就是给所有关键字加上一个HTML标签
上图,搜索java入门基础知识 ,把查询出来的所有数据中的java入门设置了一个样式,这就是高亮显示
查询结果:
高亮显示的片段:城镇人口659.1万人。[1-4] “江南佳丽地,金陵帝王州”,<span color='red'><b>南京</b></span>拥有着6000多年<span color='red'><b>文明</b></span>史、近2600年建城史和近500年的建都史,是中国四大古都之一,有“六朝古都”、“十朝都会”之称,是中华<span color='red'><b>文明</b></span>的
所有内容:南京是一个文化的城市南京,简称宁,是江苏省会,地处中国东部地区,长江下游,濒江近海。全市下辖11个区,总面积6597平方公里,2013年建成区面积752.83平方公里,常住人口818.78万,其中城镇人口659.1万人。[1-4] “江南佳丽地,金陵帝王州”,南京拥有着6000多年文明史、近2600年建城史和近500年的建都史,是中国四大古都之一,有“六朝古都”、“十朝都会”之称,是中华文明的重要发祥地,历史上曾数次庇佑华夏之正朔,长期是中国南方的政治、经济、文化中心,拥有厚重的文化底蕴和丰富的历史遗存。[5-7] 南京是国家重要的科教中心,自古以来就是一座崇文重教的城市,有“天下文枢”、“东南第一学”的美誉。截至2013年,南京有高等院校75所,其中211高校8所,仅次于北京上海;国家重点实验室25所、国家重点学科169个、两院院士83人,均居中国第三。[8-10]
文档域加权
概述:
但是在这里大家有没有发现我查询的数据,前几条都是广告,那是为什么这些广告会排在前面让我们第一眼就看到这些广告呢?
大家肯定说是因为广告投的钱多,所以排在前面。
在这里,说的是没错,但是在lucene中,把这种技术叫做文档域加权
案例演示
private String ids[]={"1","2","3","4"};
private String authors[]={"Jack","Marry","John","Json"};
private String positions[]={"accounting","technician","salesperson","boss"};
private String titles[]={"Java is a good language.","Java is a cross platform language","Java powerful","You should learn java"};
private String contents[]={
"If possible, use the same JRE major version at both index and search time.",
"When upgrading to a different JRE major version, consider re-indexing. ",
"Different JRE major versions may implement different versions of Unicode,",
"For example: with Java 1.4, `LetterTokenizer` will split around the character U+02C6,"
};
private Directory dir;//索引文件目录
@Before
public void setUp()throws Exception {
dir = FSDirectory.open(Paths.get("D:\\software\\mysql\\data"));
IndexWriter writer = getIndexWriter();
for (int i = 0; i < authors.length; i++) {
Document doc = new Document();
doc.add(new StringField("id", ids[i], Field.Store.YES));
doc.add(new StringField("author", authors[i], Field.Store.YES));
doc.add(new StringField("position", positions[i], Field.Store.YES));
TextField textField = new TextField("title", titles[i], Field.Store.YES);
// Json投钱做广告,把排名刷到第一了
if("boss".equals(positions[i])) {
textField.setBoost(2f);//设置权重,默认为1
}
doc.add(textField);
// TextField会分词,StringField不会分词
doc.add(new TextField("content", contents[i], Field.Store.NO));
writer.addDocument(doc);
}
writer.close();
}
private IndexWriter getIndexWriter() throws Exception{
Analyzer analyzer = new StandardAnalyzer();
IndexWriterConfig conf = new IndexWriterConfig(analyzer);
return new IndexWriter(dir, conf);
}
@Test
public void index() throws Exception{
IndexReader reader = DirectoryReader.open(dir);
IndexSearcher searcher = new IndexSearcher(reader);
String fieldName = "title";
String keyWord = "java";
Term t = new Term(fieldName, keyWord);
Query query = new TermQuery(t);
TopDocs hits = searcher.search(query, 10);
System.out.println("关键字:‘"+keyWord+"’命中了"+hits.totalHits+"次");
for (ScoreDoc scoreDoc : hits.scoreDocs) {
Document doc = searcher.doc(scoreDoc.doc);
System.out.println(doc.get("author"));
}
}
if("boss".equals(positions[i])) {
//加权的代码 textField.setBoost(2f);//设置权重,默认为1
}
在我们未设置加权时,数据排行是怎么排的呢?
lucene会根据关键字出现的顺序以及次数进行排序
代码中数据前两条title里java是开头的,而最后一个是排在最后的
在未给第四条数据
加权前
加权后
当多条数据都进行加权,这时候比较的是加权的权重
查询索引的详细流程
步骤:
- 创建一个Directory对象,也就是索引库存放的位置。
- 创建一个indexReader对象,需要指定Directory对象。
- 创建一个indexsearcher对象,需要指定IndexReader对象
- 创建一个TermQuery对象,指定查询的域和查询的关键词。
- 执行查询。
- 返回查询结果。遍历查询结果并输出。
- 关闭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条记录
Lucene搜索结果可通过TopDocs遍历,TopDocs类提供了少量的属性,如下:
- totalHits : 匹配搜索条件的总记录数
- scoreDocs : 顶部匹配记录
**注: **
- Search方法需要指定匹配记录数量n:indexSearcher.search(query, n)
- TopDocs.totalHits:是匹配索引库中所有记录的数量
- TopDocs.scoreDocs:匹配相关度高的前边记录数组,scoreDocs的长度小于等于search方法指定的参数n
案例演示:
这里的索引文件是基于上面创建索引的数据进行查询的
执行代码:
public static void main(String[] args) {
String indexDir = "D:\\software\\mysql\\data";
String q = "EarlyTerminating-Collector";
try {
IndexUse.search(indexDir, q);
} catch (Exception e) {
e.printStackTrace();
}
}
主要代码:
/**
* 通过关键字在索引目录中查询
* @param indexDir 索引文件所在目录
* @param q 关键字
*/
public static void search(String indexDir, String q) throws Exception{
//索引文件存储地址
FSDirectory indexDirectory = FSDirectory.open(Paths.get(indexDir));
// 注意:索引输入流不是new出来的,是通过目录读取工具类打开的
IndexReader indexReader = DirectoryReader.open(indexDirectory);
// 获取索引搜索对象
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
//分词器
Analyzer analyzer = new StandardAnalyzer();
//查询解析器对象 带有查询的关键词信息
QueryParser queryParser = new QueryParser("contents", analyzer);
// 获取符合关键字的查询对象
Query query = queryParser.parse(q);
long start=System.currentTimeMillis();
// 获取关键字出现的前十次
TopDocs topDocs = indexSearcher.search(query , 10);
long end=System.currentTimeMillis();
System.out.println("匹配 "+q+" ,总共花费"+(end-start)+"毫秒"+"查询到"+topDocs.totalHits+"个记录");
for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
int docID = scoreDoc.doc;
// 索引搜索对象通过文档下标获取文档
Document doc = indexSearcher.doc(docID);
System.out.println("通过索引文件:"+doc.get("fullPath")+"拿数据");
}
indexReader.close();
}
查询到这两个文件中包含EarlyTerminating或Collector单词
索引的删除修改
案例演示
private String ids[]={"1","2","3"};
private String citys[]={"qingdao","nanjing","shanghai"};
private String descs[]={
"Qingdao is a beautiful city.",
"Nanjing is a city of culture.",
"Shanghai is a bustling city."
};
private FSDirectory dir;
/**
* 每次都生成索引文件
* @throws Exception
*/
@Before
public void setUp() throws Exception {
dir = FSDirectory.open(Paths.get("D:\\software\\mysql\\data"));
IndexWriter indexWriter = getIndexWriter();
for (int i = 0; i < ids.length; i++) {
Document doc = new Document();
doc.add(new StringField("id", ids[i], Field.Store.YES));
doc.add(new StringField("city", citys[i], Field.Store.YES));
doc.add(new TextField("desc", descs[i], Field.Store.NO));
indexWriter.addDocument(doc);
}
indexWriter.close();
}
/**
* 获取索引输出流
* @return
* @throws Exception
*/
private IndexWriter getIndexWriter() throws Exception{
Analyzer analyzer = new StandardAnalyzer();
IndexWriterConfig conf = new IndexWriterConfig(analyzer);
return new IndexWriter(dir, conf );
}
/**
* 测试写了几个索引文件
* @throws Exception
*/
@Test
public void getWriteDocNum() throws Exception {
IndexWriter indexWriter = getIndexWriter();
System.out.println("索引目录下生成"+indexWriter.numDocs()+"个索引文件");
}
/**
* 打上标记,该索引实际并未删除
* @throws Exception
*/
@Test
public void deleteDocBeforeMerge() throws Exception {
IndexWriter indexWriter = getIndexWriter();
System.out.println("最大文档数:"+indexWriter.maxDoc());
indexWriter.deleteDocuments(new Term("id", "1"));
indexWriter.commit();
System.out.println("最大文档数:"+indexWriter.maxDoc());
System.out.println("实际文档数:"+indexWriter.numDocs());
indexWriter.close();
}
/**
* 对应索引文件已经删除,但是该版本的分词会保留
* @throws Exception
*/
@Test
public void deleteDocAfterMerge() throws Exception {
// https://blog.csdn.net/asdfsadfasdfsa/article/details/78820030
// org.apache.lucene.store.LockObtainFailedException: Lock held by this virtual machine:indexWriter是单例的、线程安全的,不允许打开多个。
IndexWriter indexWriter = getIndexWriter();
System.out.println("最大文档数:"+indexWriter.maxDoc());
indexWriter.deleteDocuments(new Term("id", "1"));
indexWriter.forceMergeDeletes(); //强制删除
indexWriter.commit();
System.out.println("最大文档数:"+indexWriter.maxDoc());
System.out.println("实际文档数:"+indexWriter.numDocs());
indexWriter.close();
}
/**
* 测试更新索引
* @throws Exception
*/
@Test
public void testUpdate()throws Exception{
IndexWriter writer=getIndexWriter();
Document doc=new Document();
doc.add(new StringField("id", "1", Field.Store.YES));
doc.add(new StringField("city","qingdao",Field.Store.YES));
doc.add(new TextField("desc", "dsss is a city.", Field.Store.NO));
writer.updateDocument(new Term("id","1"), doc);
writer.close();
}
删除索引
deleteDocBeforeMerge 此方法只标记未删除索引
deleteDocAfterMerge
修改索引
修改之前
** 修改之后**
** 这里就出现了dsss数据,代表修改成功**
高级查询
特定项查询
@Before
public void setUp() {
// 索引文件将要存放的位置
String indexDir = "D:\\software\\mysql\\data";
// 数据源地址
String dataDir = "D:\\software\\mysql\\data\\d";
IndexCreate ic = null;
try {
ic = new IndexCreate(indexDir);
long start = System.currentTimeMillis();
int num = ic.index(dataDir);
long end = System.currentTimeMillis();
System.out.println("检索指定路径下" + num + "个文件,一共花费了" + (end - start) + "毫秒");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
ic.closeIndexWriter();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 特定项搜索
*/
@Test
public void testTermQuery() {
String indexDir = "D:\\software\\mysql\\data";
String fld = "contents";
String text = "indexformattoooldexception";
// 特定项片段名和关键字
Term t = new Term(fld , text);
TermQuery tq = new TermQuery(t);
try {
FSDirectory indexDirectory = FSDirectory.open(Paths.get(indexDir));
// 注意:索引输入流不是new出来的,是通过目录读取工具类打开的
IndexReader indexReader = DirectoryReader.open(indexDirectory);
// 获取索引搜索对象
IndexSearcher is = new IndexSearcher(indexReader);
TopDocs hits = is.search(tq, 100);
// System.out.println(hits.totalHits);
for(ScoreDoc scoreDoc: hits.scoreDocs) {
Document doc = is.doc(scoreDoc.doc);
System.out.println("文件"+doc.get("fullPath")+"中含有该关键字");
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 查询表达式(queryParser)
*/
@Test
public void testQueryParser() {
String indexDir = "D:\\software\\mysql\\data";
// 获取查询解析器(通过哪种分词器去解析哪种片段)
QueryParser queryParser = new QueryParser("contents", new StandardAnalyzer());
try {
FSDirectory indexDirectory = FSDirectory.open(Paths.get(indexDir));
// 注意:索引输入流不是new出来的,是通过目录读取工具类打开的
IndexReader indexReader = DirectoryReader.open(indexDirectory);
// 获取索引搜索对象
IndexSearcher is = new IndexSearcher(indexReader);
// 由解析器去解析对应的关键字
TopDocs hits = is.search(queryParser.parse("indexformattoooldexception") , 100);
for(ScoreDoc scoreDoc: hits.scoreDocs) {
Document doc = is.doc(scoreDoc.doc);
System.out.println("文件"+doc.get("fullPath")+"中含有该关键字");
}
} catch (IOException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
}
}
组合查询等
private int ids[]={1,2,3};
private String citys[]={"qingdao","nanjing","shanghai"};
private String descs[]={
"Qingdao is a beautiful city.",
"Nanjing is a city of culture.",
"Shanghai is a bustling city."
};
private FSDirectory dir;
/**
* 每次都生成索引文件
* @throws Exception
*/
@Before
public void setUp() throws Exception {
dir = FSDirectory.open(Paths.get("D:\\software\\mysql\\data"));
IndexWriter indexWriter = getIndexWriter();
for (int i = 0; i < ids.length; i++) {
Document doc = new Document();
doc.add(new IntField("id", ids[i], Field.Store.YES));
doc.add(new StringField("city", citys[i], Field.Store.YES));
doc.add(new TextField("desc", descs[i], Field.Store.YES));
indexWriter.addDocument(doc);
}
indexWriter.close();
}
/**
* 获取索引输出流
* @return
* @throws Exception
*/
private IndexWriter getIndexWriter() throws Exception{
Analyzer analyzer = new StandardAnalyzer();
IndexWriterConfig conf = new IndexWriterConfig(analyzer);
return new IndexWriter(dir, conf );
}
/**
* 指定数字范围查询
* @throws Exception
*/
@Test
public void testNumericRangeQuery()throws Exception{
IndexReader reader = DirectoryReader.open(dir);
IndexSearcher is = new IndexSearcher(reader);
NumericRangeQuery<Integer> query=NumericRangeQuery.newIntRange("id", 1, 2, true, true);
TopDocs hits=is.search(query, 10);
for(ScoreDoc scoreDoc:hits.scoreDocs){
Document doc=is.doc(scoreDoc.doc);
System.out.println(doc.get("id"));
System.out.println(doc.get("city"));
System.out.println(doc.get("desc"));
}
}
/**
* 指定字符串开头字母查询(prefixQuery)
* @throws Exception
*/
@Test
public void testPrefixQuery()throws Exception{
IndexReader reader = DirectoryReader.open(dir);
IndexSearcher is = new IndexSearcher(reader);
PrefixQuery query=new PrefixQuery(new Term("city","n"));
TopDocs hits=is.search(query, 10);
for(ScoreDoc scoreDoc:hits.scoreDocs){
Document doc=is.doc(scoreDoc.doc);
System.out.println(doc.get("id"));
System.out.println(doc.get("city"));
System.out.println(doc.get("desc"));
}
}
/**
* 组合查询 (范围查询和指定字符串开头字母查询)
* @throws Exception
*/
@Test
public void testBooleanQuery()throws Exception{
//获取IndexSearcher对象
IndexReader reader = DirectoryReader.open(dir);
IndexSearcher is = new IndexSearcher(reader);
//条件
NumericRangeQuery<Integer> query1=NumericRangeQuery.newIntRange("id", 1, 2, true, true);
PrefixQuery query2=new PrefixQuery(new Term("city","q"));
BooleanQuery.Builder booleanQuery=new BooleanQuery.Builder();
booleanQuery.add(query1,BooleanClause.Occur.MUST);
booleanQuery.add(query2,BooleanClause.Occur.MUST);
TopDocs hits=is.search(booleanQuery.build(), 10);
for(ScoreDoc scoreDoc:hits.scoreDocs){
Document doc=is.doc(scoreDoc.doc);
System.out.println(doc.get("id"));
System.out.println(doc.get("city"));
System.out.println(doc.get("desc"));
}
}
综合案例演示
查询
查询关键字为空则根据数据库进行查询,当关键字不为空,则根据关键字在建立的索引文件进行查询,但是在这查询需要有有分词的情况下进行查询,才能查询到。
例如: java入门
分词为: java - 入门
但是 当我们查询 公司有限责任
当我们查询限责就有可能会查询不到
删除
再次查询之后就没有了,这里的删除是需要把数据库中的数据和索引的对应的数据都进行删除
修改
添加
因为加权了,所以显示在最前面
在此我还特意写了一个索引的刷新,为了防止索引文件丢失之后还需要手动去维护
后端代码
private String title;
private BlogDao blogDao = new BlogDao();
private HttpServletRequest request = ServletActionContext.getRequest();
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@SuppressWarnings({ "deprecation", "rawtypes" })
public String refresh(){
IndexWriterConfig conf = new IndexWriterConfig(new SmartChineseAnalyzer());
Directory d;
IndexWriter indexWriter = null;
try {
d = FSDirectory.open(Paths.get(PropertiesUtil.getValue("indexPath")));
indexWriter = new IndexWriter(d , conf );
//删除之前的索引
indexWriter.deleteAll();
// 为数据库中的所有数据构建索引
List<Map<String, Object>> list = blogDao.list(null, null);
//重新构建索引
for (Map<String, Object> map : list) {
Document doc = new Document();
doc.add(new StringField("id", (String) map.get("id"), Field.Store.YES));
// TextField用于对一句话分词处理 java培训机构
TextField textField = new TextField("title", (String) map.get("title"), Field.Store.YES);
HttpSession session = request.getSession();
@SuppressWarnings("unchecked")
ArrayList<String> attribute = (ArrayList)session.getAttribute("list");
if(attribute!=null) {
for (String id : attribute) {
if(id.equals((String)map.get("id"))) {
textField.setBoost(2f);//加权
}
}
}
doc.add(textField);
doc.add(new StringField("url", (String) map.get("url"), Field.Store.YES));
indexWriter.addDocument(doc);
}
ServletActionContext.getResponse().getWriter().print("123");
} catch (IOException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally {
try {
if(indexWriter!= null) {
indexWriter.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
public String list() {
try {
if (StringUtils.isBlank(title)) {
List<Map<String, Object>> blogList = this.blogDao.list(title, null);
request.setAttribute("blogList", blogList);
}else {
Directory directory = LuceneUtil.getDirectory(PropertiesUtil.getValue("indexPath"));
DirectoryReader reader = LuceneUtil.getDirectoryReader(directory);
IndexSearcher searcher = LuceneUtil.getIndexSearcher(reader);
//中文分词器
SmartChineseAnalyzer analyzer = new SmartChineseAnalyzer();
// 拿一句话到索引目中的索引文件中的词库进行关键词碰撞
Query query = new QueryParser("title", analyzer).parse(title);
Highlighter highlighter = LuceneUtil.getHighlighter(query, "title");
TopDocs topDocs = searcher.search(query ,100);
//处理得分命中的文档
List<Map<String, Object>> blogList = new ArrayList<>();
Map<String, Object> map = null;
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (ScoreDoc scoreDoc : scoreDocs) {
map = new HashMap<>();
Document doc = searcher.doc(scoreDoc.doc);
map.put("id", doc.get("id"));
String titleHighlighter = doc.get("title");
if(StringUtils.isNotBlank(titleHighlighter)) {
titleHighlighter = highlighter.getBestFragment(analyzer, "title", titleHighlighter);
}
map.put("title", titleHighlighter);
map.put("url", doc.get("url"));
blogList.add(map);
}
request.setAttribute("blogList", blogList);
}
} catch (Exception e) {
e.printStackTrace();
}
return "blogList";
}
//新增索引
public String insertBk() {
try {
HttpSession session = request.getSession();
//获取参数
Map<String, String[]> map = request.getParameterMap();
String parameter = request.getParameter("Boost");
//数据库添加
int save = blogDao.save(map);
if(save>0) {
//索引建立
IndexWriterConfig conf = new IndexWriterConfig(new SmartChineseAnalyzer());
Directory d=FSDirectory.open(Paths.get(PropertiesUtil.getValue("indexPath")));
IndexWriter indexWriter = new IndexWriter(d ,conf);
Document doc = new Document();
String id = request.getParameter("id");
System.out.println("索引ID:"+id);
doc.add(new StringField("id", id, Field.Store.YES));
doc.add(new StringField("url", request.getParameter("url"), Field.Store.NO));
TextField textField = new TextField("title",request.getParameter("title"), Field.Store.YES);
//加权判断 得添加到数据库中,在这里使用的是session版
if("on".equals(parameter)) {
ArrayList<String> list = (ArrayList)session.getAttribute("list");
if(list==null) {
list=new ArrayList<>();
System.out.println("加权ID:"+id);
}
list.add(id);
session.setAttribute("list", list);
list.forEach(e->{
System.out.println("遍历ID:"+e);
});
textField.setBoost(2f);//设置权重,默认为1
}
doc.add(textField);
indexWriter.addDocument(doc);
indexWriter.close();
}
} catch (Exception e) {
e.printStackTrace();
}
return "edit";
}
public String delete() {
try {
//判断数据库删除是否成功
if(blogDao.delete(request.getParameterMap())>0) {
IndexWriterConfig conf = new IndexWriterConfig(new SmartChineseAnalyzer());
Directory d=FSDirectory.open(Paths.get(PropertiesUtil.getValue("indexPath")));
IndexWriter indexWriter = new IndexWriter(d ,conf);
indexWriter.deleteDocuments(new Term("id", request.getParameter("id")));
indexWriter.forceMergeDeletes(); //强制删除
indexWriter.commit();
indexWriter.close();
}
} catch (Exception e) {
e.printStackTrace();
}
return "edit";
}
public String updateBk() {
Map<String, String[]> paMap = request.getParameterMap();
try {
int edit = blogDao.edit(paMap);
if(edit>0) {
String id = request.getParameter("id");
//修改
Analyzer analyzer = new StandardAnalyzer();
IndexWriterConfig conf = new IndexWriterConfig(analyzer);
Directory dir=FSDirectory.open(Paths.get(PropertiesUtil.getValue("indexPath")));
IndexWriter writer = new IndexWriter(dir, conf );
Document doc=new Document();
doc.add(new StringField("id", id, Field.Store.YES));
doc.add(new TextField("title",request.getParameter("title"), Field.Store.YES));
writer.updateDocument(new Term("id",id), doc);
writer.close();
}
} catch (Exception e) {
e.printStackTrace();
}
return "edit";
}
代码及数据 提取码:ux0f
至此,Lucene介绍与使用介绍完毕,由于作者水平有限难免有疏漏,欢迎留言纠错。
版权归原作者 鑫709 所有, 如有侵权,请联系我们删除。