Skip to content
鼓励作者:欢迎打赏犒劳

01-Lucene入门案例

POM依赖

java
<dependency>
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-core</artifactId>
    <version>7.7.2</version>
</dependency>
<dependency>
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-queryparser</artifactId>
    <version>7.7.2</version>
</dependency>
<dependency>
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-analyzers-common</artifactId>
    <version>7.7.2</version>
</dependency>

<!-- IK分词 -->
<dependency>
    <groupId>com.jianggujin</groupId>
    <artifactId>IKAnalyzer-lucene</artifactId>
    <version>7.0.0</version>
</dependency>

<!-- lucene的高亮显示 -->
<dependency>
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-highlighter</artifactId>
    <version>7.7.2</version>
</dependency>

常用实例

前置准备

java
import lombok.Data;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.DoublePoint;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.LongPoint;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.document.StringField;
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;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.PrefixQuery;
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.Before;
import org.junit.Test;
import org.wltea.analyzer.lucene.IKAnalyzer;

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class LuceneFirst {

    String indexDir = "D:\\temp\\index";

    List<Article> articleList = new ArrayList<Article>();

    @Before
    public void init(){
        Article article1 = new Article();
        article1.setId("1");
        article1.setTitle("平凡的世界");
        article1.setAuthor("路遥");
        article1.setPrice(new BigDecimal("12.4"));
        article1.setWordNumber(12000);
        article1.setCreateTime("2024-01-03 12:22:20");
        article1.setContent("《平凡的世界》是中国作家路遥创作的一部全景式地表现中国当代城乡社会生活的百万字长篇小说。");

        Article article2 = new Article();
        article2.setId("2");
        article2.setTitle("三国演义");
        article2.setAuthor("罗贯中");
        article2.setPrice(new BigDecimal("10"));
        article2.setWordNumber(8000);
        article2.setCreateTime("2024-03-03 12:22:20");
        article2.setContent("东汉末年,山河动荡,刘汉王朝气数将尽。内有十常侍颠倒黑白,祸乱朝纲。");

        Article article3 = new Article();
        article3.setId("3");
        article3.setTitle("西游记");
        article3.setAuthor("吴承恩");
        article3.setPrice(new BigDecimal("9"));
        article3.setWordNumber(14000);
        article3.setCreateTime("2024-04-03 12:22:20");
        article3.setContent("《西游记》是中国古代第一部浪漫主义章回体长篇神魔小说。");

        articleList = Arrays.asList(article1, article2, article3);
    }
    private void print(IndexSearcher indexSearcher,TopDocs topDocs) throws IOException {
        System.out.println("查询出来的总记录数:" + topDocs.totalHits);

        // 6. 第六步:返回查询结果。遍历查询结果并输出。
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取文档id
            int docId = scoreDoc.doc;
            // 根据id取文档对象
            Document document = indexSearcher.doc(docId);
            // 取文档的属性
            System.out.println("name: " + document.get("title"));
            System.out.println("content: " + document.get("content"));
            System.out.println("-------------分割线-----------------");
        }
    }

    private IndexReader getIndexReader() throws Exception {
        // 1. 第一步:创建一个Directory对象,也就是索引库存放的位置。
        Directory directory = FSDirectory.open(new File(indexDir).toPath());
        // 2. 第二步:创建一个indexReader对象,需要指定Directory对象。
        IndexReader indexReader = DirectoryReader.open(directory);
        return indexReader;
    }
    private IndexWriter getIndexWriter() throws Exception {
        // 创建IndexWriter配置
        IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer());
        // 创建IndexWriter
        IndexWriter writer = new IndexWriter(FSDirectory.open(Paths.get(indexDir)), config);
        return writer;
    }


    @Data
    public static class Article {
        //ID
        private String id;
        //标题
        private String title;
        //作者
        private String author;
        //创建时间
        private String createTime;
        //价格
        private BigDecimal price;
        //字数
        private Integer wordNumber;
        //内容
        private String content;
    }
}

创建索引

java
/**
 * 写入索引
 * @throws Exception
 */
@Test
public void createIndex() throws Exception {
    //  2. 基于Directory对象创建一个IndexWriter对象(用来写索引)
    IndexWriter indexWriter = getIndexWriter();
    //  3. 创建一个文档对象。
    for(Article article : articleList) {
        // 4. 创建Field
        // 参数1:域的名称, 参数2:域的内容, 参数3:是否存储
        Field id = new StringField("id", article.getId(), Field.Store.YES);
        Field title = new TextField("title", article.getTitle(), Field.Store.YES);
        Field author = new StringField("author", article.getAuthor(), Field.Store.YES);
        Field content = new TextField("content",  article.getContent(), Field.Store.YES);
        Field wordNumber = new LongPoint("wordNumber", article.getWordNumber());
        Field wordNumberStore = new StoredField("wordNumber", String.valueOf(wordNumber));
        Field price = new DoublePoint("price", article.getPrice().doubleValue());
        Field priceStore = new StoredField("price", String.valueOf(price));

        // 5. 将field添加到document对象中。
        Document document = new Document();
        document.add(id);
        document.add(title);
        document.add(author);
        document.add(content);
        document.add(wordNumber);
        document.add(price);
        document.add(wordNumberStore);
        document.add(priceStore);

        //  6. 使用IndexWriter对象将document对象写入索引库,此过程进行索引创建,并将索引和document对象写入索引库。
        indexWriter.addDocument(document);
    }
    //  7. 关闭IndexWriter对象。
    indexWriter.close();
}

删除索引

java
/**
 * 删除索引
 * @throws Exception
*/
@Test
public void deleteIndex() throws Exception {
    IndexWriter indexWriter = getIndexWriter();
    // 删除整个索引
    indexWriter.deleteAll();
    // 提交更改
    indexWriter.commit();
    // 关闭IndexWriter
    indexWriter.close();
}

根据id查询

java
@Test
public void selectById() throws Exception{
    IndexReader indexReader = getIndexReader();
    IndexSearcher indexSearcher = new IndexSearcher(indexReader);
    Query query = new TermQuery(new Term("id", "1"));
    TopDocs topDocs = indexSearcher.search(query,10);
    print(indexSearcher,topDocs);
}

根据id更新

java
@Test
public void updateById() throws Exception{
    IndexWriter indexWriter = getIndexWriter();
    Document document = new Document();
    document.add(new StringField("id","1", Field.Store.YES));
    document.add(new StringField("title","平凡的世界111", Field.Store.YES));
    indexWriter.updateDocument(new Term("id","1"),document);
    indexWriter.commit();
}

单字段查询

java
/**
 * 根据1个字段查询索引   注意:title是分词的
 *  select * from table where title = "xx"
 * @throws Exception
*/
@Test
public void searchIndex1() throws Exception {
    IndexReader indexReader = getIndexReader();
    // 3. 第三步:创建一个indexsearcher对象,需要指定IndexReader对象
    IndexSearcher indexSearcher = new IndexSearcher(indexReader);

    // 4. 第四步:指定查询的域和查询的关键词。
    //创建搜索解析器 参数1、默认字段域 参数2、分词器
    QueryParser queryParser = new QueryParser("title",new IKAnalyzer());
    //创建搜索对象
    Query query = queryParser.parse("我的西游世界");


    // 5. 第五步:执行查询。得到一个TopDocs对象,包含查询结果的总记录数,和文档列表
    // 参数1:查询对象 参数2:查询结果返回的最大记录数
    TopDocs topDocs = indexSearcher.search(query, 10);

    print(indexSearcher,topDocs);

    // 7. 第七步:关闭IndexReader对象
    indexReader.close();
}

多字段OR查询

java
/**
 *
 * select * from table where (title="世界" OR title="三国" ) or author like "吴%"
 * @throws Exception
 */
@Test
public void searchIndex2() throws Exception {
    IndexReader indexReader = getIndexReader();
    // 3. 第三步:创建一个indexsearcher对象,需要指定IndexReader对象
    IndexSearcher indexSearcher = new IndexSearcher(indexReader);

    // 构建查询条件
    // 第一个条件: title = "世界" OR title = "三国"
    Term pPathTerm1 = new Term("title", "世界");
    Term nameTerm1 = new Term("title", "三国");
    BooleanQuery.Builder firstConditionBuilder = new BooleanQuery.Builder();
    firstConditionBuilder.add(new TermQuery(pPathTerm1), BooleanClause.Occur.SHOULD);
    firstConditionBuilder.add(new TermQuery(nameTerm1), BooleanClause.Occur.SHOULD);
    BooleanQuery firstConditionQuery = firstConditionBuilder.build();

    // 第二个条件: author 以 "吴" 开头
    Term pPathTerm2 = new Term("author", "吴");
    PrefixQuery secondConditionQuery = new PrefixQuery(pPathTerm2);

    // 使用 BooleanQuery 将两个查询条件组合起来
    BooleanQuery.Builder booleanQueryBuilder = new BooleanQuery.Builder();
    booleanQueryBuilder.add(firstConditionQuery, BooleanClause.Occur.SHOULD);
    booleanQueryBuilder.add(secondConditionQuery, BooleanClause.Occur.SHOULD);

    // 执行查询
    BooleanQuery query = booleanQueryBuilder.build();


    // 5. 第五步:执行查询。得到一个TopDocs对象,包含查询结果的总记录数,和文档列表
    // 参数1:查询对象 参数2:查询结果返回的最大记录数
    TopDocs topDocs = indexSearcher.search(query, 10);

    print(indexSearcher,topDocs);

    // 7. 第七步:关闭IndexReader对象
    indexReader.close();
}

搜索高亮

java
@Test
public void selectByHighlighter() throws Exception{
    IndexReader indexReader = getIndexReader();
    // 3. 第三步:创建一个indexsearcher对象,需要指定IndexReader对象
    IndexSearcher indexSearcher = new IndexSearcher(indexReader);

    // 4. 第四步:指定查询的域和查询的关键词。
    //创建搜索解析器 参数1、默认字段域 参数2、分词器
    QueryParser queryParser = new QueryParser("title",new IKAnalyzer());
    //创建搜索对象
    Query query = queryParser.parse("我的西游世界");


    // 创建高亮器
    SimpleHTMLFormatter formatter = new SimpleHTMLFormatter("<span style='background-color: yellow'>", "</span>");
    QueryScorer scorer = new QueryScorer(query);
    Highlighter highlighter = new Highlighter(formatter, scorer);

    // 5. 第五步:执行查询。得到一个TopDocs对象,包含查询结果的总记录数,和文档列表
    // 参数1:查询对象 参数2:查询结果返回的最大记录数
    TopDocs topDocs = indexSearcher.search(query, 10);


    // 6. 第六步:返回查询结果。遍历查询结果并输出。
    ScoreDoc[] scoreDocs = topDocs.scoreDocs;
    for (ScoreDoc scoreDoc : scoreDocs) {
        // 取文档id
        int docId = scoreDoc.doc;
        // 根据id取文档对象
        Document document = indexSearcher.doc(docId);
        String title = document.get("title");
        // 取文档的属性
        System.out.println("title: " + title);
        // 高亮标题
        TokenStream tokenStream = new CJKAnalyzer().tokenStream("title", new StringReader(title));
        // 获取高亮的片段
        String fieldContent = highlighter.getBestFragment(tokenStream, title);
        System.out.println("title[高亮]: " + fieldContent);

        System.out.println("content: " + document.get("content"));
        System.out.println("-------------分割线-----------------");
    }

    // 7. 第七步:关闭IndexReader对象
    indexReader.close();
}

多字段查询

java
@Test
public void searchIndex3() throws Exception {
    IndexReader indexReader = getIndexReader();
    // 3. 第三步:创建一个indexsearcher对象,需要指定IndexReader对象
    IndexSearcher indexSearcher = new IndexSearcher(indexReader);

    // 构建查询条件
    String[] fields = {"title", "content"}; // 定义多个字段
    MultiFieldQueryParser parser = new MultiFieldQueryParser(fields, new IKAnalyzer());
    Query query = parser.parse("三国演义的神魔世界");

    // 5. 第五步:执行查询。得到一个TopDocs对象,包含查询结果的总记录数,和文档列表
    // 参数1:查询对象 参数2:查询结果返回的最大记录数
    TopDocs topDocs = indexSearcher.search(query, 10);

    print(indexSearcher,topDocs);

    // 7. 第七步:关闭IndexReader对象
    indexReader.close();
}

Term的用法

在 Apache Lucene 中,Term 是一个非常基础的概念,代表了索引中的最小单位。 一个 Term 包含两部分:字段名和文本值。Term 可以用来表示文档中的单个单词、数字、日期等。在中文环境中,一个 Term 通常是一个分词后的词汇。

创建 Term

在 Lucene 中创建一个 Term 很简单,只需要指定字段名和文本值即可

java
import org.apache.lucene.document.Term;

// 创建一个 Term
Term term = new Term("fieldName", "value");
java
doc.add(new TextField("title", "The quick brown fox", Field.Store.YES));

在这个例子中,TextField 类型的字段会被分析器处理成多个 Term。例如,"The quick brown fox" 将被分解成 "The", "quick", "brown", 和 "fox" 四个 Term。

Term 在查询过程中的使用 在查询时,你也可以使用 Term 来构造查询。例如,如果你想查询包含 "quick" 的文档,你可以这样做:

vue
// 创建 TermQuery
Term term = new Term("title", "quick");
Query query = new TermQuery(term);

// 执行查询
TopDocs hits = searcher.search(query, 10);

常见的Field

Field域的属性

  • 是否分析:是否对域的内容进行分词处理。前提是我们要对域的内容进行查询。
  • 是否索引:将 Field 分析后的词,或整个 Field 值进行索引,只有索引 才可被搜索到。比如:商品名称、商品简介分析后进行索引,订单号、身份证号不用分析但也要索引,这些将来都要作为查询条件。
  • 是否存储:将Field值存储在文档中,存储在文档中的Field才可以从Document中获取,比如商品名称、订单号,凡是将来要从Document中获取的Field都要存储。

注意存储不存储,是不影响查询的,我们查询的是索引,而不是内容。但如果要在查询后展示内容,就必须存储

常见的Field的子类

  • StringField(FieldName, FieldValue, Store.YES),字符串类型,这个Field用来构建一个字符串Field,但是不会进行分析(分词),会将整个串存储在索引中,比如(订单号,身份证号等)。是否存储在文档中用Store.YES或Store.NO决定
  • LongPoint(String name, long... point),Lone类型,还有IntPoint等类型存储数值类型的数据,让数值类型可以进行索引。但是不能存储数据,如果想存储数据还需要使StoredField。
  • StoredField(FieldName, FieldValue),重载方法,支持多种数据类型,这个Field用来构建不同类型Field不分析,不索引,但要Field存储在文档中
  • TextField(FieldName, FieldValue, Store.NO)或TextField(FieldName, reader),如果是一个Reader, lucene猜测内容比较多,会采用Unstored的策略
Field类数据类型Analyzed是否分析 Indexed是否索引Stored是否存储
StringField字符串NY
LongPointLong型YY
StoredField支持多种数据类型NN
TextField字符串或流YY

如有转载或 CV 的请标注本站原文地址