• Lucene基础(2)


    上一篇:Lucene基础(1) 

    一、Lucene术语

    Document, Field, Term, Query, Analyzer相信在其中大多数在之前已经理解了...对其中部分概念详细说明

    Document是一个包含了多个Field的容器,通过以下代码应该容易理解二者的关系

      Document document=new Document();
            //Field.Store.YES或者NO(存储域选项)
            //设置为YES表示或把这个域中的内容完全存储到文件中,方便进行文本的还原
            //设置为NO表示把这个域的内容不存储到文件中,但是可以被索引,此时内容无法完全还原(doc.get)
            for(int i=0;i<ids.length;i++){
                document.add(new StringField("ids",ids[i], Field.Store.YES));
                document.add(new StringField("names",names[i], Field.Store.YES));
                document.add(new TextField("describes",describes[i], Field.Store.YES));
                indexWriter.addDocument(document);
            }

    Field的一般构造器:

    protected Field(String name, FieldType type)

    下面说明常见的FieldType

    http://lucene.apache.org/core/5_5_3/core/org/apache/lucene/document/FieldType.html

    (1) NumbericType

    public enum NumericType {
        /** 32-bit integer numeric type */
        INT, 
        /** 64-bit long numeric type */
        LONG, 
        /** 32-bit float numeric type */
        FLOAT, 
        /** 64-bit double numeric type */
        DOUBLE
      }

    如果是数值类型,可以通过NumbericType来指明

    (2) Stored

    private boolean stored;

    是否存储field的值。如果true,原始的字符串值全部被保存在索引中,并可以由IndexReader类恢复。该选项对于需要展示搜索结果的一些域很有用(如URL,标题等)。如果为false,则索引中不存储field的值,通常用来索引大的文本域值。如Web页面的正文

    (3) tokenized

     private boolean tokenized = true;

    是否使用分析器将域值分解成独立的语汇单元流。该属性仅当indexed()为true时有效.

    (4) 加权相关

    • private boolean storeTermVectors;当lucene建立起倒排索引后,默认情况下它会保存所有必要的信息实施Vector Space Model。该Model需要计算文档中出现的term数,以及他们出现的位置。该属性仅当indexed为true时生效。他会为field建立一个小型的倒排索引。
    • private boolean storeTermVectorOffsets;表示是否存储field的token character的偏移量到 term vectors向量中。
    • private boolean storeTermVectorPositions;表示是否存储field中token的位置到term vectors 向量中。
    • private boolean storeTermVectorPayloads;是否存储field中token的比重到term vectors中。
    • private boolean omitNorms;是否要忽略field的加权基准值,如果为true可以节省内存消耗,但在打分质量方面会有更高的消耗,另外你也不能使用index-time 进行加权操作。

    (5) IndexOptions

    // NOTE: order is important here; FieldInfo uses this
      // order to merge two conflicting IndexOptions (always
      // "downgrades" by picking the lowest).
      /** Not indexed */
      NONE,
      /** 
       * Only documents are indexed: term frequencies and positions are omitted.
       * Phrase and other positional queries on the field will throw an exception, and scoring
       * will behave as if any term in the document appears only once.
       */
      DOCS,
      /** 
       * Only documents and term frequencies are indexed: positions are omitted. 
       * This enables normal scoring, except Phrase and other positional queries
       * will throw an exception.
       */  
      DOCS_AND_FREQS,
      /** 
       * Indexes documents, frequencies and positions.
       * This is a typical default for full-text search: full scoring is enabled
       * and positional queries are supported.
       */
      DOCS_AND_FREQS_AND_POSITIONS,
      /** 
       * Indexes documents, frequencies, positions and offsets.
       * Character offsets are encoded alongside the positions. 
       */
      DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS,

    说明如下:

    • DOCS_ONLY:仅documents被索引,term的频率和位置都将被忽略。针对field的短语或有关位置的查询都将抛出异常。 
    • DOCS_AND_FREQS:documents和term的频率被索引,term的位置被忽略。这样可以正常打分,但针对field的短语或有关位置的查询都将抛出异常。 
    • DOCS_AND_FREQS_AND_POSITIONS:这是一个全文检索的默认设置,打分和位置检索都支持。 
    • DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS:索引字符相对位置的偏移量。

    (6) DocValueType

    /**
       * No doc values for this field.
       */
      NONE,
      /** 
       * A per-document Number
       */
      NUMERIC,
      /**
       * A per-document byte[].  Values may be larger than
       * 32766 bytes, but different codecs may enforce their own limits.
       */
      BINARY,
      /** 
       * A pre-sorted byte[]. Fields with this type only store distinct byte values 
       * and store an additional offset pointer per document to dereference the shared 
       * byte[]. The stored byte[] is presorted and allows access via document id, 
       * ordinal and by-value.  Values must be {@code <= 32766} bytes.
       */
      SORTED,
      /** 
       * A pre-sorted Number[]. Fields with this type store numeric values in sorted
       * order according to {@link Long#compare(long, long)}.
       */
      SORTED_NUMERIC,
      /** 
       * A pre-sorted Set&lt;byte[]&gt;. Fields with this type only store distinct byte values 
       * and store additional offset pointers per document to dereference the shared 
       * byte[]s. The stored byte[] is presorted and allows access via document id, 
       * ordinal and by-value.  Values must be {@code <= 32766} bytes.
       */
      SORTED_SET,

    如果非空,field的值将被索引成docValues. 

    • NUMERIC:数字类型 
    • BINARY:二进制类型 
    • SORTED:只保存不同的二进制值 byte[] 
    • SORTED_SET.

    (7) frozen

    阻止field属性未来可能的变更,该属性通常在FieldType 属性已经被设置后调用。是为了防止无意识的变更

    二、增删改查演示

    原文链接:http://www.kailing.pub/index/columns/colid/16.html

    public class IndexerCRUD {
        //测试数据,模拟数据库表结构
        private static String[] ids={"1","2","3"}; //用户ID
        private static String [] names={"kl","wn","sb"};
        private static String [] describes={"shi yi ge mei nan zi","Don't know","Is an idiot
    "};
        //索引存储地址
        private static String indexDir="G:\projects-helloworld\lucene\src\main\resources\LuceneIndex";
        /**
         * 获取操作索引实体,并添加测试数据
         * @param indexDir 索引存储位置
         * @return
         * @throws Exception
         */
        public static IndexWriter getIndexWriter(String indexDir)throws Exception{
            IndexWriterConfig writerConfig=new IndexWriterConfig(getAnalyzer());
            IndexWriter indexWriter=new IndexWriter(getDirectory(indexDir),writerConfig);
            Document document=new Document();
            //Field.Store.YES或者NO(存储域选项)
            //设置为YES表示或把这个域中的内容完全存储到文件中,方便进行文本的还原
            //设置为NO表示把这个域的内容不存储到文件中,但是可以被索引,此时内容无法完全还原(doc.get)
            for(int i=0;i<ids.length;i++){
                document.add(new StringField("ids",ids[i], Field.Store.YES));
                document.add(new StringField("names",names[i], Field.Store.YES));
                document.add(new TextField("describes",describes[i], Field.Store.YES));
                indexWriter.addDocument(document);
            }
            return indexWriter;
        }
        /**
         * 得到默认分词器
         * @return
         */
        public static Analyzer getAnalyzer(){
            return  new StandardAnalyzer();
        }
        /**
         * 得到索引磁盘存储器
         * @param indexDir 存储位置
         * @return
         */
        public static Directory getDirectory(String indexDir){
            Directory directory=null;
            try {
                directory= FSDirectory.open(Paths.get(indexDir));
            }catch (Exception e){
                e.printStackTrace();
            }
            return  directory;
        }
        /**
         * 获取读索引实体,并打印读到的索引信息
         * @return
         */
        public  static IndexReader getIndexReader(){
            IndexReader reader=null;
            try {
                reader= DirectoryReader.open(getDirectory(indexDir));
                //通过reader可以有效的获取到文档的数量
                System.out.println("当前存储的文档数::"+reader.numDocs());
                System.out.println("当前存储的文档数,包含回收站的文档::"+reader.maxDoc());
                System.out.println("回收站的文档数:"+reader.numDeletedDocs());
    
            } catch (Exception e) {
                e.printStackTrace();
            }
            return reader;
        }
    
    
        /**
         * 写索引测试,借助Luke观察结果
         * @throws Exception
         */
        @Test
        public void Testinsert() throws  Exception{
            IndexWriter writer=getIndexWriter(indexDir);
            writer.close();
            getIndexReader();
        }
        /**
         * 删除索引测试,借助Luke观察结果
         * @throws Exception
         */
        public void TestDelete()throws Exception{
            //测试删除前我们先把上次的索引文件删掉,或者换个目录
            IndexWriter writer=getIndexWriter(indexDir);
            QueryParser parser=new QueryParser("ids", getAnalyzer());//指定Document的某个属性
            Query query=parser.parse("2");//指定索引内容,对应某个分词
            Term term=new Term("names","kl");
            //参数是一个选项,可以是一个query,也可以是一个term,term是一个精确查找的值
            writer.deleteDocuments(query);//此时删除的文档并不会被完全删除,而是存储在一个回收站中的,可以恢复
            writer.forceMergeDeletes();//强制合并删除的索引信息,索引量大的时候不推荐使用,真正的删除
            // writer.commit(); //更改索引要提交,和提交数据库事务一个概念,真正的删除
            writer.close();
            getIndexReader();
        }
        /**
         * 更新操作测试,借助Luke观察结果
         * @throws Exception
         */
        public void TestUpdate()throws Exception{
            // Lucene并没有提供更新,这里的更新操作相当于新增,他并不会去掉原来的信息
            IndexWriter writer = getIndexWriter(indexDir);
            try {
                Document doc = new Document();
                doc.add(new StringField("id","1",Field.Store.YES));
                doc.add(new StringField("names","ckl",Field.Store.YES));
                doc.add(new StringField("describes","chenkailing",Field.Store.NO));
                writer.updateDocument(new Term("id","1"), doc);
    
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if(writer!=null) writer.close();
            }
        }
        /**
         * 查询测试
         */
        @Test
        public void TestSearchaer(){
            try {
                IndexReader reader = getIndexReader();
                IndexSearcher searcher = new IndexSearcher(reader);
                QueryParser parser=new QueryParser("names", getAnalyzer());//指定Document的某个属性
                Query query=parser.parse("kl");//指定索引内容,对应某个分词
                Term term=new Term("names","kl");
                //参数是一个选项,可以是一个query,也可以是一个term,term是一个精确查找的值
                TopDocs hits = searcher.search(query, 10);
                for(ScoreDoc sd:hits.scoreDocs) {
                    Document doc = searcher.doc(sd.doc);
                    System.out.println(
                            doc.get("names")+"["+doc.get("describes")+"]-->"+doc.get("ids"));
                }
                reader.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

     三、常见Query用法

    Query 是一个用于查询的抽象基类。搜索指定单词或词组涉及到在项中包装它们,将项添加到查询对象,将查询对象传递到 IndexSearcher 的搜索方法。

    Lucene 包含各种类型的具体查询实现,比如 TermQuery、BooleanQuery、PhraseQuery、PrefixQuery、RangeQuery、MultiTermQuery、FilteredQuery、SpanQuery 等。以下部分讨论 Lucene 查询 API 的主查询类。

    Basic: QueryParser和ScoreDoc

    QueryParser 对于解析人工输入的查询字符非常有用。您可以使用它将用户输入的查询表达式解析为 Lucene 查询对象,这些对象可以传递到 IndexSearcher 的搜索方法。它可以解析丰富的查询表达式。 QueryParser 内部将人们输入的查询字符串转换为一个具体的查询子类。您需要使用反斜杠()将 *、? 等特殊字符进行转义。您可以使用运算符 AND、OR 和 NOT 构建文本布尔值查询。

    QueryParser queryParser = new QueryParser("subject",new StandardAnalyzer());
    // Search for emails that contain the words 'job openings' and '.net' and 'pune'
    Query query = queryParser.parse("job openings AND .net AND pune");

    indexSearcher 返回一组对分级搜索结果(如匹配给定查询的文档)的引用。您可以使用 IndexSearcher 的搜索方法确定需要检索的最优先搜索结果数量。可以在此基础上构建定制分页。您可以添加定制 Web 应用程序或桌面应用程序来显示搜索结果。检索搜索结果涉及的主要类包括 ScoreDoc 和 TopDocs。

    ScoreDoc: 搜索结果中包含一个指向文档的简单指针。这可以封装文档索引中文档的位置以及 Lucene 计算的分数。 
    封装搜索结果以及 ScoreDoc 的总数。 
    以下代码片段展示了如何检索搜索结果中包含的文档。

    /* First parameter is the query to be executed and 
    second parameter indicates the no of search results to fetch */ 
    TopDocs topDocs = indexSearcher.search(query,20); 
    System.out.println(“Total hits “+topDocs.totalHits);
    
    // Get an array of references to matched documents 
    ScoreDoc[] scoreDosArray = topDocs.scoreDocs; 
    for(ScoreDoc scoredoc: scoreDosArray){ 
    //Retrieve the matched document and show relevant details 
      Document doc = indexSearcher.doc(scoredoc.doc); 
      System.out.println(“
    Sender: “+doc.getField(“sender”).stringValue()); 
      System.out.println(“Subject: “+doc.getField(“subject”).stringValue()); 
      System.out.println(“Email file location: ” +doc.getField(“emailDoc”).stringValue()); 
    }

    3.1 TermQuery

    搜索索引最基本的查询类型。可以使用单个项构建TermQuery。项值应该区分大小写,但也并非全是如此。注意,传递的搜索项应该与文档分析得到的项一致,因为分析程序在构建索引之前对原文本执行许多操作。 
    例如,考虑电子邮件标题 “Job openings for Java Professionals at Bangalore”。假设您使用 StandardAnalyzer 编制索引。现在如果我们使用 TermQuery 搜索 “Java”,它不会返回任何内容,因为本文本应该已经规范化,并通过 StandardAnalyzer 转成小写。如果搜索小写单词 “java”,它将返回所有标题字段中包含该单词的邮件。

    //Search mails having the word "java" in the subject field
    Searcher indexSearcher = new IndexSearcher(indexDirectory);
    Term term = new Term("subject","java");
    Query termQuery = new TermQuery(term);
    TopDocs topDocs = indexSearcher.search(termQuery,10);

    3.2 RangeQuery

    您可以使用 RangeQuery 在某个范围内搜索。索引中的所有项都以字典顺序排列。Lucene 的 RangeQuery 允许用户在某个范围内搜索项。该范围可以使用起始项和最终项(包含两端或不包含两端均可)指定。

    /* RangeQuery example:Search mails from 01/06/2009 to 6/06/2009
    both inclusive */
    Term begin = new Term("date","20090601");
    Term end = new Term("date","20090606");
    Query query = new RangeQuery(begin, end, true);

    3.3 PrefixQuery

    您可以使用 PrefixQuery 通过前缀单词进行搜索,该方法用于构建一个查询,该查询查找包含以指定单词前缀开始的词汇的文档。

    //Search mails having sender field prefixed by the word 'job'
    PrefixQuery prefixQuery = new PrefixQuery(new Term("sender","job"));
    PrefixQuery query = new PrefixQuery(new Term("sender","job"));

    3.4 BooleanQuery

    您可以使用 BooleanQuery 组合任何数量的查询对象,构建强大的查询。它使用 query 和一个关联查询的子句,指示查询是应该发生、必须发生还是不得发生。在 BooleanQuery 中,子句的最大数量默认限制为 1,024。您可以调用 setMaxClauseCount 方法设置最大子句数。

    // Search mails have both 'java' and 'bangalore' in the subject field
    Query query1 = new TermQuery(new Term("subject","java"));
    Query query2 = new TermQuery(new Term("subject","bangalore"));
    BooleanQuery query = new BooleanQuery();
    query.add(query1,BooleanClause.Occur.MUST);
    query.add(query2,BooleanClause.Occur.MUST);

    3.5 PhraseQuery

    您可以使用 PhraseQuery 进行短语搜索。PhraseQuery 匹配包含特定单词序列的文档。PhraseQuery 使用索引中存储的项的位置信息。考虑匹配的项之间的距离称为 slop。默认情况下,slop 的值为零,这可以通过调用 setSlop 方法进行设置。PhraseQuery 还支持多个项短语。

    /* PhraseQuery example: Search mails that have phrase 'job opening j2ee'
       in the subject field.*/
    PhraseQuery query = new PhraseQuery();
    query.setSlop(1);
    query.add(new Term("subject","job"));
    query.add(new Term("subject","opening"));
    query.add(new Term("subject","j2ee"));

    3.6 WildcardQuery

    WildcardQuery 实现通配符搜索查询,这允许您搜索 arch*(可以查找包含 architect、architecture 等)之类的单词。使用两个标准通配符: 
    * 表示零个以上 
    ? 表示一个以上 
    如果使用以通配符查询开始的模式进行搜索,则可能会引起性能的降低,因为这需要查询索引中的所有项以查找匹配文档。

    //Search for 'arch*' to find e-mail messages that have word 'architect' in the subject
    field./
    Query query = new WildcardQuery(new Term("subject","arch*"));

    3.7 FuzzyQuery

    您可以使用 FuzzyQuery 搜索类似项,该类匹配类似于指定单词的单词。类似度测量基于 Levenshtein(编辑距离)算法进行。在列表 9 中,FuzzyQuery 用于查找与拼错的单词 “admnistrtor” 最接近的项,尽管这个错误单词没有索引。

    /* Search for emails that have word similar to 'admnistrtor' in the
    subject field. Note we have misspelled admnistrtor here.*/
    Query query = new FuzzyQuery(new Term("subject", "admnistrtor"));
  • 相关阅读:
    response输出随机图片、定时刷新网页
    @Transactional注解使用心得
    mybatis缓存(一,二级别)
    数据库四大特性及数据库隔离级别
    mybatis @SelectKey加于不加的区别
    MYSQL索引类型+索引方法
    页面缓存例子
    概率生成模型超越神经网络
    生成学习
    过程量与状态量
  • 原文地址:https://www.cnblogs.com/carl10086/p/6021424.html
Copyright © 2020-2023  润新知