Lucene结合JavaEE建立简单搜索引擎(Java 9读取有坑)

  最近试用了一下Lucene这个搜索框架,发觉还是蛮不错的。许多现在比较火的搜索服务器(如Elasticsearch)的内核用的就是Lucene。目前手头有一个小的JavaEE项目程序,想着再给它装一个Elasticsearch服务器似乎太浪费资源,所需要的搜索功能其实很简单,不需要太复杂。于是,何不趁机学学它的内核框架Lucene如何使用呢?

  使用Lucene有很多种模式,本文就拿最简单的模式讲起,其基本流程如下:

  1、用Lucene的包读取所需的数据 --> 创建索引 --> 保存索引文件

  2、用Lucene的包读取索引文件 --> 使用搜索服务

  了解这两个流程以后,基本上对Lucene的使用就能大体了解,剩下的无非是怎么读要检索的数据源(从数据库、文件、还是其它网络服务器),怎么建立索引(哪个域需要分词和索引、哪些数据不需要分词只需要单独的存储、使用什么分词器、中文还是其它什么语言)

  目前项目的需求是为项目内的静态网页提供搜索服务,而网页的内容都是中文,因此还需要经过加工和分词的过程。最终的目标,是做一个类似简单的百度的功能。以下是获取到数据后的索引建立部分代码(INDEXDIR为存储索引的目录):

    private static void index(List<String[]> webAndContentList) throws IOException{
        Directory dir = FSDirectory.open(Paths.get(INDEXDIR));
        Analyzer analyzer;
        analyzer = getDefaultAnalyzer();
        IndexWriterConfig iwc = new IndexWriterConfig(analyzer);
        try (IndexWriter writer = new IndexWriter(dir, iwc)) {
            for (String[] webAndContent : webAndContentList) {
                Document doc = new Document();
                doc.add(new StringField("pagename", webAndContent[0], Field.Store.YES));
                doc.add(new TextField("content", webAndContent[1], Field.Store.YES));
                doc.add(new StringField("index", webAndContent[2], Field.Store.YES));
                doc.add(new StringField("title", webAndContent[3], Field.Store.YES));
                doc.add(new StringField("img", webAndContent[4], Field.Store.YES));
                System.out.println(Arrays.toString(webAndContent));
                writer.addDocument(doc); // 添加文档
            }
        }
    }

 此时需要引用(注意,使用nio部分的包在java 9里边可能会遇到读取问题):

import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import java.nio.file.Paths;

  实际上的搜索效果,很大程度取决于数据源怎么筛选 和 分词器的选择,需要不断地去尝试。因此,推荐实际使用前,先建立一个本地的JavaSE程序,小规模地建立索引,并且把分词的部分做成可拆卸的,测试测试搜索效果。如果是要索引网页,那么就得事先对网页数据源做一些处理,比如去掉script标签、html标签,去掉js部分,css部分的代码,只把网页的主题内容保留下来,毕竟,我们是为了搜索网页的内容,而不是网页的代码部分(有一次忘记去掉标签,结果导致分词器爆炸,几MB网页得出快1GB的索引,全都废掉了)。

  使用java的try-with-resource功能可以避免写file.close类似的冗余代码。接下来就是读取索引文件并进行搜索的部分代码:

    public static List< SearchResultEntity> search(String q) throws IOException, InvalidTokenOffsetsException, ParseException{
        List< SearchResultEntity> m = new ArrayList<>();
        Directory dir = FSDirectory.open(Paths.get(INDEXDIR));
        try (IndexReader reader = DirectoryReader.open(dir)) {
            IndexSearcher is = new IndexSearcher(reader);
            Analyzer analyzer;
            analyzer = getDefaultAnalyzer();
            
            QueryParser parser = new QueryParser("content", analyzer);//进行分析的字段
            Query query = parser.parse(q);

            TopDocs hits = is.search(query, 10);//查询结果选择前10个

            QueryScorer scorer = new QueryScorer(query);
            Fragmenter fragmenter = new SimpleSpanFragmenter(scorer);
            String preTag = "\0";
            String postTag = "\1";
            SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter(preTag, postTag);//设置查询结果红色字体加粗(可自行修改默认为粗体)
            Highlighter highlighter = new Highlighter(simpleHTMLFormatter, scorer);
            highlighter.setTextFragmenter(fragmenter);
            ScoreDoc[] scoreDoclist = hits.scoreDocs;
            for (ScoreDoc scoreDoc : scoreDoclist) {
                
                Document doc = is.doc(scoreDoc.doc);
                
                String desc = doc.get("content");
                
                if (desc != null) {
                    TokenStream tokenStream = analyzer.tokenStream("content", new StringReader(desc));
                    String[] onePageResultList = highlighter.getBestFragments(tokenStream, desc, 10);

                    String website = doc.get("pagename");
                    String title = doc.get("title");
                    String img = doc.get("img");
                    List<String> htmlList = new ArrayList<>();
                    // 每个页面单独有这么多搜索结果
                    for(String singleResult:onePageResultList){
                        htmlList.add(getKeyContent(singleResult, preTag, postTag));
                    }
                    SearchResultEntity e = new SearchResultEntity(website, htmlList, title, img);
                    m.add(e);
                }
                
            }// End of For Loop --------------------------------------------
        }
        return m;
    }

其中SearchResultEntity为自己创作的保存结果的类。从代码看,我们可以知道,整个搜索流程包括:

1、读取索引文件

2、获取要分析的字段,并标注要使用的分词(不但对索引的文章分词,对用户的输入也要分词)

3、设定查询结果的评分器(用于评分哪些才是最符合用户搜索要求的结果)

4、设置检索词高亮

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值