zoukankan      html  css  js  c++  java
  • Lucene 基础知识

    1. 数据分类

    • 结构化数据: 指具有固定格式或有限长度的数据,如数据库等;
    • 非结构化数据: 指不定长或无固定格式的数据, 如邮件,word 文档等磁盘上的文件;

    1.1 非结构化数据查询方法

    1. 顺序扫描法(Serial Scanning)
    2. 全文检索(Full-text Search)
      • 将非结构化数据中的一部分信息提取出来,重新组织,使其变得有一定结构,然后对此有一定结构的数据进行搜索,
        从而达到搜索相对较快的目的; 这部分从非结构化数据中提取出,然后重新组织的信息,称之为索引, 例如字典.
      • 这种先建立索引,然后再对索引进行搜索的过程就叫全文检索;

    2. Lucene 概述

    • Lucene 是 apache 下的一个开放源代码的全文检索引擎工具包,提供了完整的查询引擎和索引引擎,部分文本分析引擎;

    2.1 Lucene 实现全文检索的流程

    2.2 创建文档对象

    • 获取原始内容的目的是为了索引,在索引前,需要将原始内容创建成文档(Document),文档中包括一个一个的域(Field),
      域中存储内容;
    • 我们可以将磁盘上的一个文件当成一个 document, Document 中包括一些Field(file_name 文件名称, file_path
      文件路径, file_size 文件大小, file_content 文件内容);
    • 每一个 Document 可以有多个 Field,同一个Document,可以有相同的 Field(域名和域值都相同);
    • 每一个 Document 都有一个唯一的编号,就是文档 id;

    2.3 分析文档

    • 将原始内容创建为包含域(Field)的文档(document),需要再对域中的内容进行分析,分析的过程是经过对原始文档提取单词,
      将字母转为小写,去除标点符号,去除停用词等过程生成最终的语汇单元,可以将语汇单元理解为一个一个的单词;
    • 每一个单词叫做一个Term,不同的域中拆分出来的相同的单词是不同的term; term中包含两部分,一部分是文档的域名, 另一部分
      是单词的内容;
    • Field 域的属性
      • 是否分析: 是否对域的内容进行分词处理;
      • 是否索引: 将 Field 分析后的词或整个 Field 值进行索引,只有建立索引,才能搜索到;
      • 是否存储: 存储在文档中的 Field 才可以从 Document 中获取;

    2.4 创建索引

    • 对所有文档分析得出的语汇单元进行索引,索引的目的是为了搜索,最终要实现只搜索被索引的语汇单元从而找到 Document;
      这种索引的结构叫倒排索引结构;
    • 传统方法是根据文件找到该文件的内容,在文件内容中匹配搜索关键字,这种方法是顺序扫描方法,数据量大,搜索慢;
    • 倒排索引结构是根据内容(词语)找文档; 顺序扫描方法是根据文档查找里面的内容;

    // 创建索引库
    // 环境: Lucene 4.10.3
    // jar 包
        /*
         * lucene-core-4.10.3
         * lucene-analyzers-common-4.10.3
         * lucene-queryparser-4.10.3
         * commons-io
         * junit
         */
    
    // 测试类
    public class FirstLucene{
    
        // 创建索引
        @Test
        public void testIndex() throws Exception{
            // 1. 创建一个 indexWriter 对象 new IndexWriter(arg0, arg1);
            //     arg0: 指定索引库的存放位置(Directory 对象)
            //     arg1: config
            // FSDirectory: File System Directory : 磁盘存储
            // Directory directory = new RAMDirectory(); 保存索引到内存中
            Directory directory = FSDirectory.open(new File("/Users/用户名/Documents/dic"));
    
            // 指定一个分词器
            Analyzer analyzer = new StandardAnalyzer();
            IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_10_3,analyzer);
    
            // 创建 indexWriter 对象
            IndexWriter indexWriter = new IndexWriter(directory, config);
    
            // 指定原始文件的目录
            File f = new File("/Users/用户名/Documents/searchsource");
            File[] listFiles = f.listFiles();
    
            for(File file : listFiles){
                // 创建文档对象
                Document document = new Document();
    
                // 文件名称
                String file_name = file.getName();
                Field fileNameField = new TextField("fileName",file_name,Store.YES);
    
                // 文件大小
                long file_size = FileUtils.sizeOf(file);
                Field fileSizeField = new LongField("fileSize",file_size, Store.YES);
    
                // 文件路径
                String file_path = file.getPath();
                Field filePathField = new StoredField("filePath",file_path);
    
                // 文件内容
                String file_content = FileUtils.readFileToString(file);
                Field fileContentField = new TextField("fileContent",file_content,Store.YES);
    
                document.add(fileNameField);
                document.add(fileSizeField);
                document.add(filePathField);
                document.add(fileContentField);
    
                // 使用indexWriter 对象将 document 对象写入索引库,此过程将 索引和document 对象写入索引库
                indexWriter.addDocument(document);
            }
    
            // 关闭 IndexWriter 对象
            indexWriter.close();
        }
    }
    
    
    // 查看分词完成后的文件: Luke
    java -jar lukeall-4.10.3.jar

    3. 查询索引

    3.1 创建查询

    • 用户输入查询关键字执行搜索前,需要先创建一个查询对象,查询对象中可以指定查询要搜索的 Field 文档域,查询关键字等,
      查询对象会生成具体的查询语法;
    • 例如: fileName:lucene: 表示要搜索Field域的内容为"lucene"的文档;

    3.2 执行查询

    • 根据查询语法在倒排索引词典表中分别找出对应搜索词的索引,从而找到索引所链接的文档链表;
    • 比如: fileName:lucene 的搜索过程: 在索引上查找域为 fileName, 并且关键字为Lucene的term, 并根据 term 找到
      文档 id 列表;

    3.3 渲染结果

    3.4 IndexSearcher 搜索方法

    // 查询索引
    /*
     * 步骤:
     *    1. 创建一个 Directory 对象,用于指定索引库存放的位置;
     *    2. 创建一个 indexReader 对象, 需要指定 Directory 对象, 用于读取索引库中的文件;
     *    3. 创建一个 indexSearcher 对象, 需要指定 indexReader 对象;
     *    4. 创建一个 TermQuery 对象,指定查询的域和查询的关键词
     *    5. 执行查询
     *    6. 返回查询结果,遍历查询结果并输出;
     *    7. 关闭 indexReader
     */
    
     public class IndexSearchTest{
    
        @Test
        public void testIndexSearch() throws Exception{
    
            Directory directory = FSDirectory.open(new File("/Users/用户名/Documents/dic"));
    
            IndexReader indexReader = DirectoryReader.open(directory);
    
            IndexSearcher indexSearcher = new IndexSearcher(indexReader);
    
            // 创建一个 TermQuery 对象,指定查询的域和查询的关键词
            Query query = new TermQuery(new Term("fileName","java"));
            // 执行查询
            TopDocs topDocs = indexSearcher.search(query,2);
    
            SocreDoc[] scoreDocs = topDocs.scoreDocs;
            for(ScoreDoc scoreDoc : scoreDocs){
                // 获取文档 id
                int docID = scoreDoc.doc;
    
                // 通过id,从索引中读取出对应的文档
                Document document = indexReader.document(docID);
                // 获取文件名称
                System.out.println(document.get("fileName"));
                // 获取文件内容
                System.out.println(document.get("fileContent"));
                // 文件路径
                System.out.println(document.get("filePath"));
                // 文件大小
                System.out.println(document.get("fileSize"));
    
                System.out.println("=======================");
            }
    
            indexReader.close();
        }
    }

    4. 支持中文分词器(IKAnalyzer)

    4.1 分词器(Analyzer)的执行过程

    • 从一个 Reader 字符流开始,创建一个基于 Reader 的 Tokenizer分词器,经过三个 TokenFilter,生成语汇单元 Tokens;
    • 如果要查看分词器的分词效果,只需要看Tokenstream中的内容就可以了,每个分词器都有一个方法tokenStream,返回一个
      tokenStream 对象;

    // 查看标准分词器的分词效果
    public void testTokenStream() throws Exception {
            //创建一个标准分析器对象
            Analyzer analyzer = new StandardAnalyzer();
            //获得tokenStream对象
            //第一个参数:域名,可以随便给一个
            //第二个参数:要分析的文本内容
            TokenStream tokenStream = analyzer.tokenStream("test",
                                    "The Spring Framework provides a comprehensive"
                                          +"programming and configuration model.");
    
            //添加一个引用,可以获得每个关键词
            CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);
            //添加一个偏移量的引用,记录了关键词的开始位置以及结束位置
            OffsetAttribute offsetAttribute = tokenStream.addAttribute(OffsetAttribute.class);
            //将指针调整到列表的头部
            tokenStream.reset();
            //遍历关键词列表,通过incrementToken方法判断列表是否结束
            while(tokenStream.incrementToken()) {
                //关键词的起始位置
                System.out.println("start->" + offsetAttribute.startOffset());
                //取关键词
                System.out.println(charTermAttribute);
                //结束位置
                System.out.println("end->" + offsetAttribute.endOffset());
            }
            tokenStream.close();
        }

    5.索引库的维护

    // 索引库维护: 就是索引的增删改查
    
    public class LuceneManager{
    
        public IndexWriter getIndexWriter(){
            Directory directory = FSDirectory.open(new File("/Users/用户名/Documents/dic"));
            Analyzer analyzer = new StandardAnalyzer();
            IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer);
    
            return new IndexWriter(directory,config);
        }
    
        // 全删除
        @Test
        public void testAllDelete() throws Exception{
            IndexWriter indexWriter = getIndexWriter();
            indexWriter.deleteAll();
            indexWriter.close();
        }
    
        // 根据条件删除
        @Test
        public void testDelete() throws Exception{
            IndexWriter indexWriter = getIndexWriter();
            Query query = new TermQuery(new Term("fileName","apache"));
            indexWriter.deleteDocuments(query);
            indexWriter.close();
        }
    
        // 修改
        @Test
        public void testUpdate() throws Exception{
            IndexWriter indexWriter = getIndexWriter();
            Document doc = new Document();
            doc.add(new TextField("fileN","测试文件名",Store.YES));
            doc.add(new TextField("fileC","测试文件内容",Store.YES));
    
            // 将 lucene 删除,然后添加 doc
            indexWriter.updateDocument(new Term("fileName","lucene"),doc, new IKAnalyzer());
            indexWriter.close();
        }
    }

    6. 索引库查询

    1. 对要搜索的信息创建 Query 查询对象,Lucene会根据 Query 查询对象生成最终的查询语法;
    2. 可通过两种方法创建查询对象:
      • 使用 Lucene 提供的 Query子类;
      • 使用 QueryParse 解析查询表达式, 需要加入lucene-queryparser-4.10.3.jar
    public class LuceneManager{
    
        // 获取 IndexSearcher
        public IndexSearcher getIndexSearcher() throws Exception{
            Directory directory = FSDirectory.open(new File("/Users/用户名/Documents/dic"));
            IndexReader indexReader = DirectoryReader.open(directory);
    
            return new IndexSearcher(indexReader);
        }
    
        // 获取执行结果
        public void printResult(IndexSearcher indexSearcher, Query query) throws Exception{
            TopDocs topDocs = indexSearcher.search(query,10);
    
            ScoreDoc[] scoreDocs = topDocs.scoreDocs;
            for(ScoreDoc scoreDoc : scoreDocs){
                int doc = scoreDoc.doc;
                Document document = indexSearcher.doc(doc);
    
                String fileName = docment.get("fileName");
                System.out.println(fileName);
    
                String fileContent = document.get("fileContent");
                System.out.println(fileContent);
    
                String fileSize = document.get("fileSize");
                System.out.println(fileSize);
    
                String filePath = document.get("filePath");
                System.out.println(filePath);
    
                System.out.println("======================");
            }
        }
    
        // 查询所有
        @Test
        public void testMatchAllDocsQuery() throws Exception{
            IndexSearcher indexSearcher = getIndexSearcher();
    
            Query query = new MatchAllDocsQUery();
    
            printResult(indexSearcher,query);
            // 关闭资源
            indexSearcher.getIndexReader().close();
        }
    
        // 精准查询(TermQuery)
    
    
        // NumericRangeQuery(按数值范围查询)
        @Test
        public void testNumericRangeQuery() throws Exception{
            IndexSearcher indexSearcher = getIndexSearcher();
    
            /*
             * 创建查询
             *     参数: 域名, 最小值, 最大值, 是否包含最小值, 是否包含最大值
             */
             Query query = NumericRangeQuery.newLongRange("fileSize",100L,200L,true,true);
    
             // 执行查询
             printResult(query,indexSearcher);
        }
    
        // BooleanQuery(组合查询)
        public void testBooleanQuery() throws Exception{
            IndexSearcher indexSearcher = getIndexSearcher();
    
            BooleanQuery booleanQuery = new BooleanQuery();
    
            Query query1 = new TermQuery(new Term("fileName","apache"));
            Query query2 = new TermQuery(new Term("fileName","lucene"));
    
            // Occur.MUST: 必须满足此条件, 相当于 and
            // Occur.SHOULD: 应该满足此条件, 但是不满足也可以, 相当于 or
            // Occur.MUST_NOT: 必须不满足, 相当于 not
            booleanQuery.add(query1,Occur.SHOULD);
            booleanQuery.add(query2,Occur.SHOULD);
    
            printResult(indexSearcher,booleanQuery);
            // 关闭资源
            indexSearcher.getIndexReader().close();
        }
    
    
        // 使用 QueryParse 解析查询表达式
        @Test
        public void testQueryParser() throws Exception{
            IndexSearcher indexSearcher = getIndexSearcher();
    
            // 创建 QueryParser 对象, 其中 arg0: 表示默认查询域, arg1: 分词器
            QueryParser queryParser = new QueryParser("fileName",new IKAnalyzer());
    
            // 此时,表示使用默认域: fileName
            // Query query = queryParser.parse("apache");
            // 表示查询 fileContent 域
            Query query = queryParser.parse("fileContent:apache");
    
            printResult(indexSearcher, query);
            // 关闭资源
            indexSearcher.getIndexReader().close();
        }
    
        // 指定多个默认搜索域
        @Test
        public void testMultiFieldQueryParser() throws Exception{
            IndexSearcher indexSearcher = getIndexSearcher();
            // 指定多个默认搜索域
            String[] fields = {"fileName", "fileContent"};
    
            // 创建 MultiFiledQueryParser 对象
            MultiFieldQueryParser queryParser = new MultiFieldQueryParser(fields, new IKAnalyzer());
            Query query = queryParser.parse("apache");
    
            // 输出查询条件
            System.out.println(query);
    
            // 执行查询
            printResult(indexSearcher, query);
            // 关闭资源
            indexSearcher.getIndexReader().close();
        }
    }
  • 相关阅读:
    Maven环境搭建、调试、打包
    JAVA环境变量JAVA_HOME、CLASSPATH、PATH设置详解
    Activiti工作流引擎核心介绍
    NodeJS概述
    JRE集成到Tomcat
    ORACLE递归查询(适用于ID,PARENTID结构数据表)
    爬虫入门——02
    爬虫入门——01
    利用java.lang.reflect.Constructor动态实例化对象
    【java入门点滴】向上转型与向下转型
  • 原文地址:https://www.cnblogs.com/xc1234/p/9021213.html
Copyright © 2011-2022 走看看