Lucene的入门案例

本文介绍了Lucene作为全文检索引擎的基本概念,包括全文检索定义、Lucene的主要流程,并详细阐述了一个入门级的Lucene案例,涉及索引和搜索流程的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

   最近在网上找视频学习了Lucene的相关知识,根据视频内容以及其他资料简单总结下Lucene的相关知识点。

    Lucene是apache软件基金会的一个开放源代码的全文检索引擎工具包,但它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎(英文与德文两种西方语言)。Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。

一、全文检索的定义

    全文检索首先对要搜索的文档进行分词,然后形成索引,通过查询索引来查询文档。通俗点理解就是先创建索引,然后根据索引来搜索的全过程叫做全文检索。

    生活中的字典就是一个很好的全文检索的例子:字典的偏旁部首页,就类似于Lucene的索引。而字典的具体内容就类似于Lucene的文档内容。

二、Lucene全文检索的流程

从图中可以看出来,全文检索主要分为两大块:索引流程和搜索流程。

索引流程采集数据-->文档处理-->存储到索引库中

搜索流程输入查询条件-->通过lucene的查询器查询索引-->从索引库中取出结果-->视图渲染

在这里对几个概念的词汇进行说明:

1、采集数据:全文检索搜索的内容的格式有多种,比如:视频、mp3、图片文档,需要先将它们采集到本地,然后统一封装到lucene的文档对象中,也就是说需要将存储的内容进行统一才能对它进行查询。

采集数据的方式:对于互联网中的数据,使用爬虫工具(http工具)将网页爬取到本地;

              对于数据库中的数据使用jdbc程序进行数据采集;

                对于文件系统的数据,使用io流采集。

2、索引文件的逻辑结构


文档域存储的信息就是采集到的信息通过Document对象来存储具体说是通过Document对象中field域来存储数据

索引域主要是为了搜索使用的。索引域内容是经过lucene分词之后存储的。

3、分词

Lucene中分词主要分为两个步骤:分词和过滤。

分词field域中的内容一个个的分开;

过滤将分好的词进行过滤比如去掉标点符号大写转小写词的型还原(复数转单数、过去式转成现在式)、停用词过滤等;

停用词单独应用没有特殊意义的词比如的英文中的this is a the等;

中文分词需要借助第三方插件:中文分词器:IK-analyzer。

三、入门案例

1、需求说明

使用Lucene完成对数据库中图书信息的索引和搜索功能。

2、环境搭建

JDK:1.7以上版本 、Lucene:4.10、数据库:mysql

3、Lucene工具包下载

官方网站:http://lucene.apache.org/ 

4、数据库脚本初始化(省略)

5、工程搭建
    5.1 导入jar包

MySql驱动包、Analysis包、Core包、QueryParser包、JUnit(测试用)。


    5.2 编写索引流程代码
        5.2.1 创建PO类
package com.zju.lucene.po;

public class Book {
	// 图书ID
	private Integer id;
	// 图书名称
	private String name;
	// 图书价格
	private Float price;
	// 图书图片
	private String pic;
	// 图书描述
	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 getPic() {
		return pic;
	}
	public void setPic(String pic) {
		this.pic = pic;
	}
	public String getDescription() {
		return description;
	}
	public void setDescription(String description) {
		this.description = description;
	}
	
}

        5.2.2 采集数据

package com.zju.lucene.dao;

import java.util.List;
import com.zju.lucene.po.Book;

public interface BookDao {
	//创建查询书的接口
	public List<Book> queryBooks();
}
package com.zju.lucene.dao.impl;

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.zju.lucene.dao.BookDao;
import com.zju.lucene.po.Book;

public class BookDaoImpl implements BookDao {

	@Override
	public List<Book> queryBooks() {
		
		//数据库连接
		Connection con = null;
		//预编译statement
		PreparedStatement pst = null;
		//结果集
		ResultSet rs = null;
		//创建图书列表
		List<Book> list = new ArrayList<Book>();
		
		try{
			//加载数据库驱动
			Class.forName("com.mysql.jdbc.Driver");
			//连接数据库
			con = DriverManager.getConnection("jdbc:mysql://localhost:3306/lucene", "root" , "123456");
			//编写SQL语句
			String sql = "SELECT * FROM book";
			//创建preparedStatement
			pst = con.prepareStatement(sql);
			
			//获取结果集
			rs = pst.executeQuery();
			//结果解析
			while(rs.next()){
				Book book = new Book();
				book.setId(rs.getInt("id"));
				book.setName(rs.getString("name"));
				book.setPrice(rs.getFloat("price"));
				book.setPic(rs.getString("pic"));
				book.setDescription(rs.getString("description"));
				list.add(book);
			}
		}catch(Exception e){
			e.printStackTrace();
		}
			
		return list;
	}
	
}

        5.2.3 创建索引

创建索引的流程:文档(Document)-->分词器(Analyzer)-->索引写对象(IndexWriter)-->索引目录流对象(Directory)-->索引库

IndexWriter是索引过程的核心组件,通过IndexWriter可以创建新索引、更新索引、删除索引操作。IndexWriter需要通过Directory对索引进行存储操作。

package com.zju.lucene.practice;

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.Store;
import org.apache.lucene.document.FloatField;
import org.apache.lucene.document.StoredField;
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.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.junit.Test;

import com.zju.lucene.dao.BookDao;
import com.zju.lucene.dao.impl.BookDaoImpl;
import com.zju.lucene.po.Book;

public class IndexManager {
	@Test
	public void createIndex() throws IOException{
		//采集数据
		BookDao bookDao = new BookDaoImpl();
		List<Book> bookList = bookDao.queryBooks();
		
		//将采集到的数据封装到Document对象中
		List<Document> docList = new ArrayList<>();
		Document document;
		for(Book book : bookList){
			document = new Document();
			//store.YES:存储到文档域中
			/*
			 * Field域的相关说明:
			 * Field域的三个属性:
			 * 1、是否分词(Tokenized):分词的目的就是为了索引;
			 * 2、是否索引(Indexed):索引的目的是为了搜索;
			 * 3、是否存储(Stored):存储的目的是为了搜索页面显示取值用的。
			 * 
			 * Field常用类型:
			 * StringField(FieldName,FieldValue,Store.YES):不分词、索引、存储/不存储;
			 * LongField(FieldName,FieldValue,Store.YES):分词、索引、存储/不存储;
			 * StoredField(FieldName,FieldValue):不分词、不索引、存储;
			 * TextField(FieldName,FieldValue,Store.NO):分词、索引、不存储;
			 * TextField(FieldName,reader):分词、索引、存储;
			 */
			//图书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 pic = new StoredField("pic", book.getPic());
			//图书描述
			Field description = new TextField("description", book.getDescription(), Store.YES);
			
			//将Field域设置到Document对象中
			document.add(id);
			document.add(name);
			document.add(price);
			document.add(pic);
			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:\\lucene\\indexManager\\");
		/*
		 * Directory描述了索引的存储位置,底层封装了I/O操作,负责对索引进行存储。
		 * 它是一个抽象类,它的子类常用的包括FSDirectory(在文件系统存储索引)、RAMDirectory(在内存存储索引)。
		 * FSDirectory比较常用。
		 */
		Directory dir = FSDirectory.open(indexFile);
		IndexWriter writer = new IndexWriter(dir, cfg);
		
		//通过IndexWriter对象将Document写入到索引库中
		for(Document doc : docList){
			writer.addDocument(doc);
		}
		
		//关闭writer
		writer.close();
	}
}

    5.3 编写搜索流程代码

搜索流程示意图:


package com.zju.lucene.practice;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
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.junit.Test;

public class IndexSearch {

	private void doSearch(Query query) {
		// 创建IndexSearcher
		// 指定索引库的地址
		try {
			File indexFile = new File("D:\\lucene\\indexManager\\");
			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("pic"));
			}
			// 关闭资源
			reader.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Test
	public void indexSearch() throws Exception {
		// 创建query对象
		// 使用QueryParser搜索时,需要指定分词器,搜索时的分词器要和索引时的分词器一致
		// 第一个参数:默认搜索的域的名称
		QueryParser parser = new QueryParser("description",
				new StandardAnalyzer());

		// 通过queryparser来创建query对象
		// 参数:输入的lucene的查询语句(关键字一定要大写)
		Query query = parser.parse("description:java AND lucene");

		doSearch(query);

	}

	@Test
	public void termQuery() {
		// 创建TermQuery对象
		Query query = new TermQuery(new Term("description", "java"));

		doSearch(query);
	}

	@Test
	public void numericRangeQuery() {
		// 创建NumericRangeQuery对象
		// 参数:域的名称、最小值、最大值、是否包含最小值、是否包含最大值
		Query query = NumericRangeQuery.newFloatRange("price", 55f, 60f, true, false);

		doSearch(query);
	}

	@Test
	public void booleanQuery() {
		// 创建BooleanQuery
		BooleanQuery query = new BooleanQuery();
		// 创建TermQuery对象
		Query q1 = new TermQuery(new Term("description", "lucene"));
		// 创建NumericRangeQuery对象
		// 参数:域的名称、最小值、最大值、是否包含最小值、是否包含最大值
		Query q2 = NumericRangeQuery.newFloatRange("price", 55f, 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表示“或”的概念。

		query.add(q1, Occur.MUST_NOT);
		query.add(q2, Occur.MUST_NOT);

		doSearch(query);
	}

	@Test
	public void multiFieldQueryParser() throws Exception {
		// 默认搜索的多个域的域名
		String[] fields = { "name", "description" };
		Analyzer analyzer = new StandardAnalyzer();
		Map<String, Float> boosts = new HashMap<String, Float>();
		boosts.put("name", 200f);
		MultiFieldQueryParser parser = new MultiFieldQueryParser(fields, analyzer, boosts);

		// Query query = parser.parse("name:lucene OR description:lucene");
		Query query = parser.parse("java");
		System.out.println(query);

		doSearch(query);
	}
}
补充:使用Luke工具查看索引.......未完待续

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值