【实现平台搜索功能】lucene全文搜索引擎模块的简单使用,内含原理详解

0.Lucene介绍

  • lunece是一项全文检索技术,是apache下的一个开源全文检索引擎工具包,提供了完整的查询引擎和索引引擎,部分文本分析引擎。需要实现全文检索的一般功能的时候是够用的了。

  • 数据分类分为结构化数据和非结构化数据

    • 结构化数据:具有固定格式或者有限长度的数据,如数据库,元数据等
    • 非机构数据:指不定长或者无固定格式的数据,如邮件,word文档等
  • 结构化数据查询

    • 数据库字段存储,使用sql语句查询字段,格式固定
  • 非结构化查询

    • 顺序扫描法

      顺序遍历字符串,一个文档一个文档去看,从头到尾,但是速度很慢

    • 全文检索

      将非结构化的数据中的一部分信息提取出来,重新组织,使得其变得有一定结构,然后对此有一定结构的数据进行检索,从而达到搜索相对较快的目的。这部分从非结构化数据中提取出来然后重新组织的信息我们称为索引

      这种先进行索引,再对索引进行搜索的过程就叫全文索引

      虽然创建索引非常耗时,但是索引一旦创建就可以多次使用,全文检索主要处理的是查询,所以是值得的。

0.1 全文检索的实现

创建索引:
原始文档 -->
创建索引【获得文档->构建文档对象->分析文档(分词)->创建索引】–>
放入索引库(Directory对象指向本地文件夹)–>

查询索引:
用户查询接口–>
创建查询–>
执行查询,在索引库找结果并返回结果–>
渲染结果–>
返回给用户

0.2 lucene8.11.0依赖

<!-- lucene核心库 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<!--<version>4.10.2</version>-->
<version>8.11.0</version>
</dependency>
<!-- Lucene的查询解析器 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>8.11.0</version>
</dependency>
<!-- lucene的默认分词器库 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>8.11.0</version>
</dependency>
<!-- lucene的高亮显示 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-highlighter</artifactId>
<version>8.11.0</version>
</dependency>

1.Lucene工作原理 △

1.1 关于文档

  • 需要创建文档对象
1.获取原始内容的目的就是为了索引,
	在创建索引前需要将原始内容创建成文档Document,
	一个Document可以add多个Field对象,
	每个Field对象就是结构化的数据内容。
	类似某个音频视频文件与它的元数据的关系。

2.每个Document可以有多个Field,
	不同的Document可以有不同的Field对象,
	也可以有完全相同的Field(包括域名和域值都相同)

3.每个Document都会有单独的编号,就是文档ID
  • 分析文档(这个不是必要过程)
将原始内容创建为包含多个Field的Document,
	需要再对Field中的内容进行分析,
	分析的过程是对原始文档进行提词、EN还包括大写转小写(标准化)、
	去空去标点去停词等,形成语汇单元(一个个关键词单词或者说中文)

    比如下边的文档经过分析如下:
    原始文档内容:
    Lunece is a Java full-text search rngine.
    	Lunch is not a comlete application, 
    	but rather a code library and API that can 
    	easily be used to add search capabilities to applications.
    分析后得到的语汇单元:
   		Lunece、Java、full、search、engine 。。。
	分析好后的语汇单元的每个单词叫做一个Term,
		多个terms可以代表一个document,
		不同的域中拆分出来的相同的单词是不同的term,
		term中包含两部分属性,一部分是所属的Document的路径,
		另一部分是单词的具体内容
	比如文件名中包含apache和文件内容中包含apache是不同的term
		但是比如订单编号,身份证号本身就是一个整体,
		作为单独的一个Field的时候不能进行分词,分词了就是去了意义

1.2 关于索引

  • 创建索引
1.对所有文档分析得到的语汇单元进行索引创建,
	索引的目的是为了搜索出被索引的语汇单元中指定的Document

2.创建索引是对语汇单词进行指向而不是对document进行创建索引,
	全文检索是通过词语找文档,而不是对文档内容进行模糊匹配。
	这种索引的结构胶倒排索引结构,因为是根据文本的二次分析内容
	倒回去找文本所在位置。

3.传统方法是顺序扫描法,贼慢的逐字遍历匹配,
	而倒排索引法明显需要匹配的字符更少

4.倒排索引结构也叫反向索引结构,包括索引和文档两部分,
	索引也就是词汇表,索引找语汇单元,语汇单元本身指向某个文档,
	但是索引的词汇表本身规模小,而文档集合较大,所以该方法会更加高效。
  • 查询索引
用户输入searchMsg,根据searchMsg中的关键字进行匹配索引,
根据索引找文档,根据文档返回内容

索引可以认为是词表,term语汇单元可以认为是倒排表

1.3 关于查询

  • 创建查询
要导入用户的关键字到lunece进行索引查询,首先要创建一个查询对象,
查询对象可以指定要查询的field域,规定在所有文档的该域上进行查询。
  • 执行查询
根据查询语法在倒排索引词表中找出用户输入的关键字对应的索引,
从而找到索引所链接的文档列表

先把域选出来,决定在所有文档的哪个域中查询,
然后在所有文档的该域中把关键字进行索引的匹配,
把该索引执行所有的term所指向的文档地址获取出列表,
返回多份文档内容的列表作为返回结果渲染。

索引可以认为是词表,term语汇单元可以认为是倒排表,如图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7ntBzgvI-1637669488059)(C:\Users\86137\AppData\Roaming\Typora\typora-user-images\image-20211120205852331.png)]

2.Lucene初步指导使用

2.1 创建单个索引

@Test
public void testCreate() throws Exception{
    //1 创建文档对象
    Document document = new Document();
    // 创建并添加字段信息。参数:字段的名称、字段的值、是否存储,这里选Store.YES代表存储到文档列表。Store.NO代表不存储
    document.add(new StringField("id", "1", Field.Store.YES));
    // 这里我们title字段需要用TextField,即创建索引又会被分词。StringField会创建索引,但是不会被分词
    document.add(new TextField("title", "谷歌地图之父跳槽facebook", Field.Store.YES));

    //2 索引目录类,指定索引在硬盘中的位置
    Directory directory = FSDirectory.open(new File("d:\\indexDir"));
    //3 创建分词器对象
    Analyzer analyzer = new StandardAnalyzer();
    //4 索引写出工具的配置对象
    IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, analyzer);
    //5 创建索引的写出工具类。参数:索引的目录和配置信息
    IndexWriter indexWriter = new IndexWriter(directory, conf);

    //6 把文档交给IndexWriter
    indexWriter.addDocument(document);
    //7 提交
    indexWriter.commit();
    //8 关闭
    indexWriter.close();
}

2.2 创建多个索引并设置多个字段

@Test
public void testCreate2() throws Exception{
    // 创建文档的集合
    Collection<Document> docs = new ArrayList<>();
    // 创建文档对象
    Document document1 = new Document();
    document1.add(new StringField("id", "1", Field.Store.YES));
    document1.add(new TextField("title", "谷歌地图之父跳槽facebook", Field.Store.YES));
    docs.add(document1);
    // 创建文档对象
    Document document2 = new Document();
    document2.add(new StringField("id", "2", Field.Store.YES));
    document2.add(new TextField("title", "谷歌地图之父加盟FaceBook", Field.Store.YES));
    docs.add(document2);
    // 创建文档对象
    Document document3 = new Document();
    document3.add(new StringField("id", "3", Field.Store.YES));
    document3.add(new TextField("title", "谷歌地图创始人拉斯离开谷歌加盟Facebook", Field.Store.YES));
    docs.add(document3);
    // 创建文档对象
    Document document4 = new Document();
    document4.add(new StringField("id", "4", Field.Store.YES));
    document4.add(new TextField("title", "谷歌地图之父跳槽Facebook与Wave项目取消有关", Field.Store.YES));
    docs.add(document4);
    // 创建文档对象
    Document document5 = new Document();
    document5.add(new StringField("id", "5", Field.Store.YES));
    document5.add(new TextField("title", "谷歌地图之父拉斯加盟社交网站Facebook", Field.Store.YES));
    docs.add(document5);

    // 索引目录类,指定索引在硬盘中的位置
    Directory directory = FSDirectory.open(new File("d:\\indexDir"));
    // 引入IK分词器
    Analyzer analyzer = new IKAnalyzer();
    // 索引写出工具的配置对象
    IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, analyzer);
    // 设置打开方式:OpenMode.APPEND 会在索引库的基础上追加新索引。OpenMode.CREATE会先清空原来数据,再提交新的索引
    conf.setOpenMode(IndexWriterConfig.OpenMode.CREATE);

    // 创建索引的写出工具类。参数:索引的目录和配置信息
    IndexWriter indexWriter = new IndexWriter(directory, conf);
    // 把文档集合交给IndexWriter
    indexWriter.addDocuments(docs);
    // 提交
    indexWriter.commit();
    // 关闭
    indexWriter.close();
}

2.3 查询索引数据

@Test
public void search() throws Exception {
    // 索引目录对象
    Directory directory = FSDirectory.open(new File("d:\\indexDir"));
    // 索引读取工具
    IndexReader reader = DirectoryReader.open(directory);
    // 索引搜索工具
    IndexSearcher searcher = new IndexSearcher(reader);

    // 创建查询解析器,两个参数:默认要查询的字段的名称,分词器
    QueryParser parser = new QueryParser("title", new StandardAnalyzer());
    // 创建查询对象
    Query query = parser.parse("谷歌");

    // 搜索数据,两个参数:查询条件对象要查询的最大结果条数
    // 返回的结果是 按照匹配度排名得分前N名的文档信息(包含查询到的总条数信息、所有符合条件的文档的编号信息)。
    TopDocs topDocs = searcher.search(query, 10);
    // 获取总条数
    System.out.println("本次搜索共找到" + topDocs.totalHits + "条数据");
    // 获取得分文档对象(ScoreDoc)数组.SocreDoc中包含:文档的编号、文档的得分
    ScoreDoc[] scoreDocs = topDocs.scoreDocs;
    for (ScoreDoc scoreDoc : scoreDocs) {
        // 取出文档编号
        int docID = scoreDoc.doc;
        // 根据编号去找文档
        Document doc = reader.document(docID);
        System.out.println("id: " + doc.get("id"));
        System.out.println("title: " + doc.get("title"));
        // 取出文档得分
        System.out.println("得分: " + scoreDoc.score);
    }
}

2.3.1 正常词条查询

Query query = new TermQuery(new Term("title", "谷歌地图"));
search(query);

2.3.2 通配符查询【模糊查询】

Query query = new WildcardQuery(new Term("title", "*歌*"));
search(query);

2.3.3 数值范围查询

Query query = NumericRangeQuery.newLongRange("id", 2L, 2L, true, true);
search(query);

2.3.4 组合查询

Query query1 = NumericRangeQuery.newLongRange("id", 1L, 3L, true, true);
Query query2 = NumericRangeQuery.newLongRange("id", 2L, 4L, true, true);
// 创建布尔查询的对象
BooleanQuery query = new BooleanQuery();
// 组合其它查询
query.add(query1, BooleanClause.Occur.MUST_NOT);
query.add(query2, BooleanClause.Occur.SHOULD);

search(query);

2.4 修改索引

// 创建目录对象
Directory directory = FSDirectory.open(new File("indexDir"));
// 创建配置对象
IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, new IKAnalyzer());
// 创建索引写出工具
IndexWriter writer = new IndexWriter(directory, conf);

// 创建新的文档数据
Document doc = new Document();
doc.add(new StringField("id","1",Store.YES));
doc.add(new TextField("title","谷歌地图之父跳槽facebook ",Store.YES));
/* 修改索引。参数:
* 	词条:根据这个词条匹配到的所有文档都会被修改
* 	文档信息:要修改的新的文档数据
*/
writer.updateDocument(new Term("id","1"), doc);
// 提交
writer.commit();
// 关闭
writer.close();

2.5 删除索引

// 创建目录对象
Directory directory = FSDirectory.open(new File("indexDir"));
// 创建配置对象
IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, new IKAnalyzer());
// 创建索引写出工具
IndexWriter writer = new IndexWriter(directory, conf);

// 根据词条进行删除
//		writer.deleteDocuments(new Term("id", "1"));

// 根据query对象删除,如果ID是数值类型,那么我们可以用数值范围查询锁定一个具体的ID
//		Query query = NumericRangeQuery.newLongRange("id", 2L, 2L, true, true);
//		writer.deleteDocuments(query);

// 删除所有
writer.deleteAll();
// 提交
writer.commit();
// 关闭
writer.close();

3.Lucene工作步骤详解

参考资料:
(17条消息) Lunece介绍和简单实用_heshendian的博客-CSDN博客

创建索引实现:
2、创建一个indexWriter对象。
​ 指定索引库的存放位置Directory对象(FSDirectory.open 来获取)
​ 指定一个IndexWriterConfig对象(IndexWriterConfig需要analyzer对象)。
3、创建document对象。
4、创建field对象,将field对象添加到document对象中。
5、使用indexWriter对象将document对象写入索引库,此过程进行索引创建,并将索引和docuemnt对象写入索引库
6、关闭indexWriter对象。

查询索引实现:
1.创建一个Directory对象,也就是索引库存的位置。
2.创建一个IndexReader对象,需要指定Directory对象。
3.创建一个indexSearcher对象,需要指定IndexReader对象。
4.创建一个TermQuery对象,指定查询的域和查询的关键词。
5.执行查询
6.返回查询结果,遍历查询结果并输出
7.关闭indexReader对象

3.3 Document和Field

  • Document
Document document = new Document();
document.add(new StringField("gid",gid, Field.Store.YES));
document.add(new TextField("ginfo",getInfo,Field.Store.YES));
  • Field

    Document是Field的承载体,一个Document由多个Field组成
    Field由名称和值两部分组成,Field的值是要索引的内容,即目标
    还需要对Field进行提词形成倒排表,然后对倒排表建立索引(词表)
    形成一一对应,索引--语汇单元列表--多个Document--返回文本结果
    
    但不是所有的Field都需要进行提词term的,比如身份证号,编号等
    不分词的部分需要全文完美匹配,并且索引和term是一致的,且是一对一关系
    
    • 对Field会有哪些处理呢?
    // 以下三种不是必要选项,不同情况选择不同
    1.分词tokenized
    	分词:分词目的是为了索引,比如商品名称,商品描述这些,用户输入的关键字可能不全,匹配语汇单元会更加符合实际。
    	不分词:如订单编号,身份证号是一个整体,分词就失去了意义
    	
    2.索引indexed
    	索引:用户可能输入的关键词等涉及到的Field都需要建立索引
    	不索引:如商品图片路径,不会作为查询条件,不需要建立
    
    3.存储stored
    	存储:比如商品名称,商品价格,凡是将来需要在结果列表中展现给用户的内容,都要在这个时候存储,提前返回不依赖数据库
    	不存储:比如商品描述,内容多,格式大,不需要直接在搜索结果展示,不做存储,用户点击结果进入详情页面的时候再去关系数据进行索要
    
    • 有哪些Field可以用呢?
    Field类型数据类型是否分词是否索引是否存储说明
    StringField(FieldName, FieldValue, Store.YES)字符串NYY/N字符串类型Field, 不分词, 作为一个整体进行索引 (如: 身份证号, 订单编号), 是否需要存储由Store.YES或Store.NO决定
    FloatPoint(FieldName,FieldValue)floatYYN构建一个Float数字类型Field进行分词和索引,不存储,(价格)
    DoublePoint(FieldName,FieldValue)doubleYYN构建一个double数字类型Field进行分词和索引,不存储,(价格)
    IntPoint(FieldName,FieldValue)integer型YYN构建一个double数字类型Field进行分词和索引,不存储,(价格)
    LongPoint(FieldName, FieldValue)数值型代表YYNLong数值型Field代表, 分词并且索引(如: 价格,身份证), 是否需要存储由Store.YES或Store.NO决定
    StoredField(FieldName, FieldValue)重载方法, 支持多种类型NNY构建不同类型的Field, 不分词, 不索引, 要存储. (如: 商品图片路径)
    TextField(FieldName, FieldValue, Store.NO)文本类型YYY/N文本类型Field, 分词并且索引, 是否需要存储由Store.YES或Store.NO决定

3.4 Directory

  • 指定存放/读取索引库(词表)的目录位置
// 需要使用java6之后的nio中的Path指定路径,而不是new File("xxx")
Path path = Paths.get("D:\\indexdir");
// FSDirectory.open()方法会判断我们导入的path可能会出现的问题,正确处理并获取该path所指定的目录
Directory directory = FSDirectory.open(path);

3.5 Analyzer

3.5.1 StanderAnalyzer(Lucene)

  • 单字分词:就是按照中文一个字一个字地进行分词。如:“我爱中国”

    效果:“我”、“爱“、”中“、”国”

3.5.2 SmartChineseAnalzer

  • 先分句,再分词
  • 对中文支持较好,但是扩展性差,扩展词库,禁用词库和同义词库等不好处理
需要加依赖

<dependency>
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-analyzers-smartcn</artifactId>
    <version>7.3.0</version>
</dependency>

3.5.3 IKAnalyzer

参考资料:
(17条消息) 大数据工具:IKAnalyzer分词工具介绍与使用_maoyuanming0806的博客-CSDN博客_ikanalyzer

IK分词器介绍

为什么要分词?该工具在大数据处理中提取语句特征值进行向量计算中十分有用,并且是开源项目,能够实现基本的分词需求

IK是基于java开发的轻量级中文分词工具包,以开源项目lucene为主体,结合词典分词和文法分析算法的中文分词组件。在2012版本中就实现了简单的分词歧义排除算法

优势:
1.IK采用特有的“正向迭代最细粒度切分算法”,支持细粒度和智能分词两种切分模式
2.处理能力高速
3.采用多子处理器分析模式,支持英文 字母 数字 中文
4.优化词典存储,更小内存占用

重要词典:
1.扩展词典:
	扩展词典:为的是让需要切分的字符串的词语 根据扩展词典里的词,不要切分开来。
例如:扩展词典中有:中国的台湾 。那么原本会切分成:中国 的 台湾 在 东海 。会切分成:中国的台湾 在 东海
2.停止词典:
	对比停止词典,直接删掉停止词典中出现的词语

使用方法:

  • 1、添加相应的jar或者pom引用依赖
<!-- https://mvnrepository.com/artifact/com.janeluo/ikanalyzer -->
<dependency>
    <groupId>com.janeluo</groupId>
    <artifactId>ikanalyzer</artifactId>
    <version>2012_u6</version>
</dependency>

# 只有2012版本的
  • 2、把配置文件和扩展词典和停用词词典添加到classpath下:

注意:hotword.dic和ext——stopword.dic文件格式为UTF-8,注意是无BOM的UTF-8编码。

也就是说禁止使用windows记事本编辑扩展词典文件

  • 3、使用
    IKAnalyzer analyzer = new IKAnalyzer();
    //使用智能分词
    //ik2012和ik3.0,3.0没有这个方法
    //analyzer.setUseSmart(true);


    try {
        return printAnalyzerResult(analyzer, line);
    } catch (IOException e) {
    e.printStackTrace();
    }

        return null;
    }

public static String printAnalyzerResult(Analyzer analyzer, String keyword) throws IOException {
    String resultData = "";
    String infoData = "";

    TokenStream tokenStream = analyzer.tokenStream("content",new StringReader(keyword));
    tokenStream.addAttribute(CharTermAttribute.class);
    while(tokenStream.incrementToken()){
        CharTermAttribute charTermAttribute = tokenStream.getAttribute(CharTermAttribute.class);
        infoData = infoData+ "    "+charTermAttribute.toString();

        }
        if(!"".equals(infoData)){
            resultData = resultData + infoData.trim()+"\r\n";
        }else{
            resultData = "";
        }
    return resultData;
    }

public static void main(String[] args) {
	String line = "这是一个粗糙的栅栏,浪费钱,我想要一堵巨大的墙!”网友Mary说,还附上了“理想”中的边境墙照片";
	String s = IKAnalyzerTest.beginAnalyzer(line);
	System.out.println(s);

这是一个    这是    粗糙    栅栏    浪费    费钱    我    想要    一堵    巨大的墙    巨大    网友    mary    说    还    附上    上了    理想    中    边境    墙    照片

3.6 IndexWriter和IndexReader

  • IndexWriter
Analyzer analyzer = new StandardAnalyzer();
IndexWriterConfig conf = new IndexWriterConfig(analyzer);
IndexWriter indexWriter = new IndexWriter(directory,conf);
indexWriter.addDocument(document);
【indexWriter.deleteAll();//删除所有索引】
【indexWriter.deleteDocuments(new TermQuery(new Term("name","张三")))//删除指定索引】
Document document = new Document();
document.add(new TextField("filename","要更新的文档",Field.Store.YES))
【indexWriter.updateDocument(new Term("name","zs"),document)】
indexWriter.commit();
indexWriter.close();
  • IndexReader
Directory directory =FSDirectory.open(Paths.get("path"));
//* 创建一个IndexReader对象,需要指定Directory对象。
IndexReader indexReader=DirectoryReader.open(directory);
//* 创建一个indexSearcher对象,需要指定IndexReader对象。
IndexSearcher indexSearcher =new IndexSearcher(indexReader);
//* 创建一个TermQuery对象,指定查询的域和查询的关键词。
indexReader.close();

3.7 Query

3.6.1 queryParser

  • 需要使用analyzer分析器,查询的索引对应的是多个语汇单元
// 创建查询
QueryParser queryParser=new QueryParser("name",new IKAnalyzer());
Query  query= queryParser.parse("张三");
IndexSearcher indexSearcher=new IndexSearcher(DirectoryReader.open(FSDirectory.open(Paths.get(CreateIndexes.path))));
TopDocs topDocs = indexSearcher.search(query, 10);
//共查询到的document个数
System.out.println("查询结果总数量:" + topDocs.totalHits);
//遍历查询结果
for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
    Document document = indexSearcher.doc(scoreDoc.doc);
    System.out.println(document.get("filename"));
    //System.out.println(document.get("content"));
    System.out.println(document.get("path"));
    System.out.println(document.get("size"));
}
//关闭indexreader
indexSearcher.getIndexReader().close();

3.6.2 TermQuery

  • 通过项单元查询,TermQuery不适用分析器,所以建议匹配不分词的Field域查询,比如订单号,价格等。查询的索引对应的
Directory directory =FSDirectory.open(Paths.get("path"));
//* 创建一个IndexReader对象,需要指定Directory对象。
IndexReader indexReader=DirectoryReader.open(directory);
//* 创建一个indexSearcher对象,需要指定IndexReader对象。
IndexSearcher indexSearcher =new IndexSearcher(indexReader);
//* 创建一个TermQuery对象,指定查询的域和查询的关键词。
Query query = new TermQuery(new Term("name","张"));
//* 执行查询
TopDocs topDocs = indexSearcher.search(query, 10);
System.out.println("查询结果的总条数:"+topDocs.totalHits);
//* 返回查询结果,遍历查询结果并输出
for(ScoreDoc scoreDoc:topDocs.scoreDocs){
//scoreDoc.doc属性就是document对象的id
//根据document的id找到document对象
Document document = indexSearcher.doc(scoreDoc.doc);
System.out.println(document.get("id"));
//System.out.println(document.get("content"));
System.out.println(document.get("name"));
System.out.println(document.get("address"));
System.out.println(document.get("hobby"));
System.out.println("-------------------------");
}
//* 关闭indexReader对象
indexReader.close();

4.已成功实现功能的平台搜索引擎代码

package com.sysugoods.util;

import com.sysugoods.domain.Good;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.*;
import org.apache.lucene.index.*;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.*;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;

import java.nio.file.*;
import java.util.ArrayList;
import java.util.List;

public class LuceneUtil {
	// 创建索引的时机应该在每次把商品数据插入到数据库的时候
    public static void createIndex(Good good){
    	// 先创建一个文档对象用来放good的属性
        Document document = new Document();
        // 对good的属性分别导入新文档中的域field中,形成一个good文档
        // 对于比较短的信息比如gid,gprice这些不进行分词的我们是有必要进行存储用于查询的,
        // 但是对于gname,gintroduce这些长文本我们需要用分词器进行分词的不需要存储
        // Field.Store.No表示不存储该数据到文本中,YES是存储
        document.add(new StringField("gid",String.valueOf(good.getGid()), Field.Store.YES));
        document.add(new TextField("gname",good.getGname(), Field.Store.NO));
        document.add(new StringField("gprice",good.getGprice(), Field.Store.YES));
        document.add(new StringField("uname",good.getUname(), Field.Store.YES));
        document.add(new StringField("gstatus",good.getGstatus(), Field.Store.YES));
        document.add(new StoredField("gpath",good.getGpaths()));
        document.add(new TextField("gintroduce",good.getGintroduce(),Field.Store.NO));
		
		// 下方操作都用try catch去处理异常
		// 其中Paths.get("url")是java的nio【new io】的方法
		// 旧的io用法是new File("url")
		// 但是lucene的新版本要求使用nio
        try{
            Path path = Paths.get("D:\\indexdir");
            Directory directory = FSDirectory.open(path);
            // 创建一个标准分词器用来分词
            Analyzer analyzer = new StandardAnalyzer();
            // 创建一个索引写入工具用于写入索引
            // 分词器只会用在TextField,不会用在StringField
            IndexWriterConfig conf = new IndexWriterConfig(analyzer);
            IndexWriter indexWriter = new IndexWriter(directory,conf);
            indexWriter.addDocument(document);
            indexWriter.commit();
            indexWriter.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    public void modifyIndex(){

    }
    public void deleteIndex(){

    }
	
	// 索引文档,一般是在用户输入关键字后返回数据的时候调用
    public static List<Document> queryIndex(String searchMsg){
        List<Document> result = new ArrayList();
        Document doc = null;
        try{
        	// 打开文件流导入数据用于indexreader读取
            Path path = Paths.get("D:\\indexdir");
            Directory directory = FSDirectory.open(path);
            IndexReader reader = DirectoryReader.open(directory);
            IndexSearcher searcher = new IndexSearcher(reader);
            Analyzer analyzer = new SmartChineseAnalyzer();
            QueryParser parser2 = new QueryParser("gname",analyzer);
            QueryParser parser3 = new QueryParser("gintroduce",analyzer);

            TermQuery query1 = new TermQuery(new Term("uname",searchMsg));
            Query query2 = parser2.parse(searchMsg);
            Query query3 = parser3.parse(searchMsg);
            TopDocs topDocs1 = searcher.search(query1,150);
            TopDocs topDocs2 = searcher.search(query2,150);
            TopDocs topDocs3 = searcher.search(query3,150);
            TotalHits result_num1 = topDocs1.totalHits;
            TotalHits result_num2 = topDocs2.totalHits;
            TotalHits result_num3 = topDocs3.totalHits;
            System.out.println("uname找到一共" +result_num1.value);
            System.out.println("gname找到一共"+result_num2.value);
            System.out.println("gintroduce找到一共"+result_num3.value);
            if(result_num1.value!=0){
                ScoreDoc[] scoreDocs = topDocs1.scoreDocs;
                for(ScoreDoc scoreDoc : scoreDocs){
                    int docId = scoreDoc.doc;
                    doc = reader.document(docId);
                    if(doc != null){
                        //换gid
                        result.add(doc);
                    }
                }
            }
            if(result_num2.value!=0){
                ScoreDoc[] scoreDocs = topDocs2.scoreDocs;
                for(ScoreDoc scoreDoc : scoreDocs){
                    int docId = scoreDoc.doc;
                    doc = reader.document(docId);
                    if(doc != null){
                        //换gid
                        result.add(doc);
                    }
                }
            }
            if(result_num3.value!=0){
                ScoreDoc[] scoreDocs = topDocs3.scoreDocs;
                for(ScoreDoc scoreDoc : scoreDocs){
                    int docId = scoreDoc.doc;
                    doc = reader.document(docId);
                    if(doc != null){
                        //换gid
                        result.add(doc);
                    }
                }
            }
            System.out.println("共找到"+result.size());
            reader.close();

        }catch (Exception e){
            e.printStackTrace();
        }
//        int result_num = topDocs.totalHits;
//        System.out.println(result_num);

        return result;
    }
}

该模块的个人学习完毕,已成功实现所需功能
效果图测试如下图所示:
【前端是vuecli脚手架随便写的,后端用ssm框架对接】
在这里插入图片描述
也就是输入你好搜索,下面展示的商品的商品名或者商品描述会和“你好”相关,并且你好两个字不分顺序。
【所用的数据是通过mockjs配合javascript脚本进行模拟的,有需要可以看我的mockjs的个人学习总结】

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

玖等了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值