搜索引擎之Lucene

综述:搜索引擎我们都不陌生,但是为什么要使用框架呢?而什么是Lucene呢?相比直接使用SQL搜索有哪些优点呢?下面逐一说明。

一、什么是Lucene

Lucene不是一个完整的全文检索引擎,而是一个全文检索引擎的 架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎。Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。说白了就是一个做索引的开源框架。Lucene只做索引和搜索工作。

二、了解数据的分类和全文检索
数据分为结构化数据和非结构化数据,我们知道SQL查询数据库的操作其实是很快的,因为数据库分有表有行和列,但是对于像word、pdf、excel等形式的没有具体书写规范的非结构化数据,在其中搜寻是很困难的。

非结构化数据的查询方法有两种:
1、顺序扫描法:比如要找内容包含某一个字符串的文件,就是一个文档一个文档的看,对于每一个文档,从头看到尾,如果此文档包含此字符串,则此文档为我们要找的文件,接着看下一个文件,直到扫描完所有的文件。如利用windows的搜索也可以搜索文件内容,只是相当的慢。

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

例如:字典。字典的拼音表和部首检字表就相当于字典的索引,对每一个字的解释是非结构化的,如果字典没有音节表和部首检字表,在茫茫辞海中找一个字只能顺序扫描。然而字的某些信息可以提取出来进行结构化处理,比如读音,就比较结构化,分声母和韵母,分别只有几种可以一一列举,于是将读音拿出来按一定的顺序排列,每一项读音都指向此字的详细解释的页数。我们搜索时按结构化的拼音搜到读音,然后按其指向的页数,便可找到我们的非结构化数据——也即对字的解释。
这种先建立索引,再对索引进行搜索的过程就叫全文检索(Full-text Search)。
虽然创建索引的过程也是非常耗时的,但是索引一旦创建就可以多次使用,全文检索主要处理的是查询,所以耗时间创建索引是值得的。

对于数据量大、数据结构不固定的数据可采用全文检索方式搜索,比如百度、Google等搜索引擎、论坛站内搜索、电商网站站内搜索等。

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

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

    三、全文检索的流程

    1、采集数据:第一步肯定是获取数据,采集数据(从网站爬取或连接数据库)就是为了创建索引,创建索引需要先将采集的原始数据加工为文档,再由文档分词产生索引。文档(Document) 中包含若干个Field域。
    2、创建检索:使用Lucene的相关jar包中的相关函数进行创建。
    3、用户搜索:提供用户搜索功能。
    4、搜索索引:把用户搜索的内容作为索引去索引库里面进行比对。
    5、反馈用户:把根据用户提供的相关搜索条件的内容返回给用户。

    四、Demo

    使用两个程序实现一个简单的demo

    1、创建索引

    package com.stitp.lucence;
    
    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;
    
    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 = "F:\\lucene";
            被索引数据的路径
            String dataDir = "F:\\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) + " 毫秒");
        }
    
    }
    

    运行主函数之后:
    在这里插入图片描述
    在这里插入图片描述
    2、全文检索测试

    package com.stitp.lucence;
    
    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;
    /**
     * 根据索引搜索
     */
    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 = "F:\\lucene";
            //我们要搜索的内容
            String q = "java";
            try {
                search(indexDir, q);
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    

    运行结果
    在这里插入图片描述
    转载自:Lucene7.2.1系列(一)快速入门

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值