Lucene系列(一)快速入门

系列文章:html

Lucene系列(一)快速入门java

Lucene系列(二)luke使用及索引文档的基本操做git

Lucene系列(三)查询及高亮github

<font color="#0066CC">Lucene是什么?</font>

<font color="#0066CC">Lucene在维基百科的定义</font>面试

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

另外,Lucene不提供爬虫功能,若是须要获取内容须要本身创建爬虫应用。
Lucene只作索引和搜索工做。数据库

<font color="#0066CC">Lucene官网</font>apache

http://lucene.apache.org/数组

打开Luncene官网你会发现Lucene版本更新的太快了,如今最新的版本已是7.2.1。不过这也变相说明了Luncene这个开源库的火爆。微信

<font color="#0066CC">Lucenesolr</font>工具

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

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

答案很简单:若是你问本身这个问题,在99%的状况下,你想使用的是Solr. 形象的来讲Solr和Lucene之间关系的方式是汽车及其引擎。 你不能驾驶一台发动机,但能够开一辆汽车。 一样,Lucene是一个程序化库,您不能按原样使用,而Solr是一个完整的应用程序,您能够当即使用它。(参考:Lucene vs Solr

<font color="#0066CC">全文检索是什么?</font>

<font color="#0066CC">全文检索在百度百科的定义</font>

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

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

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

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

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

  • 只处理文本。
  • 不处理语义。
  • 搜索时英文不区分大小写。
  • 结果列表有相关度排序。(查出的结果若是没有相关度排序,那么系统不知道我想要的结果在哪一页。咱们在使用百度搜索时,通常不须要翻页,为何?由于百度作了相关度排序:为每一条结果打一个分数,这条结果越符合搜索条件,得分就越高,叫作相关度得分,结果列表会按照这个分数由高到低排列,因此第1页的结果就是咱们最想要的结果。)

在信息检索工具中,全文检索是最具通用性和实用性的。

参考:https://zhuanlan.zhihu.com/p/...

<font color="#0066CC">全文检索和数据库搜索的区别</font>

简单来讲,这二者解决的问题是不同。数据库搜索在匹配效果、速度、效率等方面都逊色于全文检索。下面咱们的一个例子就能很清楚说明这一点。

<font color="#0066CC">Lucene实现全文检索流程是什么?</font>

Lucene实现全文检索流程
全文检索的流程分为两大部分:索引流程搜索流程

  • 索引流程:即采集数据构建文档对象分析文档(分词)建立索引。
  • 搜索流程:即用户经过搜索界面建立查询执行搜索,搜索器从索引库搜索渲染搜索结果

咱们在下面的一个程序中,对这个全文检索的流程会有进一步的了解。

<font color="#0066CC">Lucene实现向文档写索引并读取文档</font>

截止2018/3/30,用到的jar包结为最新。

程序用到的数据下载地址:

连接:https://pan.baidu.com/s/1ccgrCCRBBGOL-fmmOLrxlQ

密码:vyof

  1. <font color="#00CC00">建立Maven项目,并添加相关jar包依赖</font>
<!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-core -->
        <!-- Lucene核心库 -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-core</artifactId>
            <version>7.2.1</version>
        </dependency>
        <!-- Lucene解析库 -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-queryparser</artifactId>
            <version>7.2.1</version>
        </dependency>
        <!-- Lucene附加的分析库 -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-analyzers-common</artifactId>
            <version>7.2.1</version>
        </dependency>
  1. <font color="#00CC00">向文档里写索引</font>
package lucene_demo1;

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.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;
/**
 * 
 *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();
        //保存用于建立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)));
        //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) + " 毫秒");
    }

}

运行效果:
运行效果图
咱们查看D:\lucene\dataindex文件夹。咱们发现多了一些东西,这些东西就是咱们立刻用来全文搜索的索引。

//索引指定的文档路径
String indexDir = "D:\\lucene\\dataindex";

D:\lucene\dataindex文件夹

Mark博文:Lucene的索引文件格式(1)

  1. <font color="#00CC00">全文检索测试</font>
package lucene_demo1;

import java.nio.file.Paths;

import org.apache.lucene.analysis.Analyzer;
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;
/**
 * 根据索引搜索
 *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
         */
        QueryParser parser = new QueryParser("contents", analyzer);
        // 根据传进来的p查找
        Query query = parser.parse(q);
        // 计算索引开始时间
        long start = System.currentTimeMillis();
        // 开始查询
        /**
         * 第一个参数是经过传过来的参数来查找获得的query; 第二个参数是要出查询的行数
         */
        TopDocs hits = is.search(query, 10);
        // 计算索引结束时间
        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 = "Jean-Philippe sdsds Barrette-LaPierre";
        try {
            search(indexDir, q);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

上面咱们搜索的是:"Jean-Philippe Barrette-LaPierre";即便你:"Jean-Philippe ssss Barrette-LaPierre"这样搜索也仍是搜索到,觉得Lucene对其进行了分词,对中文无效。
 全文搜索

<font color="#0066CC">Lucene实现全文检索流程是什么?</font>

咱们刚刚实现的程序已经清楚地向咱们展现了Lucene实现全文检索流程,咱们再来回顾一下。
Lucene实现全文检索流程

  • 在Lucene中,采集数据(从网站爬取或链接数据库)就是为了建立索引,建立索引须要先将采集的原始数据加工为文档,再由文档分词产生索引。文档(Document) 中包含若干个Field域。
  • IndexWriter是索引过程的核心组件,经过IndexWriter能够建立新索引、更新索引、删除索引操做。IndexWriter须要经过Directory对索引进行存储操做。
  • Directory描述了索引的存储位置,底层封装了I/O操做,负责对索引进行存储。它是一个抽象类,它的子类经常使用的包括FSDirectory(在文件系统存储索引)、RAMDirectory(在内存存储索引)。
  • 在对Docuemnt中的内容索引以前须要使用分词器进行分词 ,分词的主要过程就是分词、过滤两步。 分词就是将采集到的文档内容切分红一个一个的词,具体应该说是将Document中Field的value值切分红一个一个的词。

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

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

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

参照:https://zhuanlan.zhihu.com/p/...

<font color="#0066CC">Lucene工做原理总结</font>

<font color="#00CC00">一、索引流程</font>

从原始文件中提取一些能够用来搜索的数据(封装成各类Field),把各field再封装成document,而后对document进行分析(对各字段分词),获得一些索引目录写入索引库,document自己也会被写入一个文档信息库;

<font color="#00CC00">二、搜索流程</font>

根据关键词解析(queryParser)出查询条件query(Termquery),利用搜索工具(indexSearcher)去索引库获取文档id,而后再根据文档id去文档信息库获取文档信息

分词器不一样,创建的索引数据就不一样;比较通用的一个中文分词器IKAnalyzer的用法

<font color="#00CC00">三、相关度得分</font>

a) 在创建索引的时候,能够给指定文档的指定域设置一个权重

Field.setBoosts()(如今5.5版本以前的是这样设置权重,后面的不是了)

b) 在搜索的时候,能够给不一样的搜索域设置不一样的权重

Boosts = new HashMap<String,Float>

MultiFieldsQueryParser(fields,analyzer,boosts)

欢迎关注个人微信公众号:“Java面试通关手册”(坚持原创,分享美文,分享各类Java学习资源,面试题,以及企业级Java实战项目回复关键字免费领取):
微信公众号

Lucene我想暂时先更新到这里,仅仅这三篇文章想掌握Lucene是远远不够的。另外我这里三篇文章都用的最新的jar包,Lucene更新太快,5系列后的版本和以前的有些地方仍是有挺大差距的,就好比为文档域设置权值的setBoost方法6.6之后已经被废除了等等。由于时间有限,因此我就草草的看了一下Lucene的官方文档,大多数内容仍是看java1234网站的这个视频来学习的,而后在版本和部分代码上作了改进。截止2018/4/1,上述代码所用的jar包皆为最新。

最后推荐一下本身以为还不错的Lucene学习网站/博客:

官方网站:Welcome to Apache Lucene

Github:Apache Lucene and Solr

Lucene专栏

搜索系统18:lucene索引文件结构

Lucene6.6的介绍和使用

相关文章
相关标签/搜索