2.1倒序索引的计算步骤
3.1lucene的分词器测试
引入依赖
<dependency><!--查询相关jar包 -->
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>6.0.0</version>
</dependency>
<dependency><!-- lucene自带只能中文分词器jar包 -->
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-smartcn</artifactId>
<version>6.0.0</version>
</dependency>
<dependency><!--测试用到的lucene工具包 -->
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>6.0.0</version>
</dependency>
<dependency><!--测试用到的lucene核心包 -->
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>6.0.0</version>
</dependency>
分词器的测试代码
分词处理获取文本的属性
package cn.tedu.lucene.test;
import java.io.IOException;
import java.io.StringReader;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer;
import org.apache.lucene.analysis.core.SimpleAnalyzer;
import org.apache.lucene.analysis.core.WhitespaceAnalyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.junit.Test;
//分词器测试
public class AnalyzerTest {
/**
* 处理分词器的分词过程
* 打印展示文本属性 词性
* @throws IOException
*/
public void printTerm(Analyzer analyzer,String msg) throws IOException{
//获取字符串流
StringReader reader=new StringReader(msg);
//可以通过analyzer对象调用方法
//不同实现类底层分词逻辑不同
TokenStream tokenStream = analyzer.tokenStream("test", reader);
//filedName:域属性名称,分词测试没有具体的意义
//将分词的文本的属性打印词项的词,需要指针重置
tokenStream.reset();
CharTermAttribute attribute = tokenStream.getAttribute(CharTermAttribute.class);
while(tokenStream.incrementToken()){
System.out.println(attribute.toString());
//词项
}
}
//实现测试方法,传递使用不同的分词器
@Test
public void run() throws IOException{
//准备一个计算分词的文本
String msg="你是喜欢王者荣耀,还是喜欢英雄联盟";
//使用不同的分词器实现类
Analyzer a1=new SimpleAnalyzer();//根据标点切分句子
Analyzer a2=new WhitespaceAnalyzer();//根据空格切分,句子,词,字
Analyzer a3=new StandardAnalyzer();//根据字符切分,字
Analyzer a4=new SmartChineseAnalyzer();//根据中文词切分
System.out.println("*********简单*********");
printTerm(a1, msg);
System.out.println("*********空格*********");
printTerm(a2, msg);
System.out.println("*********标准*********");
printTerm(a3, msg);
System.out.println("*********中文*********");
printTerm(a4, msg);
}
}
3.2IK分词器
<!--ik导入本地管理的分词器 -->
<dependency><!--测试用到的lucene核心包 -->
<groupId>cn.tedu</groupId>
<artifactId>ik-analyzer</artifactId>
<version>u6</version>
<scope>system</scope>
<systemPath>D://ali_repo/IKAnalyzer2012_u6.jar</systemPath>
</dependency>
IKAnalyzer2012_u6.jar下载
http://lxw1234.com/archives/2015/07/422.htm
下载地址:https://code.google.com/p/ik-analyzer/downloads/list
这个地址估计大家不方便,上传了一份到网盘:
http://pan.baidu.com/s/1i3eXhAH
密:34w6
测试代码
package cn.tedu.lucene.test;
import java.io.IOException;
import org.apache.lucene.analysis.Tokenizer;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
import org.apache.lucene.analysis.tokenattributes.TypeAttribute;
import org.wltea.analyzer.core.IKSegmenter;
import org.wltea.analyzer.core.Lexeme;
/**
*
* 如果在使用中出现错误
* java.lang.AssertionError: TokenStream implementation classes or at least their incrementToken() implementation must be final
* 那么为IKTokenizer6x class 加入final关键字
*
*/
public final class IKTokenizer6x extends Tokenizer {
// IK分词器实现
private IKSegmenter _IKImplement;
// 词元文本属性
private final CharTermAttribute termAtt;
// 词元位移属性
private final OffsetAttribute offsetAtt;
// 词元分类属性(该属性分类参考org.wltea.analyzer.core.Lexeme中的分类常量)
private final TypeAttribute typeAtt;
// 记录最后一个词元的结束位置
private int endPosition;
// Lucene 6.x Tokenizer适配器类构造函数;实现最新的Tokenizer接口
public IKTokenizer6x(boolean useSmart) {
super();
offsetAtt = addAttribute(OffsetAttribute.class);
termAtt = addAttribute(CharTermAttribute.class);
typeAtt = addAttribute(TypeAttribute.class);
_IKImplement = new IKSegmenter(input, useSmart);
}
@Override
public boolean incrementToken() throws IOException {
// 清除所有的词元属性
clearAttributes();
Lexeme nextLexeme = _IKImplement.next();
if (nextLexeme != null) {
// 将Lexeme转成Attributes
// 设置词元文本
termAtt.append(nextLexeme.getLexemeText());
// 设置词元长度
termAtt.setLength(nextLexeme.getLength());
// 设置词元位移
offsetAtt.setOffset(nextLexeme.getBeginPosition(), nextLexeme.getEndPosition());
// 记录分词的最后位置
endPosition = nextLexeme.getEndPosition();
// 记录词元分类
typeAtt.setType(nextLexeme.getLexemeText());
// 返会true告知还有下个词元
return true;
}
// 返会false告知词元输出完毕
return false;
}
@Override
public void reset() throws IOException {
super.reset();
_IKImplement.reset(input);
}
@Override
public final void end() {
int finalOffset = correctOffset(this.endPosition);
offsetAtt.setOffset(finalOffset, finalOffset);
}
}
package cn.tedu.lucene.test;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.Tokenizer;
public class IKAnalyzer6x extends Analyzer {
private boolean useSmart;
public boolean useSmart() {
return useSmart;
}
public void setUseSmart(boolean useSmart) {
this.useSmart = useSmart;
}
// IK分词器Lucene Analyzer接口实现类;默认细粒度切分算法
public IKAnalyzer6x() {
this(false);
}
// IK分词器Lucene Analyzer接口实现类;当为true时,分词器进行智能切分
public IKAnalyzer6x(boolean useSmart) {
super();
this.useSmart = useSmart;
}
// 重写最新版本的createComponents;重载Analyzer接口,构造分词组件
@Override
protected TokenStreamComponents createComponents(String fieldName) {
Tokenizer _IKTokenizer = new IKTokenizer6x(this.useSmart());
return new TokenStreamComponents(_IKTokenizer);
}
}
1.1创建索引
package cn.tedu.lucene.index;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.DoublePoint;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.store.FSDirectory;
import org.junit.Test;
import org.wltea.analyzer.lucene.IKAnalyzer;
import cn.tedu.lucene.test.IKAnalyzer6x;
/**
* lucene提供创建倒排索引算法的所有工具
* 包内容,直接使用工具包实现索引的创建
* @author 在路上
*
*/
public class CreateIndex {
/*
* 1.确定索引的输出位置
* 2.准备一个具备使用IK分词器输出对象流对象writer
* 3.内存的document(读取元数据封装)数据封装
* 4.输出写入到索引文件
* 域的类型(文本,数字),存储的索引数据
*/
@Test
public void createIndex() throws Exception{
//确认一个输出文件夹位置作为索引
Path path=Paths.get("d://index01");
//交给lucene的目录工具类
FSDirectory dir = FSDirectory.open(path);
//创建输出对象对应的配置对象
IndexWriterConfig config=new IndexWriterConfig(new IKAnalyzer6x());
//定义写出时的方式APPEND CREATE CREATE_OR_APPEND
config.setOpenMode(OpenMode.CREATE);
//配合dir路径创建输出write
IndexWriter writer=new IndexWriter(dir, config);
//封装document 2各document
Document doc1=new Document();
Document doc2=new Document();
//title content image publisher
//name 域的名称 自定义
//value 读取的yuan素据的值
//store 是否进行存储(document的数据输出到索引文件中,并不是所有的元数据内容都需要存储到索引中)
//YES 表示存储,NO表示不存储
doc1.add(new TextField("title","国庆大阅兵",Store.YES));
doc1.add(new TextField("content","10月1日在北京举行的国庆70周年阅兵备受关注",Store.NO));
doc1.add(new StringField("image", "http://image.jt.com", Store.YES));
doc1.add(new TextField("publisher", "新华网",Store.YES));
//价格
doc1.add(new DoublePoint("price", 500));
//yes/no 到底什么区别 TextField/StringField/DoublePoint的区别
doc2.add(new TextField("title","国庆假期准备去哪",Store.YES));
doc2.add(new TextField("content","10月1日在北京9点开始出城拥堵",Store.YES));
doc2.add(new TextField("image", "http://image.jt.com/KKK", Store.YES));
doc2.add(new TextField("publisher", "旅游网",Store.YES));
//价格
doc2.add(new DoublePoint("price", 8500));
//writer携带两个document输出到索引
writer.addDocument(doc1);
writer.addDocument(doc2);
//直接写出
writer.commit();
}
}
package cn.tedu.lucene.index;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.DoublePoint;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.store.FSDirectory;
import org.junit.Test;
import org.wltea.analyzer.lucene.IKAnalyzer;
import cn.tedu.lucene.test.IKAnalyzer6x;
/**
* lucene提供创建倒排索引算法的所有工具
* 包内容,直接使用工具包实现索引的创建
* @author 在路上
*
*/
public class CreateIndex {
/*
* 1.确定索引的输出位置
* 2.准备一个具备使用IK分词器输出对象流对象writer
* 3.内存的document(读取元数据封装)数据封装
* 4.输出写入到索引文件
* 域的类型(文本,数字),存储的索引数据
*/
@Test
public void createIndex() throws Exception{
//确认一个输出文件夹位置作为索引
Path path=Paths.get("d://index01");
//交给lucene的目录工具类
FSDirectory dir = FSDirectory.open(path);
//创建输出对象对应的配置对象
IndexWriterConfig config=new IndexWriterConfig(new IKAnalyzer6x());
//定义写出时的方式APPEND CREATE CREATE_OR_APPEND
config.setOpenMode(OpenMode.CREATE);
//配合dir路径创建输出write
IndexWriter writer=new IndexWriter(dir, config);
//封装document 2各document
Document doc1=new Document();
Document doc2=new Document();
//title content image publisher
//name 域的名称 自定义
//value 读取的yuan素据的值
//store 是否进行存储(document的数据输出到索引文件中,并不是所有的元数据内容都需要存储到索引中)
//YES 表示存储,NO表示不存储
doc1.add(new TextField("title","国庆大阅兵",Store.YES));
doc1.add(new TextField("content","10月1日在北京举行的国庆70周年阅兵备受关注",Store.NO));
doc1.add(new StringField("image", "http://image.jt.com", Store.YES));
doc1.add(new TextField("publisher", "新华网",Store.YES));
//价格
doc1.add(new DoublePoint("price", 500));
doc1.add(new StringField("price", "500元", Store.YES));
//yes/no 到底什么区别 TextField/StringField/DoublePoint的区别
doc2.add(new TextField("title","国庆假期准备去哪",Store.YES));
doc2.add(new TextField("content","10月1日在北京9点开始出城拥堵",Store.YES));
doc2.add(new TextField("image", "http://image.jt.com/KKK", Store.YES));
doc2.add(new TextField("publisher", "旅游网",Store.YES));
//价格
doc2.add(new DoublePoint("price", 8500));
//writer携带两个document输出到索引
writer.addDocument(doc1);
writer.addDocument(doc2);
//直接写出
writer.commit();
}
}
/*
* lucene中将查询功能封装到一个规范(接口类)Query
* 可以根据不同的查询功能使用不同的实现类
*
* 1.指定查询的索引
* 2.搜索对象的创建,使用一个输入流reader
* 3.封装查询条件Query,不同的功能,不同的实现类
* 4.浅查询实现数据的遍历使用
*/
//词项查询
@Test
public void termQuery() throws Exception{
//指定索引位置
Path path=Paths.get("d://index01");
FSDirectory dir=FSDirectory.open(path);
//获取输入流
IndexReader reader=DirectoryReader.open(dir);
//利用输入流获取搜索对象
IndexSearcher search=new IndexSearcher(reader);
//根据不同的查询条件,封装query实现类对象
//termquery
//先提供一个词项 fid域名,关键字条件
Term term=new Term("title", "国庆");
Query query=new TermQuery(term);
//利用浅查询 获取结果集
TopDocs topDocs=search.search(query, 10);//查询前10条
//返回的是一个封装搜索计算的浅查询,和对应的document数据
//评分的对象
System.out.println("总查询条数:"+topDocs.totalHits);
System.out.println("其中的最大评分:"+topDocs.getMaxScore());
ScoreDoc[] docs=topDocs.scoreDocs;//查询的条数10,最多才封装10条
for (ScoreDoc scoreDoc : docs) {
//获取document的id值
System.out.println("document的id值:"+scoreDoc.doc);
//读取document
Document doc=search.doc(scoreDoc.doc);
//读取域属性
System.out.println("title:"+doc.get("title"));
System.out.println("content:"+doc.get("content"));
System.out.println("price:"+doc.get("price"));
System.out.println("publisher:"+doc.get("publisher"));
System.out.println("image:"+doc.get("image"));
}
}
//多域查询
@Test
public void multiFieldQuery() throws Exception{
// 指定索引位置
Path path = Paths.get("d://index01");
FSDirectory dir = FSDirectory.open(path);
// 获取输入流
IndexReader reader = DirectoryReader.open(dir);
// 利用输入流获取搜索对象
IndexSearcher search = new IndexSearcher(reader);
//多域查询
//指定多个域
String[] fields={"title","content"};
//封装查询字符串条件 ,利用多域查询的解析器,对条件先进行分词计算
MultiFieldQueryParser parser=new MultiFieldQueryParser(fields, new IKAnalyzer6x());
Query query=parser.parse("北京国庆大阅兵");
//底层封装多个TermQuery title北京 title国庆 title大 title阅兵
// 利用浅查询 获取结果集
TopDocs topDocs = search.search(query, 10);// 查询前10条
// 返回的是一个封装搜索计算的浅查询,和对应的document数据
// 评分的对象,通过topDocs获取封装了documentid的评分数组
ScoreDoc[] docs = topDocs.scoreDocs;// 查询的条数10,最多才封装10条
System.out.println("总查询条数:" + topDocs.totalHits);
System.out.println("其中的最大评分:" + topDocs.getMaxScore());
for (ScoreDoc scoreDoc : docs) {
// 获取document的id值
System.out.println("document的id值:" + scoreDoc.doc);
System.out.println("当前document的评分:" + scoreDoc.score);
// 读取document
Document doc = search.doc(scoreDoc.doc);
// 读取域属性
System.out.println("title:" + doc.get("title"));
System.out.println("content:" + doc.get("content"));
System.out.println("price:" + doc.get("price"));
System.out.println("publisher:" + doc.get("publisher"));
System.out.println("image:" + doc.get("image"));
}
}
//布尔查询条件
@Test
public void booleanQuery() throws Exception{
// 指定索引位置
Path path = Paths.get("d://index01");
FSDirectory dir = FSDirectory.open(path);
// 获取输入流
IndexReader reader = DirectoryReader.open(dir);
// 利用输入流获取搜索对象
IndexSearcher search = new IndexSearcher(reader);
//布尔查询
//封装使用两个子条件
Query query1=new TermQuery(new Term("title","国庆"));
Query query2=new TermQuery(new Term("content","周年"));
//封装布尔子条件
//query 封装单独的查询条件
//occur 决定这个条件的逻辑值 MUST MUST_NOT filter should
//should 与must一起使用无效
//两个should就是并集
//filter 效果与must相同,但是无评分===0
//should和must无效 和filter也无效
BooleanClause bc1=new BooleanClause(query1, Occur.MUST);
BooleanClause bc2=new BooleanClause(query2, Occur.MUST_NOT);
//利用子条件bc1 bc2创建布尔条件
Query query=new BooleanQuery.Builder().add(bc1).add(bc2).build();
// 底层封装多个TermQuery title北京 title国庆 title大 title阅兵
// 利用浅查询 获取结果集
TopDocs topDocs = search.search(query, 10);// 查询前10条
// 返回的是一个封装搜索计算的浅查询,和对应的document数据
// 评分的对象,通过topDocs获取封装了documentid的评分数组
ScoreDoc[] docs = topDocs.scoreDocs;// 查询的条数10,最多才封装10条
System.out.println("总查询条数:" + topDocs.totalHits);
System.out.println("其中的最大评分:" + topDocs.getMaxScore());
for (ScoreDoc scoreDoc : docs) {
// 获取document的id值
System.out.println("document的id值:" + scoreDoc.doc);
System.out.println("当前document的评分:" + scoreDoc.score);
// 读取document
Document doc = search.doc(scoreDoc.doc);
// 读取域属性
System.out.println("title:" + doc.get("title"));
System.out.println("content:" + doc.get("content"));
System.out.println("price:" + doc.get("price"));
System.out.println("publisher:" + doc.get("publisher"));
System.out.println("image:" + doc.get("image"));
}
}
子条件不能过多,2-5之间,过多的子条件会产生复杂的逻辑关系,更不建议多级布尔条件
//范围查询
@Test
public void rangeQuery() throws Exception{
// 指定索引位置
Path path = Paths.get("d://index01");
FSDirectory dir = FSDirectory.open(path);
// 获取输入流
IndexReader reader = DirectoryReader.open(dir);
// 利用输入流获取搜索对象
IndexSearcher search = new IndexSearcher(reader);
//范围查询
Query query=DoublePoint.newRangeQuery("price", 100, 1000);
// 底层封装多个TermQuery title北京 title国庆 title大 title阅兵
// 利用浅查询 获取结果集
TopDocs topDocs = search.search(query, 10);// 查询前10条
// 返回的是一个封装搜索计算的浅查询,和对应的document数据
// 评分的对象,通过topDocs获取封装了documentid的评分数组
ScoreDoc[] docs = topDocs.scoreDocs;// 查询的条数10,最多才封装10条
System.out.println("总查询条数:" + topDocs.totalHits);
System.out.println("其中的最大评分:" + topDocs.getMaxScore());
for (ScoreDoc scoreDoc : docs) {
// 获取document的id值
System.out.println("document的id值:" + scoreDoc.doc);
System.out.println("当前document的评分:" + scoreDoc.score);
// 读取document
Document doc = search.doc(scoreDoc.doc);
// 读取域属性
System.out.println("title:" + doc.get("title"));
System.out.println("content:" + doc.get("content"));
System.out.println("price:" + doc.get("price"));
System.out.println("publisher:" + doc.get("publisher"));
System.out.println("image:" + doc.get("image"));
}
}
1.lucene的缺点