美文网首页Java面试通关手册JAVA相关我爱编程
Lucene7.2.1系列(三)查询及高亮

Lucene7.2.1系列(三)查询及高亮

作者: Snailclimb | 来源:发表于2018-04-03 20:48 被阅读148次

    系列文章:

    Lucene系列(一)快速入门

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

    Lucene系列(三)查询及高亮

    <font color="#0066CC">一 准备</font>

    <font color="#00CC00">创建项目并添加Maven依赖</font>

            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>
            <!-- 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>
            <!-- 高亮显示 -->
            <dependency>
                <groupId>org.apache.lucene</groupId>
                <artifactId>lucene-highlighter</artifactId>
                <version>7.2.1</version>
            </dependency>
    
            <!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-analyzers-smartcn -->
            <!-- 中文分词 -->
            <dependency>
                <groupId>org.apache.lucene</groupId>
                <artifactId>lucene-analyzers-smartcn</artifactId>
                <version>7.2.0</version>
            </dependency>
        </dependencies>
    
    

    <font color="#0066CC">二 对特定单词查询/模糊查询和查询表达式</font>

    <font color="#00CC00">写索引</font>

    import java.io.File;
    import java.io.FileReader;
    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 Exception
         */
        public Indexer(String indexDir)throws Exception{
            Directory dir=FSDirectory.open(Paths.get(indexDir));
            Analyzer analyzer=new StandardAnalyzer(); // 标准分词器
            IndexWriterConfig iwc=new IndexWriterConfig(analyzer);
            writer=new IndexWriter(dir, iwc);
        }
        
        /**
         * 关闭写索引
         * @throws Exception
         */
        public void close()throws Exception{
            writer.close();
        }
        
        /**
         * 索引指定目录的所有文件
         * @param dataDir
         * @throws Exception
         */
        public int index(String dataDir)throws Exception{
            File []files=new File(dataDir).listFiles();
            for(File f:files){
                indexFile(f);
            }
            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
         */
        private Document getDocument(File f)throws Exception {
            Document doc=new Document();
            doc.add(new TextField("contents",new FileReader(f)));
            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\\searchindex";
            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)+" 毫秒");
        }
    }
    

    <font color="#00CC00">读取索引</font>

    
    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.index.Term;
    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.TermQuery;
    import org.apache.lucene.search.TopDocs;
    import org.apache.lucene.store.Directory;
    import org.apache.lucene.store.FSDirectory;
    import org.junit.After;
    import org.junit.Before;
    import org.junit.Test;
    
    public class SearchTest {
    
        private Directory dir;
        private IndexReader reader;
        private IndexSearcher is;
        
        @Before
        public void setUp() throws Exception {
            dir=FSDirectory.open(Paths.get("D:\\lucene\\searchindex"));
            reader=DirectoryReader.open(dir);
            is=new IndexSearcher(reader);
        }
    
        @After
        public void tearDown() throws Exception {
            reader.close();
        }
    }
    

    <font color="#00CC00">对特定单词查询和模糊查询</font>

    
        /**
         * 对特定单词查询及模糊查询
         * 
         * @throws Exception
         */
        @Test
        public void testTermQuery() throws Exception {
            String searchField = "contents";
            // 所给出的必须是单词,不然差不到
            String q = "authorship";
            // 一个Term表示来自文本的一个单词。
            Term t = new Term(searchField, q);
            // 为Term构造查询。
            Query query = new TermQuery(t);
             /** 
             * 1.需要根据条件查询 
             *  
             * 2.最大可编辑数,取值范围0,1,2 
             * 允许我的查询条件的值,可以错误几个字符 
             *  
             */  
            Query query2 = new FuzzyQuery(new Term(searchField,"authorshioo"),1);  
            TopDocs hits = is.search(query, 10);
            // hits.totalHits:查询的总命中次数。即在几个文档中查到给定单词
            System.out.println("匹配 '" + q + "',总共查询到" + hits.totalHits + "个文档");
            for (ScoreDoc scoreDoc : hits.scoreDocs) {
                Document doc = is.doc(scoreDoc.doc);
                System.out.println(doc.get("fullPath"));
            }
            TopDocs hits2 = is.search(query2, 10);
            // hits.totalHits:查询的总命中次数。即在几个文档中查到给定单词
            System.out.println("匹配 '" + "authorshioo"+ "',总共查询到" + hits2.totalHits + "个文档");
            for (ScoreDoc scoreDoc : hits2.scoreDocs) {
                Document doc = is.doc(scoreDoc.doc);
                System.out.println(doc.get("fullPath"));
            }
        }
    
    
    

    我们上面查询了单词“authorship”以及模糊查询了单词"authorshioo",结果如下:


    image

    可以看到只在LICENSE.txt文档下找到该单词。


    data
    那么模糊查询为什么查不到单词"authorshioo"呢?
    这是因为我们在这里允许可以错误几个字符为1个,但是我们单词"authorshioo"错误字符个数为2个,所以就查不到。
            Query query2 = new FuzzyQuery(new Term(searchField,"authorshioo"),1);  
    

    <font color="#00CC00">解析表达式的使用</font>

        /**
         * 解析查询表达式
         * 
         * @throws Exception
         */
        @Test
        public void testQueryParser() throws Exception {
            // 标准分词器
            Analyzer analyzer = new StandardAnalyzer();
            String searchField = "contents";
            String q = "atomic a atomicReader";
            String q2 = "AtomicReader and AtomicReaderContext";
            // 建立查询解析器
            //searchField:要查询的字段; 
            //analyzer:标准分词器实例
            QueryParser parser = new QueryParser(searchField, analyzer);
            Query query = parser.parse(q);
            //返回查询到的前10项(查到100个相关内容的话也只会返回10个)
            TopDocs hits = is.search(query, 10);
            System.out.println("匹配 " + q + "查询到" + hits.totalHits + "个记录");
            for (ScoreDoc scoreDoc : hits.scoreDocs) {
                Document doc = is.doc(scoreDoc.doc);
                System.out.println(doc.get("fullPath"));
            }
            
            QueryParser parser2 = new QueryParser(searchField, analyzer);
            Query query2 = parser2.parse(q2);
            //返回查询到的前10项(查到100个相关内容的话也只会返回10个)
            TopDocs hits2 = is.search(query2, 10);
            System.out.println("匹配 " + q2 + "查询到" + hits2.totalHits + "个记录");
            for (ScoreDoc scoreDoc : hits2.scoreDocs) {
                Document doc = is.doc(scoreDoc.doc);
                System.out.println(doc.get("fullPath"));
            }
        }
    

    我们上面分别查询了:“atomic a atomicReader”和“AtomicReader and AtomicReaderContext”,通过查询结果可以看出即使稍微改变查询内容,也还是可以查询到和我们给出的表达式相关的文档。


    查询结果

    <font color="#0066CC">三 中文查询及高亮</font>

    <font color="#00CC00">写索引</font>

    import java.nio.file.Paths;
    
    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.StringField;
    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 String[] ids={"1","2","3"};  
        private String citys[]={"青岛","南京","上海"};  
        private String descs[]={  
                "青岛是一个漂亮的城市。",  
                "南京是一个文化的城市。",  
                "上海是一个繁华的城市。"  
        };  
          
        private Directory dir;  
          
        /** 
         *实例化indexerWriter 
         * @return 
         * @throws Exception 
         */  
        private IndexWriter getWriter()throws Exception{  
      
            //中文分词器  
            SmartChineseAnalyzer analyzer=new SmartChineseAnalyzer();  
              
            IndexWriterConfig iwc=new IndexWriterConfig(analyzer);  
              
            IndexWriter writer=new IndexWriter(dir, iwc);  
              
            return writer;  
        }  
          
        /** 
         * 获取indexDir 
         * @param indexDir 
         * @throws Exception 
         */  
        private void index(String indexDir)throws Exception{  
              
            dir=FSDirectory.open(Paths.get(indexDir));  
              
            IndexWriter writer=getWriter();  
              
            for(int i=0;i<ids.length;i++){  
                  
                Document doc=new Document();  
                  
                doc.add(new StringField("id", ids[i], Field.Store.YES));  
                doc.add(new StringField("city",citys[i],Field.Store.YES));  
                doc.add(new TextField("desc", descs[i], Field.Store.YES));  
                  
                writer.addDocument(doc);   
            }  
              
            writer.close();  
        }  
          
          
        public static void main(String[] args) throws Exception {  
              
            new Indexer().index("D:\\lucene\\dataindex2");  
            System.out.println("Success Indexer");  
        }  
          
    }  
    

    <font color="#00CC00">中文查询及高亮显示</font>

    import java.io.StringReader;
    import java.nio.file.Paths;
    
    import org.apache.lucene.analysis.TokenStream;
    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.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.search.highlight.Fragmenter;
    import org.apache.lucene.search.highlight.Highlighter;
    import org.apache.lucene.search.highlight.QueryScorer;
    import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
    import org.apache.lucene.search.highlight.SimpleSpanFragmenter;
    import org.apache.lucene.store.Directory;
    import org.apache.lucene.store.FSDirectory;  
      
    /** 
     *  
     * 通过索引字段来读取文档 
     * @author LXY 
     * 
     */  
    public class SearchTest {  
      
        public static void search(String indexDir, String par) throws Exception{  
                      
                //得到读取索引文件的路径  
                Directory dir = FSDirectory.open(Paths.get(indexDir));  
                      
                //通过dir得到的路径下的所有的文件  
                IndexReader reader = DirectoryReader.open(dir);  
                      
                //建立索引查询器  
                IndexSearcher searcher = new IndexSearcher(reader);  
                      
                //中文分词器  
                SmartChineseAnalyzer analyzer=new SmartChineseAnalyzer();  
                      
                //建立查询解析器  
                /** 
                 * 第一个参数是要查询的字段; 
                 * 第二个参数是分析器Analyzer 
                 * */  
                QueryParser parser = new QueryParser("desc", analyzer);  
                      
                //根据传进来的par查找  
                Query query = parser.parse(par);  
                      
                //计算索引开始时间  
                long start = System.currentTimeMillis();  
                      
                //开始查询  
                /** 
                 * 第一个参数是通过传过来的参数来查找得到的query; 
                 * 第二个参数是要出查询的行数 
                 * */  
                TopDocs topDocs = searcher.search(query, 10);  
                      
                //索引结束时间  
                long end = System.currentTimeMillis();  
                      
                System.out.println("匹配"+par+",总共花费了"+(end-start)+"毫秒,共查到"+topDocs.totalHits+"条记录。");  
                      
                      
                //高亮显示start  
                      
                //算分  
                QueryScorer scorer=new QueryScorer(query);  
                      
                //显示得分高的片段  
                Fragmenter fragmenter=new SimpleSpanFragmenter(scorer);  
                      
            //设置标签内部关键字的颜色  
            //第一个参数:标签的前半部分;第二个参数:标签的后半部分。  
            SimpleHTMLFormatter simpleHTMLFormatter=new SimpleHTMLFormatter("<b><font color='red'>","</font></b>");  
                      
                //第一个参数是对查到的结果进行实例化;第二个是片段得分(显示得分高的片段,即摘要)  
                    Highlighter highlighter=new Highlighter(simpleHTMLFormatter, scorer);  
                      
                    //设置片段  
                    highlighter.setTextFragmenter(fragmenter);  
                      
                    //高亮显示end  
                      
                    //遍历topDocs  
                    /** 
                     * ScoreDoc:是代表一个结果的相关度得分与文档编号等信息的对象。 
                     * scoreDocs:代表文件的数组 
                     * @throws Exception  
                     * */  
                    for(ScoreDoc scoreDoc : topDocs.scoreDocs){  
                          
                        //获取文档  
                        Document document = searcher.doc(scoreDoc.doc);  
                          
                        //输出全路径  
                        System.out.println(document.get("city"));  
                        System.out.println(document.get("desc"));  
                          
                        String desc = document.get("desc");  
                        if(desc!=null){  
                              
                            //把全部得分高的摘要给显示出来  
                              
                //第一个参数是对哪个参数进行设置;第二个是以流的方式读入  
                TokenStream tokenStream=analyzer.tokenStream("desc", new StringReader(desc));  
                              
                //获取最高的片段  
                System.out.println(highlighter.getBestFragment(tokenStream, desc));  
                    }  
            }  
                      
            reader.close();  
        }  
                  
                  
        //开始测试  
        public static void main(String[] args) {  
                      
            //索引指定的路径  
            String indexDir = "D:\\lucene\\dataindex2";  
                      
            //查询的字段  
            String par = "南京";  
                      
            try {  
                          
                search(indexDir,par);  
                          
            } catch (Exception e) {  
            // TODO Auto-generated catch block  
                e.printStackTrace();  
            }  
        }         
    }  
    

    结果会把我们查询的“南京”单词给高亮显示,这在我们平时搜索中很常见了。


    高亮显示

    我们平时搜索中的高亮就像下图:


    image

    欢迎关注我的微信公众号(分享各种Java学习资源,面试题,以及企业级Java实战项目回复关键字免费领取):

    微信公众号

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

    最后推荐一下自己觉得还不错的Lucene学习网站/博客:

    官方网站:[Welcome to Apache Lucene](Welcome to Apache Lucene)

    Github:Apache Lucene and Solr

    Lucene专栏

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

    Lucene6.6的介绍和使用

    相关文章

      网友评论

      本文标题:Lucene7.2.1系列(三)查询及高亮

      本文链接:https://www.haomeiwen.com/subject/khochftx.html