JAVA_WEB项目之Lucene使用中文分词器IKAnalyzer3.2.8

时间:2021-06-10 03:09:16

上一篇介绍了JAVA_WEB项目之Lucene检索框架中的IndexWriter、IndexSearch优化使用的都是标准的分词器,也就是老外的以一套分词器,主要是以英文的以空格等标准进行分词,和中文分词相差甚远。下面贴出介绍一下中文分词的类别:

1、最大词长分词:

ikanalyzer |  是  |  一个  |  开源  |  的  |  基于  | java |  诧言  |  开发  |  的  |  轻量级  |  的  |  中
文  |  分词  |  工具包  |  从  | 2006 |  年    | 12 |  月  |  推出  | 1.0 |  版  |  开始  | ikanalyzer | 
已经  |  推出  |  出了  |    3  |    个    |    大    |    版本

2、细粒度分词:

ikanalyzer |  是  |  一个  |  一  |  个  |  开源  |  的  |  基于  |    java    |  诧言  |  开发  |  的  |  轻
量级  |  量级  |  的  |  中文  |  分词  |  工具包  |  工具  |  从  | 2006 |  年    | 12 |  月  |  推出  | 
1.0 |  版  |  开始  | ikanalyzer |  已经  |  推出  |  出了  |    3  |    个    |    大    |    版本

其中IKAnalyzer的构造方法中:

new IKAnalyzer();//表示细粒度分词

new IKAnalyzer();//表示最大词长分词

下面依然使用上一篇实例的代码:

依然是一个实体类Goods:

package com.shop.demo2;

public class Goods {

private Integer id;
private String name;
private Double price;
private String pic;
private String remark;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public String getPic() {
return pic;
}
public void setPic(String pic) {
this.pic = pic;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}


}

ConfigureLucene:

package com.shop.demo2;

import java.io.File;
import java.io.IOException;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.wltea.analyzer.lucene.IKAnalyzer;

public class ConfigureLucene {
private ConfigureLucene(){}
//创建索引库
private static Directory dir=null;
//创建分词器
private static Analyzer ana=null;
static{
//根据指定的路径创建索引库,如果路径不存在就会创建
try {
dir=FSDirectory.open(new File("c:/demo"));
//不同的分词器的版本不同,分词的算法不同,StandardAnalyzer只适用于英文
//ana=new StandardAnalyzer(Version.LUCENE_30);
ana=new IKAnalyzer(true);//使用最大词长分词
} catch (Exception e) {
// TODO Auto-generated catch block
throw new RuntimeException(e);
}
}
public static Directory getDir() {
return dir;
}
public static Analyzer getAna() {
return ana;
}

}
DocumentUtil:

package com.shop.demo2;

import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Index;
import org.apache.lucene.document.Field.Store;

public class DocumentUtil {
private DocumentUtil(){}
/**
* 把goods对象转为document对象
*/
public static Document goodsToDocument(Goods goods){
//把goods对象转为document
Document doc=new Document();
doc.add(new Field("id", goods.getId().toString(), Store.YES, Index.NOT_ANALYZED));
doc.add(new Field("name", goods.getName(), Store.YES, Index.ANALYZED));
doc.add(new Field("price", goods.getPrice().toString(), Store.YES, Index.NOT_ANALYZED));
doc.add(new Field("remark", goods.getRemark(), Store.NO, Index.ANALYZED));
return doc;
}

/**
* 把document对象转为goods对象
*/
public static Goods documentToGoods(Document doc){
Goods goods=new Goods();
goods.setId(Integer.parseInt(doc.get("id")));
goods.setName(doc.get("name"));
goods.setPrice(Double.parseDouble(doc.get("price")));
goods.setRemark(doc.get("remark"));
return goods;
}
}
LuceneUtil:

package com.shop.demo2;

import java.io.IOException;

import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriter.MaxFieldLength;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.store.LockObtainFailedException;
import org.wltea.analyzer.lucene.IKAnalyzer;

public class LuceneUtil {
//项目启动的时候创建,关闭的时候销毁
private static IndexWriter indexWriter=null;
private static IndexSearcher indexSearcher=null;
static{//类加载的时候初始化indexWriter
try {
indexWriter=new IndexWriter(ConfigureLucene.getDir(), ConfigureLucene.getAna(), MaxFieldLength.LIMITED);
//在项目销毁的时候关闭indexWriter,每个应用程序对应一个Runtime
Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run() {
// TODO Auto-generated method stub
try {
System.out.println("--J2SE 资源销毁的代码在此处执行--");
indexWriter.close();
} catch (Exception e) {
// TODO Auto-generated catch block
throw new RuntimeException(e);
}
}
});
} catch (Exception e) {
// TODO Auto-generated catch block
throw new RuntimeException(e);
}
}
/**
* 在用的时候取indexWriter,关闭IndexSearcher,因为我们知道如果不关闭IndexSearcher,下次取得IndexSearcher是从内存中取得,并没有同步到索引库
* 因此会导致我们刚插入的数据在用IndexSearcher查询的时候会查询不得到刚插入的数据
* @return
*/
public static IndexWriter getIndexWriter() {
closeIndexSearcher();//关键代码
return indexWriter;
}
public static IndexSearcher getIndexSearcher() {
//作用: 避免其他线程等待,意思是指如果有一个线程执行到创建indexSearcher之后,那么下一个或者多个线程就不用在进入到synchronized里面
if(indexSearcher==null){
synchronized (LuceneUtil.class) {//如果没有其他线程创建了indexSearcher,只允许一个线程进入到里面创建
if(indexSearcher==null){//作用:是否需要创建indexSearcher
try {
indexSearcher=new IndexSearcher(ConfigureLucene.getDir());
} catch (Exception e) {
// TODO Auto-generated catch block
throw new RuntimeException(e);
}
}
}

}
return indexSearcher;
}
public static void closeIndexWriter(){
if(indexWriter!=null){
try {
indexWriter.close();
indexWriter=null;
} catch (Exception e) {
// TODO Auto-generated catch block
throw new RuntimeException(e);
}
}
}
public static void closeIndexSearcher(){
if(indexSearcher!=null){
try {
indexSearcher.close();
indexSearcher = null;
} catch (Exception e) {
// TODO Auto-generated catch block
throw new RuntimeException(e);
}
}
}
}


实现类:

package com.shop.demo2;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Index;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriter.MaxFieldLength;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.wltea.analyzer.lucene.IKQueryParser;
/**
* 此案例实现Lucene向索引库中添加索引和查询索引的功能
* @author Administrator
*
*/
public class HelloWordLucene {

/**
* 把good商品对象添加到索引库中
* @param goods
*/
public void addDocument(Goods goods){
//创建indexWriter
IndexWriter indexwriter=null;
try {
indexwriter=LuceneUtil.getIndexWriter();
//把goods对象转为document
//Document doc=new Document();
/**
* Store配置field字段是否存储到索引库
* YES:字段存储到索引库中,以后查询的时候可以查询出来
* No:不存储到索引库中
* Index: Lucene为提高查询效率,会像字典一样创建索引. 配置此字段是否要建立索引(建立索引的Field就是Term),
* 如果建立索引以后就可以通过此字段查询记录
* NOT_ANALYZED: 创建索引,但是Field的不分词(不分开) 整体作为一个索引
* ANALYZED: 不但要建立索引此Field会被分词(可能一个Field分为多个Term的情况)
* NO: 不建立索引,以后不能通过此字段查询数据
* Store yes Index: ANALYZED: 此Field可以存储,而且Field 关键字支持分词
* Store yes Index: NOT_ANALYZED 此Field可以存储,但是Field不支持分词,作为一个完成Term 例如: 数字 id price 和URL 专业词汇
* Store yes Index: NO: 可以查询出此字段, 但是此字段不作为查询关键字
* Store no Index: ANALYZED: 此Field不存储,但是此Field可以做为关键字搜索
* Store no Index: NOT_ANALYZED: 此Field不存储,但是此Field可以做为整体(不拆分)关键字搜索
* Store no Index: NO: 既不建索引也不存储 没有任何意义,如果这样配置则会抛出异常
*/
// doc.add(new Field("id", goods.getId().toString(), Store.YES, Index.NOT_ANALYZED));
// doc.add(new Field("name", goods.getName(), Store.YES, Index.ANALYZED));
// doc.add(new Field("price", goods.getPrice().toString(), Store.YES, Index.NOT_ANALYZED));
// doc.add(new Field("remark", goods.getRemark(), Store.NO, Index.ANALYZED));
indexwriter.addDocument(DocumentUtil.goodsToDocument(goods));
// 如果没有提交,在没有异常的情况close()之前会自动提交
indexwriter.commit();
} catch (Exception e) {
try {
indexwriter.rollback();
throw new RuntimeException(e);
} catch (IOException e1) {
// TODO Auto-generated catch block
throw new RuntimeException(e);
}
}
}
/**
* 根据指定的条件查询,
* @param name 指定的关键字
* @return 封装了goods对象的list集合
*/
public List<Goods> queryGoods(String name){
List<Goods> goodsList=new ArrayList<Goods>();
//创建查询对象
IndexSearcher searcher=null;
try {
searcher=LuceneUtil.getIndexSearcher();
// 指定查询的关键字到索引库查询
Query query=IKQueryParser.parse("name", name);
/**
* 根据给定的关键字查询,与索引库Term去匹配,5代表: 期望返回的结果数
* 第一次查询: indexSearcher.search 只能获取文档的索引号和匹配的数量
* 返回的结果是TopDoc类型
* totalHits: 命中数, 数组的长度,后面用来做分页
* ScoreDoc[]: 存储匹配的文档编号的数组
* Score: 文档的积分,按照命中率自动算出来
* Doc:当前文档的编号
*/
TopDocs topDocs= searcher.search(query, 5);
// 此变量/每页显示的记录数就是总页数
System.out.println("真正命中的结果数:" + topDocs.totalHits);
// 返回的是符合条件的文档编号,并不是文档本事
ScoreDoc scoreDocs[]= topDocs.scoreDocs;
for(int i=0;i<scoreDocs.length;i++){
ScoreDoc scoreDoc= scoreDocs[i];
System.out.println("真正的命中率:"+scoreDoc.score);
System.out.println("存储的是文档编号:"+scoreDoc.doc);
Document doc= searcher.doc(scoreDoc.doc);
System.out.println(doc.get("id"));
System.out.println(doc.get("name"));
System.out.println(doc.get("price"));
System.out.println(doc.get("remark"));
System.out.println("---------");
// Goods goods=new Goods();
// goods.setId(Integer.parseInt(doc.get("id")));
// goods.setName(doc.get("name"));
// goods.setPrice(Double.parseDouble(doc.get("price")));
// goods.setRemark(doc.get("remark"));
goodsList.add(DocumentUtil.documentToGoods(doc));
}

} catch (Exception e) {
// TODO Auto-generated catch block
throw new RuntimeException(e);
}
return goodsList;
}

public void deleteDocument(int id){
//创建indexWriter
IndexWriter indexwriter=null;
try {
indexwriter=LuceneUtil.getIndexWriter();
// 一般来说都是通过id来删除,所以即使是通过name查询,ID也要建索引,因为更新和删除需要id
// 根据ID把符合条件的document对象删除掉,但是索引(term) 并没有删除
indexwriter.deleteDocuments(new Term("id", id+""));
//同步删除索引库中的索引部分
indexwriter.optimize();
// 如果没有提交,在没有异常的情况close()之前会自动提交
indexwriter.commit();
} catch (Exception e) {
try {
indexwriter.rollback();
throw new RuntimeException(e);
} catch (IOException e1) {
// TODO Auto-generated catch block
throw new RuntimeException(e);
}
}
}
public void updateDocument(Goods goods){
IndexWriter indexwriter=null;
try{
indexwriter=LuceneUtil.getIndexWriter();
indexwriter.updateDocument(new Term("id", goods.getId().toString()), DocumentUtil.goodsToDocument(goods));
indexwriter.optimize();
indexwriter.commit();
}catch (Exception e) {
// TODO: handle exception
try {
indexwriter.rollback();
throw new RuntimeException(e);
} catch (IOException e1) {
// TODO Auto-generated catch block
throw new RuntimeException(e);
}

}
}
/**
* 实现分页的功能
* @param name 查询的关键字
* @param currentPage 当前的页数
* @return 记录数
*/
public List<Goods> queryByPage(String name,int currentPage){
int number=5; // 每页显示5条
List<Goods> goodsList=new ArrayList<Goods>();
IndexSearcher indexSearcher=null;
try {
// 创建查询对象
indexSearcher=LuceneUtil.getIndexSearcher();
// 指定查询的关键字
Query query=IKQueryParser.parse("name", name);
TopDocs topDocs=indexSearcher.search(query,currentPage*number);
// 此变量/每页显示的记录数就是总页数
System.out.println("真正命中的结果数:" + topDocs.totalHits);
int totalPage=0;
if(topDocs.totalHits%number!=0){
totalPage=topDocs.totalHits/number+1;
}else{
totalPage=topDocs.totalHits/number;
}
System.out.println("通过系统的总结果数/每页显示的数量=总页数" + totalPage);
// 返回的是符合条件的文档编号,并不是文档本事
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
// 去期望值 和实际值的 最小值
System.out.println("真正查询出来的数组的长度:" + scoreDocs.length);
for(int i=(currentPage-1)*number;i<scoreDocs.length;i++){
ScoreDoc scoreDoc=scoreDocs[i];
System.out.println("存储了命中率积分:" + scoreDoc.score);
System.out.println("存储的是文档编号:" + scoreDoc.doc);
// 第二次查询: 通过文档的编号,查询真正的文档信息
Document document=indexSearcher.doc(scoreDoc.doc);
goodsList.add(DocumentUtil.documentToGoods(document));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return goodsList;
}

}

测试类:

/**
* 测试中文分词器IKAnalyzer3.2.8添加
*/
@Test
public void test8() {
Goods goods=new Goods();
goods.setId(11);
goods.setName("IBM是全球知名的电脑厂商,现在已经专门做服务的,已经把生产版权转给联想了");
goods.setPrice(2333.9);
goods.setRemark("IBM有质量保证,耐用");
hellowod.addDocument(goods);
}

/**
* 测试中文分词器IKAnalyzer3.2.8的检索
*/
@Test
public void test10() {
List<Goods> list= hellowod.queryGoods("电脑");
for(Goods good:list){
System.out.println("商品编号:"+good.getId()+",商品名称:"+good.getName()+
",商品价格:"+good.getPrice()+",商品的详细信息:"+good.getRemark()
);
}
}

测试结果:

真正命中的结果数:1
真正的命中率:0.37037593
存储的是文档编号:3
11
IBM是全球知名的电脑厂商,现在已经专门做服务的,已经把生产版权转给联想了
2333.9
null
---------
商品编号:11,商品名称:IBM是全球知名的电脑厂商,现在已经专门做服务的,已经把生产版权转给联想了,商品价格:2333.9,商品的详细信息:null
--J2SE 资源销毁的代码在此处执行--