1       Lucene介绍

1.1   什么是Lucene

Lucene是apache下的一个开源的全文检索引擎工具包。它为软件开发人员提供一个简单易用的工具包(类库),以方便的在目标系统中实现全文检索的功能。

1.2   全文检索的应用场景

1.2.1  搜索引擎

全文检索技术---Lucene-LMLPHP

1.2.2  站内搜索(关注)

全文检索技术---Lucene-LMLPHP

1.3  全文检索定义

  全文检索首先将要查询的目标文档中的词提取出来,组成索引,通过查询索引达到搜索目标文档的目的。这种先建立索引,再对索引进行搜索的过程就叫全文检索(Full-text Search

2       Lucene实现全文检索的流程

全文检索技术---Lucene-LMLPHP

3       入门程序

  3.1.1  第一步:添加jar包

   3.1.2  PO,DAO以及测试代码

      全文检索技术---Lucene-LMLPHP

 package cn.xjy.po ;

 public class Book {

     // 图书ID
private Integer id ;
// 图书名称
private String name ;
// 图书价格
private Float price ;
// 图书图片
private String pic ;
// 图书描述
private String description ; public Book() {} public Book(Integer id, String name, Float price, String pic, String description) {
super() ;
this.id = id ;
this.name = name ;
this.price = price ;
this.pic = pic ;
this.description = description ;
} 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 Float getPrice() {
return price ;
} public void setPrice(Float price) {
this.price = price ;
} public String getPic() {
return pic ;
} public void setPic(String pic) {
this.pic = pic ;
} public String getDescription() {
return description ;
} public void setDescription(String description) {
this.description = description ;
} @Override
public String toString() {
return "Book [id=" + id + ", name=" + name + ", price=" + price + ", pic=" + pic
+ ", description=" + description + "]" ;
} }

    

 package cn.xjy.dao ;

 import java.sql.Connection ;
import java.sql.DriverManager ;
import java.sql.PreparedStatement ;
import java.sql.ResultSet ;
import java.util.ArrayList ;
import java.util.List ;
import cn.xjy.po.Book ; public class BookDaoImpl implements BookDao { @Override
public List<Book> getBooks() {
List<Book> books = new ArrayList<Book>() ; try {
Class.forName("com.mysql.jdbc.Driver") ;
Connection con = DriverManager.getConnection("jdbc:mysql:///luncene", "root", "root") ;
PreparedStatement statement = con.prepareStatement("select * from book") ;
ResultSet resultSet = statement.executeQuery() ;
while (resultSet.next()) {
Book book = new Book(resultSet.getInt("id"), resultSet.getString("name"),
resultSet.getFloat("price"), resultSet.getString("pic"),
resultSet.getString("description")) ;
books.add(book) ;
}
} catch (Exception e) {
e.printStackTrace() ;
} return books ;
} }

 

 package cn.xjy.lucene ;

 import java.io.File ;
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.Store ;
import org.apache.lucene.document.FloatField ;
import org.apache.lucene.document.IntField ;
import org.apache.lucene.document.TextField ;
import org.apache.lucene.index.DirectoryReader ;
import org.apache.lucene.index.IndexReader ;
import org.apache.lucene.index.IndexWriter ;
import org.apache.lucene.index.IndexWriterConfig ;
import org.apache.lucene.index.Term ;
import org.apache.lucene.queryparser.classic.QueryParser ;
import org.apache.lucene.search.BooleanClause.Occur ;
import org.apache.lucene.search.BooleanQuery ;
import org.apache.lucene.search.IndexSearcher ;
import org.apache.lucene.search.NumericRangeQuery ;
import org.apache.lucene.search.Query ;
import org.apache.lucene.search.ScoreDoc ;
import org.apache.lucene.search.TermQuery ;
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.IKAnalyzer ;
import cn.xjy.dao.BookDao ;
import cn.xjy.dao.BookDaoImpl ;
import cn.xjy.po.Book ; public class TestLucene { /**
* 创建索引库
* @throws Exception
*/
public void lucene() throws Exception {
BookDao bookDao = new BookDaoImpl() ;
List<Book> books = bookDao.getBooks() ;
// 采集数据的目的是为了索引,在索引前需要将原始内容创建成文档(Document),
// 文档(Document)中包括一个一个的域(Field)。 // 1.创建document集合对象
List<Document> documents = new ArrayList<Document>() ; // 2.循环遍历数据集,根据需求创建不同的filed,添加到对应的document对象中
Document document = null ;
for (Book book : books) {
document = new Document() ;
Field id = new IntField("id", book.getId(), Store.YES) ;
Field name = new TextField("name", book.getName(), Store.YES) ; if (book.getId()==4)
name.setBoost(100f) ;// 设置权重.值越大搜索越靠前
Field price = new FloatField("price", book.getPrice(), Store.YES) ;
Field pic = new TextField("pic", book.getPic(), Store.YES) ;
Field description = new TextField("description", book.getDescription(), Store.NO) ; document.add(id) ;
document.add(name) ;
document.add(price) ;
document.add(pic) ;
document.add(description) ;
documents.add(document) ;
} // 3.把每个document对象添加到document集合中 // 4.分析文档,对文档中的内容记性分词,实例化分析器对象,首先创建索引目录
Analyzer analyzer = new StandardAnalyzer() ;
Directory directory = FSDirectory.open(new File("src/index")) ; // 5.创建indexWriterConfig对象
IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, analyzer) ; // 6.创建indexWriter对象
IndexWriter writer = new IndexWriter(directory, config) ; // 7.通过indexWriter对象,添加文档对象,写入索引库的过程
for (Document doc : documents) {
writer.addDocument(doc) ;
} // 8.关闭indexWriter流
writer.close() ; } /**
* 创建索引库,可解析中文
* @throws Exception
*/
public void luceneCN() throws Exception {
BookDao bookDao = new BookDaoImpl() ;
List<Book> books = bookDao.getBooks() ;
// 采集数据的目的是为了索引,在索引前需要将原始内容创建成文档(Document),
// 文档(Document)中包括一个一个的域(Field)。 // 1.创建document集合对象
List<Document> documents = new ArrayList<Document>() ; // 2.循环遍历数据集,根据需求创建不同的filed,添加到对应的document对象中
Document document = null ;
for (Book book : books) {
document = new Document() ;
Field id = new IntField("id", book.getId(), Store.YES) ;
Field name = new TextField("name", book.getName(), Store.YES) ; if (book.getId()==4)
name.setBoost(100f) ;// 设置权重.值越大搜索越靠前
Field price = new FloatField("price", book.getPrice(), Store.YES) ;
Field pic = new TextField("pic", book.getPic(), Store.YES) ;
Field description = new TextField("description", book.getDescription(), Store.YES) ; document.add(id) ;
document.add(name) ;
document.add(price) ;
document.add(pic) ;
document.add(description) ;
documents.add(document) ;
} // 3.把每个document对象添加到document集合中 // 4.分析文档,对文档中的内容记性分词,实例化分析器对象,首先创建索引目录
Analyzer analyzer = new IKAnalyzer();
Directory directory = FSDirectory.open(new File("src/index")) ; // 5.创建indexWriterConfig对象
IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, analyzer) ; // 6.创建indexWriter对象
IndexWriter writer = new IndexWriter(directory, config) ; // 7.通过indexWriter对象,添加文档对象,写入索引库的过程
for (Document doc : documents) {
writer.addDocument(doc) ;
} // 8.关闭indexWriter流
writer.close() ; } /**
* 删除指定的索引
* @throws Exception
*/
public void deleteIndex() throws Exception {
// 1.指定索引库的位置
Directory directory = FSDirectory.open(new File("src/index")) ; // 2.创建indexWriterConfig
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LATEST, null) ; // 3.创建indexWriter
IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig) ; // 4.删除指定的索引(new Term())
// indexWriter.deleteDocuments(new Term("id", "1"));//参数是Term()
// indexWriter.deleteDocuments(new QueryParser("id", new
// StandardAnalyzer()).parse("id:1"));//参数为query
indexWriter.deleteAll() ;// 删除所有
// 5.关闭流
indexWriter.close() ; System.out.println("删除成功") ; // 在查询一遍验证是否删除
searchIndex() ;
} /**
* 更新索引,
* 最好的做法是先查出要修改的索引进行更新
* @throws Exception
*/
public void updateIndex() throws Exception {
// 1.指定索引库
Directory directory = FSDirectory.open(new File("src/index")) ; // 2.定义indexReader
IndexReader indexReader = DirectoryReader.open(directory) ; // 3.定义indexSearcher
IndexSearcher indexSearcher = new IndexSearcher(indexReader) ; Query query = new QueryParser("id", new StandardAnalyzer()).parse("id:1") ;
// 查询索引库
TopDocs topDocs = indexSearcher.search(query, 1) ; // 获取查询到的对象
ScoreDoc scoreDoc = topDocs.scoreDocs[0] ; // 获取document对象
Document document = indexSearcher.doc(scoreDoc.doc) ; // 更新内容
document.removeField("name") ;
document.add(new TextField("name", "这是测试更新的内容", Store.YES)) ; // 初始化indexWriterConfig和indexWriter对象
IndexWriterConfig IndexWriterConfig = new IndexWriterConfig(Version.LATEST,
new StandardAnalyzer()) ;
IndexWriter indexWriter = new IndexWriter(directory, IndexWriterConfig) ; // 开始更新,这个方法第一个参数如果设置为null,则不会删除原来的数据,而且添加了一条更新后的新数据
// 为了保证数据的严谨性,必须删除为更新之前的数据,添加上更新后的数据就哦了
indexWriter.updateDocument(new Term("id", "1"), document) ;
indexWriter.close() ;
indexReader.close() ; System.out.println("更新成功") ;
} /**
* 可多条件连接QueryParser会将用户输入的查询表达式解析成Query对象实例。
* 搜索 Query query = queryParser.parse("*:*") ;
* @throws Exception
*/
public void searchIndex() throws Exception {
// 创建分析器
Analyzer analyzer = new StandardAnalyzer() ; // 查询条件
QueryParser queryParser = new QueryParser("description", analyzer) ;
Query query = queryParser.parse("description:个") ; // 指定搜索目录
Directory directory = FSDirectory.open(new File("src/index")) ; // 创建indexReader
IndexReader indexReader = DirectoryReader.open(directory) ; // 创建indexSearch对象
IndexSearcher indexSearcher = new IndexSearcher(indexReader) ; // 查询索引库
TopDocs topDocs = indexSearcher.search(query, 10) ; // 获取前十条记录
ScoreDoc [] scoreDocs = topDocs.scoreDocs ; System.out.println("文档个数:" + topDocs.totalHits) ; for (ScoreDoc scoreDoc : scoreDocs) {
Document doc = indexSearcher.doc(scoreDoc.doc) ;
System.out.println(doc) ;
}
} /**
* 这种不可多条件查询
* 搜索 Query query = new TermQuery(new Term("id", "1"));
* @throws Exception
*/
public void searchIndex2() throws Exception {
// 创建分析器
Analyzer analyzer = new StandardAnalyzer() ; // 查询条件
Query query = new TermQuery(new Term("description", "徐景洋驻马店")) ; // 指定搜索目录
Directory directory = FSDirectory.open(new File("src/index")) ; // 创建indexReader
IndexReader indexReader = DirectoryReader.open(directory) ; // 创建indexSearch对象
IndexSearcher indexSearcher = new IndexSearcher(indexReader) ; // 查询索引库
TopDocs topDocs = indexSearcher.search(query, 10) ; // 获取前十条记录
ScoreDoc [] scoreDocs = topDocs.scoreDocs ; System.out.println("文档个数:" + topDocs.totalHits) ; for (ScoreDoc scoreDoc : scoreDocs) {
Document doc = indexSearcher.doc(scoreDoc.doc) ;
System.out.println(doc) ;
}
} /**
* NumericRangeQuery,指定数字范围查询.(创建field类型时,注意与之对应)
* 搜索 Query query = NumericRangeQuery.newIntRange("id", 1, 9, true, true);
* @throws Exception
*/
public void searchIndex3() throws Exception {
// 创建分析器
Analyzer analyzer = new StandardAnalyzer() ; // 查询条件
// 创建查询
// 第一个参数:域名
// 第二个参数:最小值
// 第三个参数:最大值
// 第四个参数:是否包含最小值
// 第五个参数:是否包含最大值 Query query = NumericRangeQuery.newIntRange("id", 1, 9, true, true) ; // 指定搜索目录
Directory directory = FSDirectory.open(new File("src/index")) ; // 创建indexReader
IndexReader indexReader = DirectoryReader.open(directory) ; // 创建indexSearch对象
IndexSearcher indexSearcher = new IndexSearcher(indexReader) ; // 查询索引库
TopDocs topDocs = indexSearcher.search(query, 10) ; // 获取前十条记录
ScoreDoc [] scoreDocs = topDocs.scoreDocs ; System.out.println("文档个数:" + topDocs.totalHits) ; for (ScoreDoc scoreDoc : scoreDocs) {
Document doc = indexSearcher.doc(scoreDoc.doc) ;
System.out.println(doc) ;
}
} /**
* 1、MUST和MUST表示“与”的关系,即“并集”。
2、MUST和MUST_NOT前者包含后者不包含。
3、MUST_NOT和MUST_NOT没意义
4、SHOULD与MUST表示MUST,SHOULD失去意义;
5、SHOUlD与MUST_NOT相当于MUST与MUST_NOT。
6、SHOULD与SHOULD表示“或”的概念。 * BooleanQuery,布尔查询,实现组合条件查询。
* 搜索 BooleanQuery query = new BooleanQuery() ;
* @throws Exception
*/
public void searchIndex4() throws Exception {
// 创建分析器
Analyzer analyzer = new StandardAnalyzer() ; // 查询条件
BooleanQuery query = new BooleanQuery() ; Query query1 = new TermQuery(new Term("name", "spring")) ;
Query query2 = NumericRangeQuery.newFloatRange("price", 60f, 80f, true, true) ; // MUST:查询条件必须满足,相当于AND
// SHOULD:查询条件可选,相当于OR
// MUST_NOT:查询条件不能满足,相当于NOT非
query.add(query2, Occur.SHOULD) ;
query.add(query1, Occur.MUST) ; // 指定搜索目录
Directory directory = FSDirectory.open(new File("src/index")) ; // 创建indexReader
IndexReader indexReader = DirectoryReader.open(directory) ; // 创建indexSearch对象
IndexSearcher indexSearcher = new IndexSearcher(indexReader) ; // 查询索引库
TopDocs topDocs = indexSearcher.search(query, 10) ; // 获取前十条记录
ScoreDoc [] scoreDocs = topDocs.scoreDocs ; System.out.println("文档个数:" + topDocs.totalHits) ; for (ScoreDoc scoreDoc : scoreDocs) {
Document doc = indexSearcher.doc(scoreDoc.doc) ;
System.out.println(doc) ;
}
} }
 package cn.xjy.test ;

 import org.junit.Test ;
import cn.xjy.lucene.TestLucene ; public class MyTest { @Test
public void testIndex() throws Exception {
TestLucene lucene = new TestLucene() ;
// lucene.lucene();
lucene.luceneCN() ;
System.out.println("创建成功") ;
} @Test
public void testSearch() throws Exception {
TestLucene lucene = new TestLucene() ;
// lucene.searchIndex();
lucene.searchIndex2() ;
// lucene.searchIndex3();
// lucene.searchIndex4();
} @Test
public void testDelete() throws Exception {
TestLucene lucene = new TestLucene() ;
lucene.deleteIndex() ;
} @Test
public void testUpdate() throws Exception {
TestLucene lucene = new TestLucene() ;
lucene.updateIndex() ;
}
}

配置文件:IKAnalyzer.cfg.xml

 <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties> <comment>IK Analyzer 扩展配置</comment>
<!-- 用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">mydict.dic</entry>
<!-- 用户可以在这里配置自己的扩展停用词字典 -->
<entry key="ext_stopwords">ext_stopword.dic</entry> </properties>

4      Field域

4.1   Field属性

  Field是文档中的域,包括Field名和Field值两部分,一个文档可以包括多个Field,Document只是Field的一个承载体,Field值即为要索引的内容,也是要搜索的内容。

是否分词(tokenized)

是:作分词处理,即将Field值进行分词,分词的目的是为了索引。

比如:商品名称、商品简介等,这些内容用户要输入关键字搜索,由于搜索的内容格式大、内容多需要分词后将语汇单元索引。

否:不作分词处理

比如:商品id、订单号、身份证号等

是否索引(indexed)

是:进行索引。将Field分词后的词或整个Field值进行索引,索引的目的是为了搜索。

比如:商品名称、商品简介分析后进行索引,订单号、身份证号不用分析但也要索引,这些将来都要作为查询条件。

否:不索引。该域的内容无法搜索到

比如:商品id、文件路径、图片路径等,不用作为查询条件的不用索引。

是否存储(stored)

是:将Field值存储在文档中,存储在文档中的Field才可以从Document中获取。

比如:商品名称、订单号,凡是将来要从Document中获取的Field都要存储。

否:不存储Field值,不存储的Field无法通过Document获取

比如:商品简介,内容较大不用存储。如果要向用户展示商品简介可以从系统的关系数据库中获取商品简介。

4.2   Field常用类型

  下边列出了开发中常用 的Filed类型,注意Field的属性,根据需求选择:

  

Field类

数据类型

Analyzed

是否分词

Indexed

是否索引

Stored

是否存储

说明

StringField(FieldName, FieldValue,Store.YES))

字符串

N

Y

Y或N

这个Field用来构建一个字符串Field,但是不会进行分词,会将整个串存储在索引中,比如(订单号,身份证号等)

是否存储在文档中用Store.YES或Store.NO决定

LongField(FieldName, FieldValue,Store.YES)

Long型

Y

Y

Y或N

这个Field用来构建一个Long数字型Field,进行分词和索引,比如(价格)

是否存储在文档中用Store.YES或Store.NO决定

StoredField(FieldName, FieldValue)

重载方法,支持多种类型

N

N

Y

这个Field用来构建不同类型Field

不分析,不索引,但要Field存储在文档中

TextField(FieldName, FieldValue, Store.NO)

TextField(FieldName, reader)

字符串

Y

Y

Y或N

如果是一个Reader, lucene猜测内容比较多,会采用Unstored的策略.

5   使用中文分词器IKAnalyzer

  IKAnalyzer继承Lucene的Analyzer抽象类,使用IKAnalyzer和Lucene自带的分析器方法一样,将Analyzer测试代码改为IKAnalyzer测试中文分词效果。

如果使用中文分词器ik-analyzer,就在索引和搜索程序中使用一致的分词器ik-analyzer。

5.1 添加jar包

      全文检索技术---Lucene-LMLPHP

04-24 13:52