Lucene学习总结

Lucene是什么?

Lucene在维基百科的定义

Lucene是一套用于全文检索和搜索的开放源代码程序库,由Apache软件基金会支持和提供。Lucene提供了一个简单却强大的应用程序接口,能够做全文索引和搜索,在Java开发环境里Lucene是一个成熟的免费开放源代码工具;就其本身而论,Lucene是现在并且是这几年,最受欢迎的免费Java信息检索程序库。

Lucene和solr

我想提到Lucene,不得不提solr了。

很多刚接触Lucene和Solr的人都会问这个明显的问题:我应该使用Lucene还是Solr?

答案很简单:如果你问自己这个问题,在99%的情况下,你想使用的是Solr. 形象的来说Solr和Lucene之间关系的方式是汽车及其引擎。 你不能驾驶一台发动机,但可以开一辆汽车。 同样,Lucene是一个程序化库,您不能按原样使用,而Solr是一个完整的应用程序,您可以立即使用它。

全文检索是什么?

全文检索在百度百科的定义

全文数据库是全文检索系统的主要构成部分。所谓全文数据库是将一个完整的信息源的全部内容转化为计算机可以识别、处理的信息单元而形成的数据集合。全文数据库不仅存储了信息,而且还有对全文数据进行词、字、段落等更深层次的编辑、加工的功能,而且所有全文数据库无一不是海量信息数据库。

全文检索首先将要查询的目标文档中的词提取出来,组成索引,通过查询索引达到搜索目标文档的目的。这种先建立索引,再对索引进行搜索的过程就叫全文检索(Full-text Search)。

全文检索(Full-Text Retrieval)是指以文本作为检索对象,找出含有指定词汇的文本。

全面、准确和快速是衡量全文检索系统的关键指标。

关于全文检索,我们要知道:

只处理文本。
不处理语义。
搜索时英文不区分大小写。
结果列表有相关度排序。

(查出的结果如果没有相关度排序,那么系统不知道我想要的结果在哪一页。我们在使用百度搜索时,一般不需要翻页,为什么?因为百度做了相关度排序:为每一条结果打一个分数,这条结果越符合搜索条件,得分就越高,叫做相关度得分,结果列表会按照这个分数由高到低排列,所以第1页的结果就是我们最想要的结果。) 在信息检索工具中,全文检索是最具通用性和实用性的。

全文检索和数据库搜索的区别

简单来说,这两者解决的问题是不一样。数据库搜索在匹配效果、速度、效率等方面都逊色于全文检索。

Lucene实现全文检索流程是什么?

全文检索的流程分为两大部分:索引流程、搜索流程

索引流程:即采集数据构建文档对象分析文档(分词)创建索引。

搜索流程:即用户通过搜索界面创建查询执行搜索,搜索器从索引库搜索渲染搜索结果

简单案例

案例1.Lucene实现以单一文件为单一记录建立全文索引,将查询关键字分词,到索引库查询记录

(基于Lucene 7.2.x)

package com.xiaobai.lucene;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Paths;

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.Document;
import org.apache.lucene.document.Field;
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;
/**
 *替换Lucene版本为7.2.x
 *TODO  索引文件
 * @author Snaiclimb
 * @date 2018年3月30日
 * @version 1.8
 */
public class Indexer {
    // 写索引实例
    private IndexWriter writer;

    /**
     * 构造方法 实例化IndexWriter
     *
     * @param indexDir
     * @throws IOException
     */
    public Indexer(String indexDir) throws IOException {
        //得到索引所在目录的路径
        Directory directory = FSDirectory.open(Paths.get(indexDir));
        // 标准分词器
//        Analyzer analyzer = new StandardAnalyzer();
        Analyzer analyzer = new SmartChineseAnalyzer();
        //保存用于创建IndexWriter的所有配置。
        IndexWriterConfig iwConfig = new IndexWriterConfig(analyzer);
        //实例化IndexWriter
        writer = new IndexWriter(directory, iwConfig);
    }

    /**
     * 关闭写索引
     *
     * @throws Exception
     * @return 索引了多少个文件
     */
    public void close() throws IOException {
        writer.close();
    }

    public int index(String dataDir) throws Exception {
        File[] files = new File(dataDir).listFiles();
        for (File file : files) {
            //索引指定文件
            indexFile(file);
        }
        //返回索引了多少个文件
        return writer.numDocs();

    }

    /**
     * 索引指定文件
     *
     * @param f
     */
    private void indexFile(File f) throws Exception {
        //输出索引文件的路径
        System.out.println("索引文件:" + f.getCanonicalPath());
        //获取文档,文档里再设置每个字段
        Document doc = getDocument(f);
        //开始写入,就是把文档写进了索引文件里去了;
        writer.addDocument(doc);
    }

    /**
     * 获取文档,文档里再设置每个字段
     *
     * @param f
     * @return document
     */
    private Document getDocument(File f) throws Exception {
        Document doc = new Document();
        //把设置好的索引加到Document里,以便在确定被索引文档
        doc.add(new TextField("contents", new FileReader(f)));//每篇文件的信息作为一个Document写入,有单独id,对应数据库一条记录的主键,Field对应文件全文、文件名、路径,对应数据库一条记录的这些对应字段
        //Field.Store.YES:把文件名存索引文件里,为NO就说明不需要加到索引文件里去
        doc.add(new TextField("fileName", f.getName(), Field.Store.YES));
        //把完整路径存在索引文件里
        doc.add(new TextField("fullPath", f.getCanonicalPath(), Field.Store.YES));
        return doc;
    }

    public static void main(String[] args) {
        //索引指定的文档路径
        String indexDir = "D:\lucene\dataindex";
        ////被索引数据的路径
        String dataDir = "D:\lucene\data";
        Indexer indexer = null;
        int numIndexed = 0;
        //索引开始时间
        long start = System.currentTimeMillis();
        try {
            indexer = new Indexer(indexDir);
            numIndexed = indexer.index(dataDir);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            try {
                indexer.close();
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        //索引结束时间
        long end = System.currentTimeMillis();
        System.out.println("索引:" + numIndexed + " 个文件 花费了" + (end - start) + " 毫秒");
    }

}

执行结果:

package com.xiaobai.lucene;


import java.nio.file.Paths;

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.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
/**替换Lucene版本为7.2.x
 * 根据索引搜索
 *TODO
 * @author Snaiclimb
 * @date 2018年3月25日
 * @version 1.8
 */
public class Searcher {

    public static void search(String indexDir, String q) throws Exception {

        // 得到读取索引文件的路径
        Directory dir = FSDirectory.open(Paths.get(indexDir));
        // 通过dir得到的路径下的所有的文件
        IndexReader reader = DirectoryReader.open(dir);
        // 建立索引查询器
        IndexSearcher is = new IndexSearcher(reader);
        // 实例化分析器
//        Analyzer analyzer = new StandardAnalyzer();
        Analyzer analyzer = new SmartChineseAnalyzer();
        // 建立查询解析器
        /**
         * 第一个参数是要查询的字段; 第二个参数是分析器Analyzer
         */
        QueryParser parser = new QueryParser("contents", analyzer);//带分词器的查询解析
        // 根据传进来的p查找
        Query query = parser.parse(q);//这里进行了分词,Spring Cloud被分成Spring和Cloud两个词
        // 计算索引开始时间
        long start = System.currentTimeMillis();
        // 开始查询
        /**
         * 第一个参数是通过传过来的参数来查找得到的query; 第二个参数是要出查询的行数
         */
        //第二个参数:查询符合条件的前100条记录
        /**
         * 查询是这样:每篇文章是作为一-个-记-录,也就是一个Document写入索引文件的,所以这里查出的记录数是包含查询关键词的文章数,而!不!是!所有文章中出现这个词的总数!!
         * 索引的原理是倒排索引:记录一个索引词出现在哪些文章中,记录这些文章的唯一编号(ID),而不是一篇文章出现了什么词、多少次
         * 所以这里返回包含"原理"这个词的文章数(即记录数),也就是3篇,对应三个文件,控制台列出的记录也是对应的3个文件名,代表3个记录,不是说这个词一共出现3次,而是它出现在3篇文章里
         * 这里的windows环境的txt文件有些问题:需要使用notepad命令编辑数据,保存为UTF-8格式才可正常搜索到中文关键字,直接鼠标右键建立或默认保存的
         * 文件无法正确查询到中文关键词,需要格外注意!!
         */
        TopDocs hits = is.search(query, 100);//将分词后的关键字封装为查询条件,从索引库查询
        // 计算索引结束时间
        long end = System.currentTimeMillis();
        System.out.println("匹配 " + q + " ,总共花费" + (end - start) + "毫秒" + "查询到" + hits.totalHits + "个记录");
        // 遍历hits.scoreDocs,得到scoreDoc
        /**
         * ScoreDoc:得分文档,即得到文档 scoreDocs:代表的是topDocs这个文档数组
         *
         * @throws Exception
         */
        for (ScoreDoc scoreDoc : hits.scoreDocs) {
            Document doc = is.doc(scoreDoc.doc);
            System.out.println(doc.get("fullPath"));
        }

        // 关闭reader
        reader.close();
    }

    public static void main(String[] args) {
        String indexDir = "D:\lucene\dataindex";
        //我们要搜索的内容
//        String q = "Spring Cloud";//目前这个分词器,无法搜索中文内容
        String q = "原理";
        try {
            search(indexDir, q);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

执行结果:

案例2.Lucene自带不同分词器的中文分词效果,及自定义分词器(自定义停用词)

(基于Lucene 7.2.x)

package com.xiaobai.lucene.self_stopword;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.CharArraySet;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.junit.Test;

import java.util.Iterator;

/**
 * 替换Lucene版本为7.2.x
 */
public class SmartChineseTest {

    private void print(Analyzer analyzer) throws Exception {
        String text = "Lucene自带多种分词器,其中对中文分词支持比较好的是smartcn。";
        TokenStream tokenStream = analyzer.tokenStream("content", text);
        CharTermAttribute attribute = tokenStream.addAttribute(CharTermAttribute.class);
        tokenStream.reset();
        while (tokenStream.incrementToken()) {
            System.out.println(new String(attribute.toString()));
        }
    }

    @Test
    public void testStandardAnalyzer() throws Exception {
        StandardAnalyzer standardAnalyzer = new StandardAnalyzer();
        print(standardAnalyzer);
    }

    @Test
    public void testSmartChineseAnalyzer() throws Exception {
        SmartChineseAnalyzer smartChineseAnalyzer = new SmartChineseAnalyzer();
        print(smartChineseAnalyzer);
    }

    @Test
    public void testMySmartChineseAnalyzer() throws Exception {
        CharArraySet charArraySet = new CharArraySet(0, true);
        // 系统默认停用词
        Iterator<Object> iterator = SmartChineseAnalyzer.getDefaultStopSet().iterator();
        while (iterator.hasNext()) {
            charArraySet.add(iterator.next());
        }
        // 自定义停用词
        String[] myStopWords = { "对", "的", "是", "其中" };
        for (String stopWord : myStopWords) {
            charArraySet.add(stopWord);
        }
        SmartChineseAnalyzer smartChineseAnalyzer = new SmartChineseAnalyzer(charArraySet);
        print(smartChineseAnalyzer);
    }
}

标准分词器StandardAnalyzer测试结果:

SmartChineseAnalyzer测试结果:

自定义停用词SmartChineseAnalyzer分词器测试结果:

参考博文(原理分析):

https://blog.csdn.net/xxpsw/article/details/78902312

案例3.IKAnalyzer中文分词器的应用,包括扩展词和停用词的配置

(基于Lucene 4.4.0)

使用IKAnalyzer中文分词器(常用版本IKAnalyzer2012_FF,这里是2012_u6,只能兼容Lucene版本4.4.0,高版本analyzer.tokenStream方法报错:AbstractMethodError: org.apache.lucene.analysis.Analyzer.createComponents)

package com.xiaobai.lucene.chinese;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
import org.junit.Test;
import org.wltea.analyzer.lucene.IKAnalyzer;

import java.io.IOException;

/**
 * 替换Lucene版本为4.4.0
 * 使用IKAnalyzer中文分词器(常用版本IKAnalyzer2012_FF,这里是2012_u6,只能兼容Lucene版本4.4.0,高版本analyzer.tokenStream方法报错:AbstractMethodError: org.apache.lucene.analysis.Analyzer.createComponents)
 */
public class LuenceFirst {

    // 查看分析器的分词效果
    @Test
    public void testAnanlyzer() throws IOException {
        // 1、创建一个分析器对象
        Analyzer analyzer = new IKAnalyzer(); // 智能中文分析器
        // 2、从分析器对象中获得tokenStream对象
        // 参数1:域的名称,可以为null,或者是""
        // 参数2:要分析的文本
        TokenStream tokenStream = analyzer.tokenStream("", "数据库xixx(实际测试中写真名,这里手动改的,不然无法通过博客审核)中存储的数据是结构化数据高富帅,即行数据java,可以用二维表结构来逻辑表达实现的数据。");

        // 3、设置一个引用(相当于指针),这个引用可以是多种类型,可以是关键词的引用,偏移量的引用等等
        CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class); // charTermAttribute对象代表当前的关键词
        // 偏移量(其实就是关键词在文档中出现的位置,拿到这个位置有什么用呢?因为我们将来可能要对该关键词进行高亮显示,进行高亮显示要知道这个关键词在哪?)
        OffsetAttribute offsetAttribute = tokenStream.addAttribute(OffsetAttribute.class);
        // 4、调用tokenStream的reset方法,不调用该方法,会抛出一个异常
        tokenStream.reset();
        // 5、使用while循环来遍历单词列表
        while (tokenStream.incrementToken()) {
            System.out.println("start→" + offsetAttribute.startOffset()); // 关键词起始位置
            // 6、打印单词
            System.out.println(charTermAttribute);
            System.out.println("end→" + offsetAttribute.endOffset()); // 关键词结束位置
        }
        // 7、关闭tokenStream对象
        tokenStream.close();
    }

}

IKAnalyzer配置文件、扩展词库、停用词库配置(须放在classpath路径下):

IKAnalyzer.cfg.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">  
<properties>  
    <comment>IK Analyzer 扩展配置</comment>
    <!--用户可以在这里配置自己的扩展字典 -->
    <entry key="ext_dict">ext.dic;</entry> 
    
    <!--用户可以在这里配置自己的扩展停止词字典-->
    <entry key="ext_stopwords">stopword.dic;</entry> 
    
</properties>

ext.dic:

诛仙
诛仙2
梦幻诛仙
梦幻诛仙2
高富帅

stopword.dic:

a
an
and
are
as
at
be
but
by
for
if
in
into
is
it
no
not
of
on
or
such
that
the
their
then
there
these
they
this
to
was
will
with
也
了
仍
从
以
使
则
却
又
及
对
就
并
很
或
把
是
的
着
给
而
被
让
在
还
比
等
当
与
于
但
xixx(这里手动改了,写真名无法通过审核,实际有效果,分词器可以屏蔽掉这个停用词)

执行结果:

加载扩展词典:ext.dic
加载扩展停止词典:stopword.dic
start→0
数据库
end→3
start→0
数据
end→2
start→2

end→3
start→6

end→7
start→7
存储
end→9
start→10
数据
end→12
start→13
结构化
end→16
start→13
结构
end→15
start→15

end→16
start→16
数据
end→18
start→18
高富帅
end→21
start→22
即行
end→24
start→23
行数
end→25
start→24
数据
end→26
start→26
java
end→30
start→31
可以用
end→34
start→31
可以
end→33
start→33

end→34
start→34
二维
end→36
start→34

end→35
start→35

end→36
start→36

end→37
start→37
结构
end→39
start→39

end→40
start→40
逻辑
end→42
start→42
表达
end→44
start→44
实现
end→46
start→47
数据
end→49

案例4.使用IKAnalyzer中文分词器进行索引和搜索,使用扩展词和停用词库

(基于Lucene 4.4.0)

使用IKAnalyzer中文分词器(常用版本IKAnalyzer2012_FF,这里是2012_u6,只能兼容Lucene版本4.4.0,高版本analyzer.tokenStream方法报错:AbstractMethodError: org.apache.lucene.analysis.Analyzer.createComponents)

package com.xiaobai.lucene.chinese;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
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.wltea.analyzer.lucene.IKAnalyzer;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Paths;

/**
 * 替换Lucene版本为4.4.0
 * 使用IKAnalyzer中文分词器(常用版本IKAnalyzer2012_FF,这里是2012_u6,只能兼容Lucene版本4.4.0)
 */
public class Indexer {
    // 写索引实例
    private IndexWriter writer;

    /**
     * 构造方法 实例化IndexWriter
     *
     * @param indexDir
     * @throws IOException
     */
    public Indexer(String indexDir) throws IOException {//替换Lucene版本为4.4.0,打开这里所有注释掉的内容
        //得到索引所在目录的路径
        Directory directory = FSDirectory.open(new File(indexDir));
        // 标准分词器
//        Analyzer analyzer = new StandardAnalyzer();
        Analyzer analyzer = new IKAnalyzer();
        Version matchVersion = Version.LUCENE_CURRENT;// lucene当前匹配的版本
        //保存用于创建IndexWriter的所有配置。
        IndexWriterConfig iwConfig = new IndexWriterConfig(matchVersion,analyzer);
        //实例化IndexWriter
        writer = new IndexWriter(directory, iwConfig);
    }

    /**
     * 关闭写索引
     *
     * @throws Exception
     * @return 索引了多少个文件
     */
    public void close() throws IOException {
        writer.close();
    }

    public int index(String dataDir) throws Exception {
        File[] files = new File(dataDir).listFiles();
        for (File file : files) {
            //索引指定文件
            indexFile(file);
        }
        //返回索引了多少个文件
        return writer.numDocs();

    }

    /**
     * 索引指定文件
     *
     * @param f
     */
    private void indexFile(File f) throws Exception {
        //输出索引文件的路径
        System.out.println("索引文件:" + f.getCanonicalPath());
        //获取文档,文档里再设置每个字段
        Document doc = getDocument(f);
        //开始写入,就是把文档写进了索引文件里去了;
        writer.addDocument(doc);
    }

    /**
     * 获取文档,文档里再设置每个字段
     *
     * @param f
     * @return document
     */
    private Document getDocument(File f) throws Exception {
        Document doc = new Document();
        //把设置好的索引加到Document里,以便在确定被索引文档
        doc.add(new TextField("contents", new FileReader(f)));//每篇文件的信息作为一个Document写入,有单独id,对应数据库一条记录的主键,Field对应文件全文、文件名、路径,对应数据库一条记录的这些对应字段
        //Field.Store.YES:把文件名存索引文件里,为NO就说明不需要加到索引文件里去
        doc.add(new TextField("fileName", f.getName(), Field.Store.YES));
        //把完整路径存在索引文件里
        doc.add(new TextField("fullPath", f.getCanonicalPath(), Field.Store.YES));
        return doc;
    }

    public static void main(String[] args) {
        //索引指定的文档路径
        String indexDir = "D:\lucene\dataindex";
        ////被索引数据的路径
        String dataDir = "D:\lucene\data";
        Indexer indexer = null;
        int numIndexed = 0;
        //索引开始时间
        long start = System.currentTimeMillis();
        try {
            indexer = new Indexer(indexDir);
            numIndexed = indexer.index(dataDir);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            try {
                indexer.close();
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        //索引结束时间
        long end = System.currentTimeMillis();
        System.out.println("索引:" + numIndexed + " 个文件 花费了" + (end - start) + " 毫秒");
    }

}

删除此前生成的所有索引文件(否则结果中发现索引的文件越来越多),重新索引的执行结果:

package com.xiaobai.lucene.chinese;


import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer;
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.QueryParser;
import org.apache.lucene.search.*;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.wltea.analyzer.lucene.IKAnalyzer;

import java.io.File;
import java.nio.file.Paths;

/**
 * 替换Lucene版本为4.4.0
 * 使用IKAnalyzer中文分词器(常用版本IKAnalyzer2012_FF,这里是2012_u6,只能兼容Lucene版本4.4.0)
 */
public class Searcher {

    public static void search(String indexDir, String q) throws Exception {//替换Lucene版本为4.4.0,打开这里所有注释掉的内容

        // 得到读取索引文件的路径
        Directory dir = FSDirectory.open(new File(indexDir));
        // 通过dir得到的路径下的所有的文件
        IndexReader reader = DirectoryReader.open(dir);
        // 建立索引查询器
        IndexSearcher is = new IndexSearcher(reader);
        // 实例化分析器
//        Analyzer analyzer = new StandardAnalyzer();
        Analyzer analyzer = new IKAnalyzer();
        // 建立查询解析器
        /**
         * 第二个参数是要查询的字段; 第三个参数是分析器Analyzer
         */
        Version matchVersion = Version.LUCENE_CURRENT;// lucene当前匹配的版本
        QueryParser parser = new QueryParser(matchVersion,"contents", analyzer);//带分词器的查询解析
        // 根据传进来的p查找
        Query query = parser.parse(q);//这里进行了分词,Spring Cloud被分成Spring和Cloud两个词
//        Query query = new TermQuery(new Term("contents", "原理"));
        // 计算索引开始时间
        long start = System.currentTimeMillis();
        // 开始查询
        /**
         * 第一个参数是通过传过来的参数来查找得到的query; 第二个参数是要出查询的行数
         */
        //第二个参数:查询符合条件的前100条记录
        /**
         * 查询是这样:每篇文章是作为一-个-记-录,也就是一个Document写入索引文件的,所以这里查出的记录数是包含查询关键词的文章数,而!不!是!所有文章中出现这个词的总数!!
         * 索引的原理是倒排索引:记录一个索引词出现在哪些文章中,记录这些文章的唯一编号(ID),而不是一篇文章出现了什么词、多少次
         * 所以这里返回包含"原理"这个词的文章数(即记录数),也就是3篇,对应三个文件,控制台列出的记录也是对应的3个文件名,代表3个记录,不是说这个词一共出现3次,而是它出现在3篇文章里
         * 这里的windows环境的txt文件有些问题:需要使用notepad命令编辑数据,保存为UTF-8格式才可正常搜索到中文关键字,直接鼠标右键建立或默认保存的
         * 文件无法正确查询到中文关键词,需要格外注意!!
         */
        TopDocs hits = is.search(query, 100);//将分词后的关键字封装为查询条件,从索引库查询
        // 计算索引结束时间
        long end = System.currentTimeMillis();
        System.out.println("匹配 " + q + " ,总共花费" + (end - start) + "毫秒" + "查询到" + hits.totalHits + "个记录");
        // 遍历hits.scoreDocs,得到scoreDoc
        /**
         * ScoreDoc:得分文档,即得到文档 scoreDocs:代表的是topDocs这个文档数组
         *
         * @throws Exception
         */
        for (ScoreDoc scoreDoc : hits.scoreDocs) {
            Document doc = is.doc(scoreDoc.doc);
            System.out.println(doc.get("fullPath"));
        }

        // 关闭reader
        reader.close();
    }

    public static void main(String[] args) {
        String indexDir = "D:\lucene\dataindex";
        //我们要搜索的内容
//        String q = "Spring Cloud";//目前这个分词器,无法搜索中文内容
        String q = "高富帅";
        try {
            search(indexDir, q);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

执行结果:

而如果扩展词中去掉"高富帅"这个网络流行词,重新索引,搜索后执行结果是这样的:

其中csst.txt文件经编辑器搜索,发现没有"高富帅",有"高"、“富"两个字,而没有"帅"字

这说明扩展词起了作用,在索引和搜索时均将其当作一个完整的词来搜索,所以csst.txt没有匹配到,

而去掉了这个扩展词,则可按字匹配,有其中一个、两个字即可匹配,所以csst.txt可以匹配到。

而查找xixx这个停用词,明明文件中有,匹配结果为0,说明停用词也起了作用

上面的中文分词器程序,搜索Spring Cloud这样的英文词也是OK的,3个文件都匹配到了,经断点Debug,发现其中Spring Cloud被分词成Spring和Cloud两个词,与其他Lucene自带中文、英文分词器分词效果相同。

参考博文:

IKAnalyzer的TokenStream原理,分词逻辑,扩展词、停用词使用:https://blog.csdn.net/yerenyuan_pku/article/details/72591778

Lucene倒排索引、索引记录数原理,与数据库的区别和联系,索引CRUD,查询,过滤,高亮,优化等:https://www.cnblogs.com/DarrenChan/p/5860738.html

Lucene全文检索组件分析

在Lucene中,采集数据(从网站爬取或连接数据库)就是为了创建索引,创建索引需要先将采集的原始数据加工为文档,再由文档分词产生索引

文档(Document) 中包含若干个Field域。

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

Directory描述了索引的存储位置,底层封装了I/O操作,负责对索引进行存储。它是一个抽象类,它的子类常用的包括FSDirectory(在文件系统存储索引)、RAMDirectory(在内存存储索引)。

在对Docuemnt中的内容索引之前需要使用分词器进行分词 ,分词的主要过程就是分词、过滤两步。

分词就是将采集到的文档内容切分成一个一个的词,具体应该说是将Document中Fieldvalue值切分成一个一个的词。

Lucene中自带了StandardAnalyzer,它可以对英文进行分词。

过滤包括去除标点符号、去除停用词(的、是、a、an、the等)、大写转小写、词的形还原(复数形式转成单数形参、过去式转成现在式等)。

停用词是为节省存储空间和提高搜索效率,搜索引擎在索引页面或处理搜索请求时会自动忽略某些字或词,这些字或词即被称为Stop Words(停用词)。比如语气助词、副词、介词、连接词等,通常自身并无明确的意义,只有将其放入一个完整的句子中才有一定作用,如常见的“的”、“在”、“是”、“啊”等。 

参考博文:

7.2.x入门三篇:

https://blog.csdn.net/qq_34337272/article/details/79764305

两种中文分词:

https://blog.csdn.net/yerenyuan_pku/article/details/72591778

https://blog.csdn.net/xxpsw/article/category/7343521

Lucene各方面比较全面、深入的一篇(4.4.0版本Lucene,支持IKAnalyzer2012版本):

https://www.cnblogs.com/DarrenChan/p/5860738.html

补4篇:

各种查询:https://www.cnblogs.com/xiaobai1226/p/7652093.html

基础各方面:https://www.cnblogs.com/linkworld/p/7826815.html

Lucene6.6:https://blog.csdn.net/k_122/article/details/76400888

深入索引:https://blog.csdn.net/zhengbo0/article/details/19078527

Lucene原理与代码分析系列:

https://www.cnblogs.com/forfuture1978/category/300665.html

https://blog.csdn.net/sihai12345/article/category/6881900

https://blog.csdn.net/pfnie/article/category/6765099

https://blog.csdn.net/wuyinggui10000/article/category/3173543

易百教程:

https://www.yiibai.com/lucene/lucene_first_application.html

Elasticsearch系列:

https://blog.csdn.net/zkf541076398/article/category/7452785/1

原文地址:https://www.cnblogs.com/free-wings/p/9735224.html