一、什么是Lucene
首先介绍下全文检索。全文检索是指计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。
Lucene是一套用于全文检索和搜寻的开源程式库,由Apache软件基金会支持和提供。Lucene提供了一个简单却强大的应用程式接口,能够做全文索引和搜寻。
二、Lucene相关概念
1. 倒排索引
查询前会先将查询的内容提取出来组成文档(正文), 对文档进行切分词组成索引(目录), 索引和文档有关联关系,查询的时候先查询索引,通过索引找文档。被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射,Lucene会对文档进行倒排索引。
2. Lucene部分索引结构
①索引
一个目录一个索引,在Lucene中一个索引是放在一个文件夹中的。Lucene提取文档中关键信息,建立索引,搜索时根据索引找到文档的位置。
②文档(document)
文档是我们建索引的基本单位,将从互联网上、数据库和文件系统获取的原始数据转化为文档。
采集原始数据的方法:
(1)对于互联网上的网页,可以使用工具将网页抓取到本地生成html文件。
(2)数据库中的数据,可以直接连接数据库读取表中的数据。
(3)文件系统中的某个文件,可以通过I/O操作读取文件的内容。
③域(field)
一篇文档包含不同类型的信息,可以分开索引,比如标题,时间,正文,作者等,都可以保存在不同的域里。
3. 分词器
分词器(Analyzer)的作用是把一段文本中的词按规则取出所包含的所有词,对应的是Analyzer类。在创建索引的时候需要用到分词器,在使用字符串搜索的时候也会用到分词器,并且这两个地方要使用同一个分词器,否则可能会搜索不出来结果。
三、Lucene全文检索的流程
1. 创建索引
①将数据转化为Lucene文档
将原始数据创建为文档(document),文档中包含一个个域(field),域中存储内容。
// Document对象 Document doc = null; for (NewBeeMallGoods good : goods) { // 创建Document对象,同时要创建field对象 doc = new Document(); // 根据需求创建不同的Field Field id = new StringField("goods_id", good.getGoodsId().toString(),Field.Store.YES); Field name = new TextField("goods_name", good.getGoodsName(), Field.Store.YES); Field intro = new TextField("goods_intro", good.getGoodsIntro(), Field.Store.NO); Field img = new StoredField("goods_cover_img", good.getGoodsCoverImg()); Field price = new TextField("selling_price", good.getSellingPrice().toString(), Field.Store.YES); // 把域(Field)添加到文档(Document)中 doc.add(id); doc.add(name); doc.add(intro); doc.add(img); doc.add(price); docList.add(doc); }
②将Lucene文档传给分词器
在对Document中的内容进行索引之前,需要使用分词器进行分词,分词的目的是为了搜索。分词的主要过程就是先分词后过滤。
分词:采集到的数据会存储到document对象的Field域中,分词就是将Document中Field的value值切分成一个一个的词。
过滤:包括去除标点符号过滤、去除停用词过滤(的、是、a、an、the等)、大写转小写、词的形还原(复数形式转成单数形参、过去式转成现在式)等。
// 创建索引 // 索引目录类,指定索引在硬盘中的位置,我的设置为E盘的indexDir文件夹 Directory directory = FSDirectory.open(FileSystems.getDefault().getPath( Objects.requireNonNull(config.getProperty("lucene.index.path")) )); // 引入IK分词器 Analyzer analyzer = new MyIKAnalyzer(); // 索引写出工具的配置对象,这个地方就是最上面报错的问题解决方案 IndexWriterConfig conf = new IndexWriterConfig(analyzer); // 设置打开方式:OpenMode.APPEND 会在索引库的基础上追加新索引。OpenMode.CREATE会先清空原来数据,再提交新的索引 conf.setOpenMode(IndexWriterConfig.OpenMode.CREATE); // 创建索引的写出工具类。参数:索引的目录和配置信息 IndexWriter indexWriter = new IndexWriter(directory, conf); // 把文档集合交给IndexWriter indexWriter.addDocuments(luceneGoodsService.getDocuments(luceneGoodsService.getAllGoods())); // 提交 indexWriter.commit(); // 关闭 indexWriter.close();
2. 搜索索引
①输入查询内容
从用户搜索条中获取关键字,或从分类选项中获取分类id进行查询。
②创建搜索对象并搜索索引
第一步,创建Query搜索对象
第二步,创建Directory流对象,声明索引库位置
第三步,创建索引读取对象IndexReader
第四步,创建索引搜索对象
第五步,使用索引搜索对象执行搜索,返回结果集
LuceneUtil luceneUtil = new LuceneUtil(); Directory directory1=FSDirectory.open(FileSystems.getDefault().getPath(luceneUtil.getLucenePath())); // 索引读取工具 IndexReader reader = DirectoryReader.open(directory1); // 索引搜索工具 IndexSearcher searcher=new IndexSearcher(reader); // 创建查询解析器,两个参数:默认要查询的字段的名称,分词器 String[] fields = {"goods_name", "goods_intro"}; MultiFieldQueryParser parser= new MultiFieldQueryParser(fields,new MyIKAnalyzer()); // 创建查询对象 Query query=parser.parse(keyword); // 获取前n条记录 TopDocs topDocs=searcher.search(query,500);
③解析结果集
获取文档,根据文档编号寻找每一个文档,并将文档中field内容赋值给相应商品属性,将每一个商品都加入商品列表中。
List<NewBeeMallGoods>list = new ArrayList<>(); for(ScoreDoc scoreDoc:scoreDocs){ // 取出文档编号 int docID=scoreDoc.doc; // 根据编号去找文档 Document doc=reader.document(docID); //ltd.newbee.mall.api.random.Content content = contentMapper.selectByPrimaryKey(doc.get("id")); NewBeeMallGoods good=new NewBeeMallGoods(); good.setGoodsId(Long.parseLong(doc.get("goods_id"))); good.setGoodsName(doc.get("goods_name")); good.setGoodsIntro(doc.get("goods_intro")); good.setGoodsCoverImg(doc.get("goods_cover_img")); good.setSellingPrice(Integer.valueOf(doc.get("selling_price"))); list.add(good); }