Lucene学习-创建索引、关键词查询

Lucene是一个全文检索的开源软件,对需要查询的关键词进行检索

1. 需要的jar包

  • lucene-analyzers-3.0.2.jar
  • lucene-core-3.0.2.jar
  • lucene-highlighter-3.0.2.jar
  • lucene-memory-3.0.2.jar

2. 编码步骤

2.1 准备Article文章类

public class Article {
    private Integer id;
    private String title;
    private String content;

    public Article(){}

    public Article(Integer id, String title, String content) {
        super();
        this.id = id;
        this.title = title;
        this.content = content;
    }
        @Override
    public String toString() {
        return "Article [id=" + id + ", title=" + title + ", content="
                + content + "]";
    }
    //省略getter/setter方法
......
}

2.2 创建索引库

2.2.1 步骤

  1. 创建Article对象
  2. 创建Document对象
  3. 将Article对象的三个属性值绑定到Document对象中
  4. 创建IndexWriter字符流对象
  5. 将document对象写入lucene索引库
  6. 关闭indexWriter字符流对象

2.2.2 方法实例

/**
     * 创建索引库
     * 将Article对象放入索引库的原始记录中,形成词汇表
     * @throws IOException 
     */
    @Test
    public void createIndexDB() throws IOException{
        // 1. 创建Article对象
        Article article = new Article(1,"处理器","处理器是电脑的核心部件");
        // 2. 创建Document对象
        Document document = new Document();
        // 3. 将Article对象的三个属性值绑定到Document对象中
        /*
         * 参数1:document对象中的属性名叫xid,article对象中的属性名为id,项目中建议相同
         * 参数2:xid对应的article的属性id,注意必须转化为字符型
         * 参数3:是否将xid属性值存入由原始记录表中转存入词汇表
         *          - Store.YES:表示该属性值会存入词汇表
         *          - Store.NO:表示该属性值不会存入词汇表
         * 参数4:是否将xid属性进行分词算法
         *          - Index.ANALYZED:表示会对该属性进行词汇拆分
         *          - Index.NOT_ANALYZED:表示不会对该属性进行词汇拆分
         *          项目中建议非 id 属性都进行分词拆分
         */
        document.add(new Field("xid", article.getId().toString(),Store.YES,Index.ANALYZED));
        document.add(new Field("xtitle", article.getTitle(),Store.YES,Index.ANALYZED));
        document.add(new Field("xcontent", article.getContent(),Store.YES,Index.ANALYZED));

        // 4. 创建IndexWriter字符流对象
        Directory directory = FSDirectory.open(new File("D:/IndextDB"));
        Version version = Version.LUCENE_30;
        Analyzer analyzer = new StandardAnalyzer(version);
        MaxFieldLength maxFieldLength = MaxFieldLength.LIMITED;
        /*
         * 参数1:Lucene索引库对应在磁盘的目录中,D:/IndextDB
         * 参数2:采用什么策略将文本拆分,一个策略就是一个具体的实现类
         * 参数3:最多将文本拆出来多少词汇,LIMITED表示1W个,即只取前1W个词汇,如果不足1W个词汇个,以实际为准
         */
        IndexWriter indexWriter = new IndexWriter(directory, analyzer, maxFieldLength);
        // 5. 将document对象写入lucene索引库
        indexWriter.addDocument(document);
        // 6. 关闭indexWriter字符流对象
        indexWriter.close();
    }

该方法执行后会在对应的磁盘创建索引库文件
这里写图片描述
此处有三个.cfs文件是因为下面创建了多个,下文会做解释

2.3 关键词检索

2.3.1 步骤

  1. 准备工作,创建需要查询的关键词(String类型)
  2. 创建接受结果的List<Article>集合
  3. 创建IndexSearcher字符流对象
  4. 创检QueryParser查询解析器对象
  5. 创建Query对象封装查询关键字
  6. 根据关键字,去索引库中查找相关词汇返回TopDocs索引号对象
  7. 迭代词汇表中符合条件的编号

2.3.2 方法实例

/**
     * 根据关键字词从索引库中取出来符合条件的内容
     * @throws IOException 
     * @throws ParseException 
     */
    @Test
    public void findIndexDB() throws IOException, ParseException{
        // 0. 准备工作,创建需要查询的关键词
        String keyword = "处";
        // 1. 创建接受结果的list集合
        List<Article>articleList = new ArrayList<Article>();
        Directory directory = FSDirectory.open(new File("D:/IndextDB"));
        Version version = Version.LUCENE_30;
        Analyzer analyzer = new StandardAnalyzer(version);
        MaxFieldLength maxFieldLength = MaxFieldLength.LIMITED;

        // 2. 创建IndexSearcher字符流对象
        IndexSearcher indexSearcher = new IndexSearcher(directory);

        // 3. 创检查询解析器对象
        /*
         * 参数一:使用分词器的版本,提倡使用最新的版本
         * 参数二:对document对象中的哪一个属性进行搜索
         * 参数三:参数解析器
         * */
        QueryParser queryParser = new QueryParser(version,"xcontent", analyzer);
        // 4. 创建对象封装查询关键字
        Query query = queryParser.parse(keyword);
        // 5. 根据关键字,去索引库中查找相关词汇返回TopDocs索引号对象
        /*
         * 参数一:表示封装关键字查询对象,其它QueryParser表示查询解析器
         * 参数二:MAX_RECORD表示如果根据关键字搜索出来的内容较多,只取前MAX_RECORD个内容
         *         不足MAX_RECORD个数的话,以实际为准
         * */
        int MAX_RECORD = 100;
        TopDocs topDocs = indexSearcher.search(query, MAX_RECORD);
        // 6. 迭代词汇表中符合条件的编号
        for(int i=0;i<topDocs.scoreDocs.length;i++){
            // 6.1 取出封装编号和分数的scoreDoc对象
            ScoreDoc scoreDoc = topDocs.scoreDocs[i];
            // 6.2 取出来每一个编号
            int no = scoreDoc.doc;
            // 6.3 根据编号去索引库中原始记录表中查询对应的document对象
            Document document = indexSearcher.doc(no);
            // 6.4 获取document对象的三个属性值
            String xid = document.get("xid");
            String xtitle = document.get("xtitle");
            String xcontent = document.get("xcontent");
            // 6.5 封装到article对象中
            Article article = new Article(Integer.parseInt(xid), xtitle, xcontent);
            // 6.6 将article添加到list集合中
            articleList.add(article);
        }
    }

输出查询结果:

Article [id=1, title=处理器, content=处理器是电脑的核心部件]

3. Lucene查询的优化

3.1 创建工具类

上面的代码步骤繁多,看起来都很麻烦,我们把常用的和冗余的代码抽取出来,封装到工具类中,可以是代码更简洁。
该工具类用到了 java.lang.reflect 反射包

package com.bart.lucene.util;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

import org.apache.commons.beanutils.BeanUtils;
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.Index;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.index.IndexWriter.MaxFieldLength;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.junit.Test;

import com.bart.lucene.entity.Article;

/**
 * 封装常用的操作
 * @author bart
 *
 */
public class LuceneUtils {

    private static Directory directory;//Lucene索引库对应在磁盘的目录中,D:/IndextDB
    private static Version version;//版本号
    private static Analyzer analyzer;//采用什么策略将文本拆分,一个策略就是一个具体的实现类
    private static MaxFieldLength maxFieldLength;//默认1W条记录

    static{
        try {
            directory =  FSDirectory.open(new File("E:/IndexDBDBDB"));
            version = Version.LUCENE_30;
            analyzer = new StandardAnalyzer(version);
            maxFieldLength = MaxFieldLength.LIMITED;
        } catch (Exception e) {
            e.printStackTrace();
            //手动抛出运行时异常
            throw new RuntimeException(e);
        }
    }

    public static Directory getDirectory() {
        return directory;
    }

    public static Version getVersion() {
        return version;
    }

    public static Analyzer getAnalyzer() {
        return analyzer;
    }

    public static MaxFieldLength getMaxFieldLength() {
        return maxFieldLength;
    }

    //私有的构造方法,防止外部new来
    private LuceneUtils(){}

    /**
     * JavaBean转化为Document对象
     * @param object
     * @return Document
     * @throws Exception 
     */
    public static Document javaBean2Document(Object obj) throws Exception{
        Document document = new Document();
        // 1. 获取obj的字节码
        Class clazz = obj.getClass();
        // 2. 获取私有属性
        Field[] fields = clazz.getDeclaredFields();
        // 3. 迭代私有属性
        for(Field field : fields){
            // 3.1 设置允许访问,强力反射,提高速度
            field.setAccessible(true);
            // 3.2 获取属性名
            String name = field.getName();
            // 3.3 拼接方法名 getId()/getTitle()
            String methodName = "get"+name.substring(0, 1).toUpperCase()+name.substring(1);
            // 3.4 获取方法,(方法名,参数名)
            Method method = clazz.getMethod(methodName,null);
            // 3.5 执行方法,获得返回值
            String value = method.invoke(obj,null).toString();
//            // 3.6 添加到document对象中
            document.add(new org.apache.lucene.document.Field(name,value,Store.YES,Index.ANALYZED));
        }
        return document;
    }

    /**
     * document对象转化为javabean对象
     * @param document
     * @param clazz
     * @return t
     * @throws Exception
     */
    public static  <T> T document2JavaBean(Document document,Class<T>clazz) throws Exception{
        // 1. 创建对应的实例
        T t = clazz.newInstance();
        // 2. 通过反射得到私有成员变量
        Field[] fields = clazz.getDeclaredFields();
        // 3. 遍历私有成员变量
        for(Field field : fields){
            // 3.1 得到成员变量名
            String name = field.getName();
            // 3.2 得到document中的成员变量值
            String value = document.get(name);
            // 3.3 使用BeanUtils.setProperty(类实例,变量名,变量值)
            BeanUtils.setProperty(t, name, value);
        }
        return t;
    }

    //测试
    public static void main(String[] args) throws Exception{
        Article article = new Article(1, "处理器","处理器是一台电子计算机的重要部件");
        Document document = LuceneUtils.javaBean2Document(article);
        System.out.println(document.toString());
        System.out.println("---------");
        Article article2 = LuceneUtils.document2JavaBean(document, Article.class);
        System.out.println(article2);
    }
}

3.2 重构FirstApp类

使用封装的工具类重构FirstApp.java

3.2.1 创建索引库

@Test
    public void createIndexDB() throws Exception{
        // 1. 创建Article对象
        //Article article = new Article(1,"处理器","处理器是电脑的核心部件");
        //Article article = new Article(2,"显卡","显卡是电脑的显示输出部件");
        Article article = new Article( 3,"内存条","内存条是电脑的核心部件之一");
        // 2. 用LuceneUtils工具类的方法获得Document对象
        Document document = LuceneUtils.javaBean2Document(article);
        // 3. 创建IndexWriter字符流对象
        IndexWriter indexWriter = new IndexWriter(LuceneUtils.getDirectory(),LuceneUtils.getAnalyzer(),LuceneUtils.getMaxFieldLength());
        // 4. indexWriter对象写入document对象
        indexWriter.addDocument(document);
        // 5. 关闭indexWriter流
        indexWriter.close();
    }

因为此处创建了三个Article都放在了索引库中,故此在2.2.2中的图中三个.cfs文件。

3.2.2 根据关键词查询

 @Test
    public void findIndexDB() throws Exception{
        // 1. 创建keyword
        //准备工作
        String keyword = "显卡";
        List<Article>articleList = new ArrayList<Article>();
        // 2. 创建indexSearcher
        IndexSearcher indexSearcher = new IndexSearcher(LuceneUtils.getDirectory());
        // 3. 创检查询解析器对象
        QueryParser queryParser = new QueryParser(LuceneUtils.getVersion(),"content",LuceneUtils.getAnalyzer());
        // 4. 创建对象封装查询关键字
        Query query = queryParser.parse(keyword);
        // 5. 查询
        int MAX_RECORD=100;
        TopDocs topDocs = indexSearcher.search(query, MAX_RECORD);
        // 6. 迭代词汇表中符合条件的编号
        for(int i=0;i<topDocs.scoreDocs.length;i++){
            // 6.1 取出封装编号和分数的scoreDoc对象
            ScoreDoc scoreDoc = topDocs.scoreDocs[i];
            // 6.2 取出来每一个编号
            int no = scoreDoc.doc;
            // 6.3 根据编号去索引库中原始记录表中查询对应的document对象
            Document document = indexSearcher.doc(no);
            // 6.4 利用工具类将document对象转化为article对象
            Article article = LuceneUtils.document2JavaBean(document, Article.class);
            // 6.5 将查询到的article对象添加到list集合中
            articleList.add(article);
        }
        //遍历结果
        for(Article article : articleList){
            System.out.println(article);
        }

    }

输出结果:

Article [id=2, title=显卡, content=显卡是电脑的显示输出部件]

哈哈,作为一个卡巴司机,当然首先查询一下显卡了。

总结

我们发现代码重构之后,代码简洁了很多,而且工具的重用性更强了。在之后的项目中可以导包,直接调用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值