Lucene是apache软件基金会4 jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,但它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎(英文与德文两种西方语言)。Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。Lucene是一套用于全文检索和搜寻的开源程式库,由Apache软件基金会支持和提供。Lucene提供了一个简单却强大的应用程式接口,能够做全文索引和搜寻。在Java开发环境里Lucene是一个成熟的免费开源工具。就其本身而言,Lucene是当前以及最近几年最受欢迎的免费Java信息检索程序库。人们经常提到信息检索程序库,虽然与搜索引擎有关,但不应该将信息检索程序库与搜索引擎相混淆。
在使用Lucene时有一些注意事项:
1.由于Lucene目前已经更新至6.6.0版本,所以对于JDK版本会有一些要求,建议使用低版本JDK的同学相应下载低版本Lucene。
2.Lucene版本更新迅速,所以各版本之间部分代码实现会有差异,所以在实现相关功能时,注意版本问题。
1.Lucene全文检索流程:
通过对于原数据的采集,并创建索引(Lucene的核心),将索引存放至索引库中,用户进行查询时,直接通过索引查询,从而实现了Lucene搜索的快捷,这也是我们学习使用Lucene的原因。相比较传统方式搜索,通过索引,无需依次扫描大量文件,提升了搜索速度。
2.Lucene下载:点击打开链接
3.Lucene环境搭建:
3.1环境准备:
JDK:1.7
lucene:4.10(最新为6.6.0)
MySql:提供原数据
4.Eclipse创建工程,导入相关jar包:Mysql驱动包,Analysis分词包,Core核心包,QueryParser包
注意:jUnit包为项目单元测试包,具体使用方法,大家可以百度Eclipse中如何使用jUnit,这里不做过多介绍。
5.通过java程序实现数据采集与索引创建:
5.1创建Book.java实体类:
public class Book { // 图书ID private Integer id; // 图书名称 private String name; // 图书价格 private Float price; // 图书描述 private String 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 getDescription() { return description; } public void setDescription(String description) { this.description = description; } }
5.2创建数据库使用对象BookDao.java:
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 com.jsoft.vo.Book; public class BookDao{ public List<Book> queryBookList() throws Exception { // 数据库链接 Connection connection = null; // 预编译statement PreparedStatement preparedStatement = null; // 结果集 ResultSet resultSet = null; // 图书列表 List<Book> list = new ArrayList<Book>(); try { // 加载数据库驱动 Class.forName("com.mysql.jdbc.Driver"); // 连接数据库 connection = DriverManager.getConnection( "jdbc:mysql://localhost:3306/book", "root", "root"); // SQL语句 String sql = "SELECT * FROM book"; // 创建preparedStatement preparedStatement = connection.prepareStatement(sql); // 获取结果集 resultSet = preparedStatement.executeQuery(); // 结果集解析 while (resultSet.next()) { Book book = new Book(); book.setId(resultSet.getInt("id")); book.setName(resultSet.getString("name")); book.setPrice(resultSet.getFloat("price")); book.setDescription(resultSet.getString("description")); list.add(book); } } catch (Exception e) { e.printStackTrace(); } return list; }
}
5.3创建索引管理类IndexManager,并进行索引创建:
public class IndexManager { @Test public void createIndex() throws Exception { // 采集数据 BookDao dao = new BookDao(); List<Book> list = dao.queryBookList(); // 将采集到的数据封装到Document对象中 List<Document> docList = new ArrayList<>(); Document document; for (Book book : list) { document = new Document(); // store:如果是yes,则说明存储到文档域中 // 图书ID //不分词、索引、存储 Field id = new StringField("id", book.getId().toString(), Store.YES); // 图书名称 //分词、索引、存储 Field name = new TextField("name", book.getName(), Store.YES); // 图书价格 //分词、索引、存储 Field price = new FloatField("price", book.getPrice(),Store.YES); // 图书描述 //不分词、索引、不存储 Field description = new TextField("description",book.getDescription(), Store.YES); // 将field域设置到Document对象中 document.add(id); document.add(name); document.add(price); document.add(description); docList.add(document); } // 创建分词器,标准分词器 Analyzer analyzer = new StandardAnalyzer(); // 创建IndexWriter IndexWriterConfig cfg = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer); // 指定索引库的地址 File indexFile = new File("D:/LueneIndex/"); Directory directory = FSDirectory.open(indexFile); IndexWriter writer = new IndexWriter(directory, cfg); // 通过IndexWriter对象将Document写入到索引库中 for (Document doc : docList) { writer.addDocument(doc); } // 关闭writer writer.close(); } }
5.4通过jUnit进行单元测试,测试结果如下:
5.5查看自定义索引库地址,发现有新创建文件:
通过上述步骤,Lucene架构索引即创建成功。
6.创建IndexSearch进行搜索:
public class IndexSearch { @Test public void indexSearch() throws Exception { // 创建query对象 // 使用QueryParser搜索时,需要指定分词器,搜索时的分词器要和索引时的分词器一致 // 第一个参数:默认搜索的域的名称 QueryParser parser = new QueryParser("description",new StandardAnalyzer()); // 通过queryparser来创建query对象 // 参数:输入的lucene的查询语句(关键字一定要大写) Query query = parser.parse("description:java OR lucene"); // 创建IndexSearcher // 指定索引库的地址 File indexFile = new File("D:/LueneIndex/"); Directory directory = FSDirectory.open(indexFile); IndexReader reader = DirectoryReader.open(directory); IndexSearcher searcher = new IndexSearcher(reader); // 通过searcher来搜索索引库 // 第二个参数:指定需要显示的顶部记录的N条 TopDocs topDocs = searcher.search(query, 10); // 根据查询条件匹配出的记录总数 int count = topDocs.totalHits; System.out.println("匹配出的记录总数:" + count); // 根据查询条件匹配出的记录 ScoreDoc[] scoreDocs = topDocs.scoreDocs; for (ScoreDoc scoreDoc : scoreDocs) { // 获取文档的ID int docId = scoreDoc.doc; // 通过ID获取文档 Document doc = searcher.doc(docId); System.out.println("商品ID:" + doc.get("id")); System.out.println("商品名称:" + doc.get("name")); System.out.println("商品价格:" + doc.get("price")); // System.out.println("商品描述:" + doc.get("description")); System.out.println("=========================="); } // 关闭资源 reader.close(); } }
结果如下:
7.进行Lecene索引维护:
7.1.根据id删除索引:
@Test public void deleteIndex() throws Exception { // 创建分词器,标准分词器 Analyzer analyzer = new StandardAnalyzer(); // 创建IndexWriter IndexWriterConfig cfg = new IndexWriterConfig(Version.LUCENE_4_10_3,analyzer); Directory directory = FSDirectory.open(new File("D:/LueneIndex/")); // 创建IndexWriter IndexWriter writer = new IndexWriter(directory, cfg); // Terms writer.deleteDocuments(new Term("id", "1")); // 删除全部 //writer.deleteAll(); writer.close();
}7.2根据name更新索引:
@Test public void updateIndex() throws Exception { // 创建分词器,标准分词器 Analyzer analyzer = new StandardAnalyzer(); // 创建IndexWriter IndexWriterConfig cfg = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer); Directory directory = FSDirectory.open(new File("D:/LueneIndex/")); // 创建IndexWriter IndexWriter writer = new IndexWriter(directory, cfg); // 第一个参数:指定查询条件 // 第二个参数:修改之后的对象 // 修改时如果根据查询条件,可以查询出结果,则将以前的删掉,然后覆盖新的Document对象,如果没有查询出结果,则新增一个Document // 修改流程即:先查询,再删除,在添加 Document doc = new Document(); doc.add(new TextField("name", "Python", Store.YES)); writer.updateDocument(new Term("name", "C"), doc); writer.close(); }
8.Lucene进行条件搜索:
8.1创建QuerySearch类
public class QuerySearch { private void search(Query query) { // 创建QuerySearcher // 指定索引库的地址 try { File indexFile = new File("D:/LueneIndex/"); Directory directory = FSDirectory.open(indexFile); IndexReader reader = DirectoryReader.open(directory); IndexSearcher searcher = new IndexSearcher(reader); // 通过searcher搜索索引库 // 第二个参数:指定需要显示的顶部记录的N条 TopDocs topDocs = searcher.search(query, 10); // 根据查询条件匹配出的记录总数 int count = topDocs.totalHits; System.out.println("匹配出的记录总数:" + count); // 根据查询条件匹配出的记录 //TopDocs.totalHits:是匹配索引库中所有记录的数量 //TopDocs.scoreDocs:匹配相关度高的前边记录数组 ScoreDoc[] scoreDocs = topDocs.scoreDocs; for (ScoreDoc scoreDoc : scoreDocs) { // 获取文档的ID int docId = scoreDoc.doc; Document doc = searcher.doc(docId); System.out.println("商品ID:" + doc.get("id")); System.out.println("商品名称:" + doc.get("name")); System.out.println("商品价格:" + doc.get("price")); // System.out.println("商品描述:" + doc.get("description")); System.out.println("=========================="); } // 关闭资源 reader.close(); } catch (IOException e) { e.printStackTrace(); } } }
8.2通过以下两种方式创建查询对象:
(1)通过Query子类来创建查询对象
Query子类常用的有:TermQuery、NumericRangeQuery、BooleanQuery
不能输入lucene的查询语法,不需要指定分词器
(2)通过QueryParser来创建查询对象(常用)
QueryParser、MultiFieldQueryParser
可以输入lucene的查询语法、可以指定分词器
8.2.1 TermQuery:
// 创建TermQuery对象 @Test public void termQuery() { Query query = new TermQuery(new Term("description", "java")); search(query); }
结果如下:
8.2.2 NumericRangeQuery:
@Test public void numericRangeQuery() { // 创建NumericRangeQuery对象 // 参数:域的名称、最小值、最大值、是否包含最小值、是否包含最大值 Query query = NumericRangeQuery.newFloatRange("price", 55f, 60f, true,false); search(query); }结果如下:
8.2.3 BooleanQuery:
@Test public void booleanQuery() { // 创建BooleanQuery BooleanQuery query = new BooleanQuery(); // 创建TermQuery对象 Query q1 = new TermQuery(new Term("description", "oracle")); // 创建NumericRangeQuery对象 // 参数:域的名称、最小值、最大值、是否包含最小值、是否包含最大值 Query q2 = NumericRangeQuery.newFloatRange("price", 15f, 60f, true,false); // 组合关系代表的意思如下: // 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表示“或”的概念。 // Occur.MUST 查询条件必须满足,相当于and (+) // Occur.SHOULD 查询条件可选,相当于or (空) // Occur.MUST_NOT 查询条件不能满足,相当于not (-) query.add(q1, Occur.MUST); query.add(q2, Occur.MUST); search(query); }
结果如下:
8.2.4 MultiFieldQueryParser:
@Test public void multiFieldQueryParser() throws Exception { // 默认搜索的多个域 String[] fields = { "name", "description" }; Analyzer analyzer = new StandardAnalyzer(); MultiFieldQueryParser parser = new MultiFieldQueryParser(fields,analyzer); Query query = parser.parse("paradigm"); //等同于 // Query query = parser.parse("name:paradigm OR description:paradigm"); search(query); }
结果如下:
9.分词器:
由于Lucene标准分词器不支持汉语分词,所以建议使用第三方分词器。
10.Lucene搜索相关度排序:boost值设置
10.1 创建索引时设置boost值:
10.2 搜索时设置Boost值