首頁 > 軟體

Springboot通過lucene實現全文檢索詳解流程

2022-06-10 14:01:44

Lucene提供了一個簡單卻強大的應用程式介面(API),能夠做全文索引和搜尋,在Java開發環境裡Lucene是一個成熟的免費開放原始碼工具。

Lucene全文檢索就是對檔案中全部內容進行分詞,然後對所有單詞建立倒排索引的過程。主要操作是使用Lucene的API來實現對索引的增(建立索引)、刪(刪除索引)、改(修改索引)、查(搜尋資料)。

假設我們的電腦的目錄中含有很多文字檔案,我們需要查詢哪些檔案含有某個關鍵詞。為了實現這種功能,我們首先利用 Lucene 對這個目錄中的檔案建立索引,然後在建立好的索引中搜尋我們所要查詢的檔案。通過這個例子讀者會對如何利用 Lucene 構建自己的搜尋應用程式有個比較清楚的認識。

建立索引

Document

Document 是用來描述檔案的,這裡的檔案可以指一個 HTML 頁面,一封電子郵件,或者是一個文字檔案。一個 Document 物件由多個 Field 物件組成,可以把一個 Document 物件想象成資料庫中的一個記錄,而每個 Field 物件就是記錄的一個欄位。

Field

Field 物件是用來描述一個檔案的某個屬性的,比如一封電子郵件的標題和內容可以用兩個 Field 物件分別描述。

Analyzer

在一個檔案被索引之前,首先需要對檔案內容進行分詞處理,這部分工作就是由 Analyzer 來做的。Analyzer 類是一個抽象類,它有多個實現。針對不同的語言和應用需要選擇適合的 Analyzer。Analyzer 把分詞後的內容交給 IndexWriter 來建立索引。

IndexWriter

IndexWriter 是 Lucene 用來建立索引的一個核心的類,他的作用是把一個個的 Document 物件加到索引中來。

Directory

這個類代表了 Lucene 的索引的儲存的位置,這是一個抽象類,它目前有兩個實現,第一個是 FSDirectory,它表示一個儲存在檔案系統中的索引的位置。第二個是 RAMDirectory,它表示一個儲存在記憶體當中的索引的位置。

檢索檔案

Query

這是一個抽象類,他有多個實現,比如 TermQuery, BooleanQuery, PrefixQuery. 這個類的目的是把使用者輸入的查詢字串封裝成 Lucene 能夠識別的 Query。

Term

Term 是搜尋的基本單位,一個 Term 物件有兩個 String 型別的域組成。生成一個 Term 物件可以有如下一條語句來完成:Term term = new Term(“fieldName”,”queryWord”); 其中第一個引數代表了要在檔案的哪一個 Field 上進行查詢,第二個引數代表了要查詢的關鍵詞。

TermQuery

TermQuery 是抽象類 Query 的一個子類,它同時也是 Lucene 支援的最為基本的一個查詢類。生成一個 TermQuery 物件由如下語句完成: TermQuery termQuery = new TermQuery(new Term(“fieldName”,”queryWord”)); 它的建構函式只接受一個引數,那就是一個 Term 物件。

IndexSearcher

IndexSearcher 是用來在建立好的索引上進行搜尋的。它只能以唯讀的方式開啟一個索引,所以可以有多個 IndexSearcher 的範例在一個索引上進行操作。

Hits

Hits 是用來儲存搜尋的結果的。

範例

1、pom依賴

		<!-- lucene核心庫 -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-core</artifactId>
            <version>7.6.0</version>
        </dependency>
        <!-- Lucene的查詢解析器 -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-queryparser</artifactId>
            <version>7.6.0</version>
        </dependency>
        <!-- lucene的預設分詞器庫,適用於英文分詞 -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-analyzers-common</artifactId>
            <version>7.6.0</version>
        </dependency>
        <!-- lucene的高亮顯示 -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-highlighter</artifactId>
            <version>7.6.0</version>
        </dependency>
        <!-- smartcn中文分詞器 -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-analyzers-smartcn</artifactId>
            <version>7.6.0</version>
        </dependency>
        <!-- ik分詞器 -->
        <dependency>
            <groupId>com.janeluo</groupId>
            <artifactId>ikanalyzer</artifactId>
            <version>2012_u6</version>
        </dependency>

2、自定義IK分詞器

public class MyIKAnalyzer extends Analyzer {
    private boolean useSmart;
    public MyIKAnalyzer() {
        this(false);
    }
    public MyIKAnalyzer(boolean useSmart) {
        this.useSmart = useSmart;
    }
    @Override
    protected TokenStreamComponents createComponents(String s) {
        Tokenizer _MyIKTokenizer = new MyIKTokenizer(this.useSmart());
        return new TokenStreamComponents(_MyIKTokenizer);
    }
    public boolean useSmart() {
        return this.useSmart;
    }
    public void setUseSmart(boolean useSmart) {
        this.useSmart = useSmart;
    }
}
public class MyIKTokenizer extends Tokenizer {
    private IKSegmenter _IKImplement;
    private final CharTermAttribute termAtt = (CharTermAttribute)this.addAttribute(CharTermAttribute.class);
    private final OffsetAttribute offsetAtt = (OffsetAttribute)this.addAttribute(OffsetAttribute.class);
    private final TypeAttribute typeAtt = (TypeAttribute)this.addAttribute(TypeAttribute.class);
    private int endPosition;
    //useSmart:設定是否使用智慧分詞。預設為false,使用細粒度分詞,這裡如果更改為TRUE,那麼搜尋到的結果可能就少的很多
    public MyIKTokenizer(boolean useSmart) {
        this._IKImplement = new IKSegmenter(this.input, useSmart);
    }
    @Override
    public boolean incrementToken() throws IOException {
        this.clearAttributes();
        Lexeme nextLexeme = this._IKImplement.next();
        if (nextLexeme != null) {
            this.termAtt.append(nextLexeme.getLexemeText());
            this.termAtt.setLength(nextLexeme.getLength());
            this.offsetAtt.setOffset(nextLexeme.getBeginPosition(), nextLexeme.getEndPosition());
            this.endPosition = nextLexeme.getEndPosition();
            this.typeAtt.setType(nextLexeme.getLexemeTypeString());
            return true;
        } else {
            return false;
        }
    }
    @Override
    public void reset() throws IOException {
        super.reset();
        this._IKImplement.reset(this.input);
    }
    @Override
    public final void end() {
        int finalOffset = this.correctOffset(this.endPosition);
        this.offsetAtt.setOffset(finalOffset, finalOffset);
    }
}

測試1

	@RequestMapping("/createIndex")
    public String createIndex() throws IOException {
        List<Content> list1 = new ArrayList<>();
        list1.add(new Content(null, "Java物件導向", "10", null, "Java物件導向從入門到精通,簡單上手"));
        list1.add(new Content(null, "Java物件導向java", "10", null, "Java物件導向從入門到精通,簡單上手"));
        list1.add(new Content(null, "Java面向程式設計", "15", null, "Java物件導向程式設計書籍"));
        list1.add(new Content(null, "JavaScript入門", "18", null, "JavaScript入門程式設計書籍"));
        list1.add(new Content(null, "深入理解Java程式設計", "13", null, "十三四天掌握Java基礎"));
        list1.add(new Content(null, "從入門到放棄_Java", "20", null, "一門從入門到放棄的書籍"));
        list1.add(new Content(null, "Head First Java", "30", null, "《Head First Java》是一本完整地物件導向(object-oriented,OO)程式設計和Java的學習指導用書"));
        list1.add(new Content(null, "Java 核心技術:卷1 基礎知識", "22", null, "全書共14章,包括Java基本的程式結構、物件與類、繼承、介面與內部類、圖形程式設計、事件處理、Swing使用者介面元件"));
        list1.add(new Content(null, "Java 程式設計思想", "12", null, "本書贏得了全球程式設計師的廣泛讚譽,即使是最晦澀的概念,在Bruce Eckel的文字親和力和小而直接的程式設計範例面前也會化解於無形"));
        list1.add(new Content(null, "Java開發實戰經典", "51", null, "本書是一本綜合講解Java核心技術的書籍,在書中使用大量的程式碼及案例進行知識點的分析與運用"));
        list1.add(new Content(null, "Effective Java", "10", null, "本書介紹了在Java程式設計中57條極具實用價值的經驗規則,這些經驗規則涵蓋了大多數開發人員每天所面臨的問題的解決方案"));
        list1.add(new Content(null, "分散式 Java 應用:基礎與實踐", "14", null, "本書介紹了編寫分散式Java應用涉及的眾多知識點,分為了基於Java實現網路通訊、RPC;基於SOA實現大型分散式Java應用"));
        list1.add(new Content(null, "http權威指南", "11", null, "超文字傳輸協定(Hypertext Transfer Protocol,HTTP)是在全球資訊網上進行通訊時所使用的協定方案"));
        list1.add(new Content(null, "Spring", "15", null, "這是啥,還需要學習嗎?Java程式設計師必備書籍"));
        list1.add(new Content(null, "深入理解 Java 虛擬機器器", "18", null, "作為一位Java程式設計師,你是否也曾經想深入理解Java虛擬機器器,但是卻被它的複雜和深奧拒之門外"));
        list1.add(new Content(null, "springboot實戰", "11", null, "完成對於springboot的理解,是每個Java程式設計師必備的姿勢"));
        list1.add(new Content(null, "springmvc學習", "72", null, "springmvc學習指南"));
        list1.add(new Content(null, "vue入門到放棄", "20", null, "vue入門到放棄書籍資訊"));
        list1.add(new Content(null, "vue入門到精通", "20", null, "vue入門到精通相關書籍資訊"));
        list1.add(new Content(null, "vue之旅", "20", null, "由淺入深地全面介紹vue技術,包含大量案例與程式碼"));
        list1.add(new Content(null, "vue實戰", "20", null, "以實戰為導向,系統講解如何使用 "));
        list1.add(new Content(null, "vue入門與實踐", "20", null, "現已得到蘋果、微軟、谷歌等主流廠商全面支援"));
        list1.add(new Content(null, "Vue.js應用測試", "20", null, "Vue.js創始人尤雨溪鼎力推薦!Vue官方測試工具作者親筆撰寫,Vue.js應用測試完全學習指南"));
        list1.add(new Content(null, "PHP和MySQL Web開發", "20", null, "本書是利用PHP和MySQL構建資料庫驅動的Web應用程式的權威指南"));
        list1.add(new Content(null, "Web高效程式設計與優化實踐", "20", null, "從思想提升和內容修煉兩個維度,圍繞前端工程師必備的前端技術和程式設計基礎"));
        list1.add(new Content(null, "Vue.js 2.x實踐指南", "20", null, "本書旨在讓初學者能夠快速上手vue技術棧,並能夠利用所學知識獨立動手進行專案開發"));
        list1.add(new Content(null, "初始vue", "20", null, "解開vue的面紗"));
        list1.add(new Content(null, "什麼是vue", "20", null, "一步一步的瞭解vue相關資訊"));
        list1.add(new Content(null, "深入淺出vue", "20", null, "深入淺出vue,慢慢掌握"));
        list1.add(new Content(null, "三天vue實戰", "20", null, "三天掌握vue開發"));
        list1.add(new Content(null, "不知火舞", "20", null, "不知名的vue"));
        list1.add(new Content(null, "娜可露露", "20", null, "一招秒人"));
        list1.add(new Content(null, "宮本武藏", "20", null, "我就是一個超級兵"));
        list1.add(new Content(null, "vue宮本vue", "20", null, "我就是一個超級兵"));
        // 建立檔案的集合
        Collection<Document> docs = new ArrayList<>();
        for (int i = 0; i < list1.size(); i++) {
            //contentMapper.insertSelective(list1.get(i));
            // 建立檔案物件
            Document document = new Document();
            //StringField會建立索引,但是不會被分詞,TextField,即建立索引又會被分詞。
            document.add(new StringField("id", (i + 1) + "", Field.Store.YES));
            document.add(new TextField("title", list1.get(i).getTitle(), Field.Store.YES));
            document.add(new TextField("price", list1.get(i).getPrice(), Field.Store.YES));
            document.add(new TextField("descs", list1.get(i).getDescs(), Field.Store.YES));
            docs.add(document);
        }
        // 索引目錄類,指定索引在硬碟中的位置,我的設定為D槽的indexDir資料夾
        Directory directory = FSDirectory.open(FileSystems.getDefault().getPath("D:\Lucene\indexDir"));
        // 引入IK分詞器
        Analyzer analyzer = new MyIKAnalyzer();
        // 索引寫出工具的設定物件,這個地方就是最上面報錯的問題解決方案
        IndexWriterConfig conf = new IndexWriterConfig(analyzer);
        // 設定開啟方式:OpenMode.APPEND 會在索引庫的基礎上追加新索引。OpenMode.CREATE會先清空原來資料,再提交新的索引
        conf.setOpenMode(IndexWriterConfig.OpenMode.CREATE);
        // 建立索引的寫出工具類。引數:索引的目錄和設定資訊
        IndexWriter indexWriter = new IndexWriter(directory, conf);
        // 把檔案集合交給IndexWriter
        indexWriter.addDocuments(docs);
        // 提交
        indexWriter.commit();
        // 關閉
        indexWriter.close();
        return "success";
    }
    @RequestMapping("/updateIndex")
    public String update(String age) throws IOException {
        // 建立目錄物件
        Directory directory = FSDirectory.open(FileSystems.getDefault().getPath("D:\Lucene\indexDir"));
        // 建立設定物件
        IndexWriterConfig conf = new IndexWriterConfig(new MyIKAnalyzer());
        // 建立索引寫出工具
        IndexWriter writer = new IndexWriter(directory, conf);
        // 建立新的檔案資料
        Document doc = new Document();
        doc.add(new StringField("id", "34", Field.Store.YES));
        //Content content = contentMapper.selectByPrimaryKey("34");
        //content.setTitle("宮本武藏超級兵");
        //contentMapper.updateByPrimaryKeySelective(content);
        Content content = new Content(34, "宮本武藏超級兵", "", "", "");
        doc.add(new TextField("title", content.getTitle(), Field.Store.YES));
        doc.add(new TextField("price", content.getPrice(), Field.Store.YES));
        doc.add(new TextField("descs", content.getDescs(), Field.Store.YES));
        writer.updateDocument(new Term("id", "34"), doc);
        // 提交
        writer.commit();
        // 關閉
        writer.close();
        return "success";
    }
    @RequestMapping("/deleteIndex")
    public String deleteIndex() throws IOException {
        // 建立目錄物件
        Directory directory = FSDirectory.open(FileSystems.getDefault().getPath("D:\Lucene\indexDir"));
        // 建立設定物件
        IndexWriterConfig conf = new IndexWriterConfig(new IKAnalyzer());
        // 建立索引寫出工具
        IndexWriter writer = new IndexWriter(directory, conf);
        // 根據詞條進行刪除
        writer.deleteDocuments(new Term("id", "34"));
        // 提交
        writer.commit();
        // 關閉
        writer.close();
        return "success";
    }
    @RequestMapping("/searchText")
    public Object searchText(String text, HttpServletRequest request) throws IOException, ParseException {
        Directory directory = FSDirectory.open(FileSystems.getDefault().getPath("D:\Lucene\indexDir"));
        // 索引讀取工具
        IndexReader reader = DirectoryReader.open(directory);
        // 索引搜尋工具
        IndexSearcher searcher = new IndexSearcher(reader);
        // 建立查詢解析器,兩個引數:預設要查詢的欄位的名稱,分詞器
        QueryParser parser = new QueryParser("descs", new MyIKAnalyzer());
        // 建立查詢物件
        Query query = parser.parse(text);
        // 獲取前十條記錄
        TopDocs topDocs = searcher.search(query, 10);
        // 獲取總條數
        System.out.println("本次搜尋共找到" + topDocs.totalHits + "條資料");
        // 獲取得分檔案物件(ScoreDoc)陣列.SocreDoc中包含:檔案的編號、檔案的得分
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        List<Content> list = new ArrayList<>();
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出檔案編號
            int docID = scoreDoc.doc;
            // 根據編號去找檔案
            Document doc = reader.document(docID);
            //Content content = contentMapper.selectByPrimaryKey(doc.get("id"));
            Content content = new Content();
            content.setId(Integer.valueOf(doc.get("id")));
            content.setTitle(doc.get("title"));
            content.setDescs(doc.get("descs"));
            list.add(content);
        }
        return list;
    }
    @RequestMapping("/searchText1")
    public Object searchText1(String text, HttpServletRequest request) throws IOException, ParseException {
        String[] str = {"title", "descs"};
        Directory directory = FSDirectory.open(FileSystems.getDefault().getPath("d:\indexDir"));
        // 索引讀取工具
        IndexReader reader = DirectoryReader.open(directory);
        // 索引搜尋工具
        IndexSearcher searcher = new IndexSearcher(reader);
        // 建立查詢解析器,兩個引數:預設要查詢的欄位的名稱,分詞器
        MultiFieldQueryParser parser = new MultiFieldQueryParser(str, new MyIKAnalyzer());
        // 建立查詢物件
        Query query = parser.parse(text);
        // 獲取前十條記錄
        TopDocs topDocs = searcher.search(query, 100);
        // 獲取總條數
        System.out.println("本次搜尋共找到" + topDocs.totalHits + "條資料");
        // 獲取得分檔案物件(ScoreDoc)陣列.SocreDoc中包含:檔案的編號、檔案的得分
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        List<Content> list = new ArrayList<>();
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出檔案編號
            int docID = scoreDoc.doc;
            // 根據編號去找檔案
            Document doc = reader.document(docID);
            //Content content = contentMapper.selectByPrimaryKey(doc.get("id"));
            Content content = new Content();
            content.setId(Integer.valueOf(doc.get("id")));
            list.add(content);
        }
        return list;
    }
    @RequestMapping("/searchText2")
    public Object searchText2(String text, HttpServletRequest request) throws IOException, ParseException, InvalidTokenOffsetsException {
        String[] str = {"title", "descs"};
        Directory directory = FSDirectory.open(FileSystems.getDefault().getPath("d:\indexDir"));
        // 索引讀取工具
        IndexReader reader = DirectoryReader.open(directory);
        // 索引搜尋工具
        IndexSearcher searcher = new IndexSearcher(reader);
        // 建立查詢解析器,兩個引數:預設要查詢的欄位的名稱,分詞器
        MultiFieldQueryParser parser = new MultiFieldQueryParser(str, new MyIKAnalyzer());
        // 建立查詢物件
        Query query = parser.parse(text);
        // 獲取前十條記錄
        TopDocs topDocs = searcher.search(query, 100);
        // 獲取總條數
        System.out.println("本次搜尋共找到" + topDocs.totalHits + "條資料");
        //高亮顯示
        SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>");
        Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));
        Fragmenter fragmenter = new SimpleFragmenter(100);   //高亮後的段落範圍在100字內
        highlighter.setTextFragmenter(fragmenter);
        // 獲取得分檔案物件(ScoreDoc)陣列.SocreDoc中包含:檔案的編號、檔案的得分
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        List<Content> list = new ArrayList<>();
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出檔案編號
            int docID = scoreDoc.doc;
            // 根據編號去找檔案
            Document doc = reader.document(docID);
            //Content content = contentMapper.selectByPrimaryKey(doc.get("id"));
            Content content = new Content();
            //處理高亮欄位顯示
            String title = highlighter.getBestFragment(new MyIKAnalyzer(), "title", doc.get("title"));
            if (title == null) {
                title = content.getTitle();
            }
            String descs = highlighter.getBestFragment(new MyIKAnalyzer(), "descs", doc.get("descs"));
            if (descs == null) {
                descs = content.getDescs();
            }
            content.setDescs(descs);
            content.setTitle(title);
            list.add(content);
        }
        request.setAttribute("list", list);
        return "index";
    }
    @RequestMapping("/searchText3")
    public String searchText3(String text, HttpServletRequest request) throws IOException, ParseException, InvalidTokenOffsetsException {
        String[] str = {"title", "descs"};
        int page = 1;
        int pageSize = 10;
        Directory directory = FSDirectory.open(FileSystems.getDefault().getPath("d:\indexDir"));
        // 索引讀取工具
        IndexReader reader = DirectoryReader.open(directory);
        // 索引搜尋工具
        IndexSearcher searcher = new IndexSearcher(reader);
        // 建立查詢解析器,兩個引數:預設要查詢的欄位的名稱,分詞器
        MultiFieldQueryParser parser = new MultiFieldQueryParser(str, new MyIKAnalyzer());
        // 建立查詢物件
        Query query = parser.parse(text);
        // 獲取前十條記錄
        //TopDocs topDocs = searcher.search(query, 100);
        TopDocs topDocs = searchByPage(page, pageSize, searcher, query);
        // 獲取總條數
        System.out.println("本次搜尋共找到" + topDocs.totalHits + "條資料");
        //高亮顯示
        SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>");
        Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));
        Fragmenter fragmenter = new SimpleFragmenter(100);   //高亮後的段落範圍在100字內
        highlighter.setTextFragmenter(fragmenter);
        // 獲取得分檔案物件(ScoreDoc)陣列.SocreDoc中包含:檔案的編號、檔案的得分
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        List<Content> list = new ArrayList<>();
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出檔案編號
            int docID = scoreDoc.doc;
            // 根據編號去找檔案
            Document doc = reader.document(docID);
            //Content content = contentMapper.selectByPrimaryKey(doc.get("id"));
            Content content = new Content();
            //處理高亮欄位顯示
            String title = highlighter.getBestFragment(new MyIKAnalyzer(), "title", doc.get("title"));
            if (title == null) {
                title = content.getTitle();
            }
            String descs = highlighter.getBestFragment(new MyIKAnalyzer(), "descs", doc.get("descs"));
            if (descs == null) {
                descs = content.getDescs();
            }
            content.setDescs(descs);
            content.setTitle(title);
            list.add(content);
        }
        System.err.println("list的長度:" + list.size());
        request.setAttribute("page", page);
        request.setAttribute("pageSize", pageSize);
        request.setAttribute("list", list);
        return "index";
    }
    private TopDocs searchByPage(int page, int perPage, IndexSearcher searcher, Query query) throws IOException {
        TopDocs result = null;
        if (query == null) {
            System.out.println(" Query is null return null ");
            return null;
        }
        ScoreDoc before = null;
        if (page != 1) {
            TopDocs docsBefore = searcher.search(query, (page - 1) * perPage);
            ScoreDoc[] scoreDocs = docsBefore.scoreDocs;
            if (scoreDocs.length > 0) {
                before = scoreDocs[scoreDocs.length - 1];
            }
        }
        result = searcher.searchAfter(before, query, perPage);
        return result;
    }
    @RequestMapping("/searchText4")
    public String searchText4(String text, HttpServletRequest request) throws IOException, ParseException, InvalidTokenOffsetsException {
        String[] str = {"title", "descs"};
        int page = 1;
        int pageSize = 100;
        IndexSearcher searcher = getMoreSearch("d:\indexDir");
        // 建立查詢解析器,兩個引數:預設要查詢的欄位的名稱,分詞器
        MultiFieldQueryParser parser = new MultiFieldQueryParser(str, new MyIKAnalyzer());
        // 建立查詢物件
        Query query = parser.parse(text);
        TopDocs topDocs = searchByPage(page, pageSize, searcher, query);
        // 獲取總條數
        System.out.println("本次搜尋共找到" + topDocs.totalHits + "條資料");
        //高亮顯示
        SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>");
        Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));
        Fragmenter fragmenter = new SimpleFragmenter(100);   //高亮後的段落範圍在100字內
        highlighter.setTextFragmenter(fragmenter);
        // 獲取得分檔案物件(ScoreDoc)陣列.SocreDoc中包含:檔案的編號、檔案的得分
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        List<Content> list = new ArrayList<>();
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出檔案編號
            int docID = scoreDoc.doc;
            // 根據編號去找檔案
            //Document doc = reader.document(docID);
            Document doc = searcher.doc(docID);//多索引找檔案要用searcher找了,reader容易報錯
            //Content content = contentMapper.selectByPrimaryKey(doc.get("id"));
            Content content = new Content();
            //處理高亮欄位顯示
            String title = highlighter.getBestFragment(new MyIKAnalyzer(), "title", doc.get("title"));
            if (title == null) {
                title = content.getTitle();
            }
            String descs = highlighter.getBestFragment(new MyIKAnalyzer(), "descs", doc.get("descs"));
            if (descs == null) {
                descs = content.getDescs();
            }
            content.setDescs(descs);
            content.setTitle(title);
            list.add(content);
        }
        System.err.println("list的長度:" + list.size());
        request.setAttribute("page", page);
        request.setAttribute("pageSize", pageSize);
        request.setAttribute("list", list);
        return "index";
    }
    private IndexSearcher getMoreSearch(String string) {
        MultiReader reader = null;
        //設定
        try {
            File[] files = new File(string).listFiles();
            IndexReader[] readers = new IndexReader[files.length];
            for (int i = 0; i < files.length; i++) {
                readers[i] = DirectoryReader.open(FSDirectory.open(Paths.get(files[i].getPath(), new String[0])));
            }
            reader = new MultiReader(readers);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new IndexSearcher(reader);
        //如果索引檔案過多,可以這樣加快效率
        /**
         ExecutorService service = Executors.newCachedThreadPool();
         return new IndexSearcher(reader,service);
         */
    }

測試2

public static void main(String[] args) throws IOException, ParseException {
        long startTime = System.currentTimeMillis();
        // indexDir is the directory that hosts Lucene's index files
        File indexDir = new File("D:\Lucene\indexDir");
        // dataDir is the directory that hosts the text files that to be indexed
        File dataDir = new File("D:\Lucene\dataDir");
        Analyzer luceneAnalyzer = new StandardAnalyzer();
        // 或引入IK分詞器
        Analyzer IkAnalyzer = new MyIKAnalyzer();
        Directory directory = FSDirectory.open(FileSystems.getDefault().getPath("D:\Lucene\indexDir"));
        IndexWriterConfig indexWriterConfig = new IndexWriterConfig(IkAnalyzer);
        // 設定開啟方式:OpenMode.APPEND 會在索引庫的基礎上追加新索引、OpenMode.CREATE會先清空原來資料,再提交新的索引
        indexWriterConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE);
        IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig);
        File[] dataFiles = dataDir.listFiles();
        for (int i = 0; i < dataFiles.length; i++) {
            if (dataFiles[i].isFile() && dataFiles[i].getName().endsWith(".txt")) {
                System.out.println("Indexing file " + dataFiles[i].getCanonicalPath());
                Document document = new Document();
                Reader txtReader = new FileReader(dataFiles[i]);
                document.add(new TextField("path", dataFiles[i].getCanonicalPath(), Field.Store.YES));
                document.add(new TextField("contents", txtReader));
                indexWriter.addDocument(document);
            }
        }
        indexWriter.commit();
        indexWriter.close();
        long endTime = System.currentTimeMillis();
        System.out.println("It takes "
                + (endTime - startTime)
                + " milliseconds to create index for the files in directory "
                + dataDir.getPath());
        String queryStr = "hello";
        // 索引讀取工具
        IndexReader indexReader = DirectoryReader.open(directory);
        // 索引搜尋工具
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);
        // 建立查詢解析器,兩個引數:預設要查詢的欄位的名稱,分詞器
        QueryParser parser = new QueryParser("contents", IkAnalyzer);
        // 建立查詢物件
        Query query = parser.parse(queryStr);
        // 獲取前十條記錄
        TopDocs topDocs = indexSearcher.search(query, 10);
        // 獲取總條數
        System.out.println("本次搜尋共找到" + topDocs.totalHits + "條資料");
        // 獲取得分檔案物件(ScoreDoc)陣列.SocreDoc中包含:檔案的編號、檔案的得分
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出檔案編號
            int docID = scoreDoc.doc;
            // 根據編號去找檔案
            Document doc = indexReader.document(docID);
            System.out.println(doc.get("path"));
        }
    }

IndexWriter物件將dataDir下的所有txt檔案建立索引,指定索引檔案的目錄為indexDir,Document物件對應一個帶搜尋的檔案,可以是文字檔案也可以是一個網頁,為Document物件指定field,這裡為文字檔案定義了兩個field:path和contents,執行完第一部分程式碼後,則在指定目錄下生成了索引檔案,如下

IndexReader物件讀取索引檔案,通過QueryParser物件指定語法分析器和對document的那個欄位進行查詢,Query物件則制定了搜尋的關鍵字,通過IndexSearcher物件實現檢索,並返回結果集TopDocs,執行完第二部分程式碼後,會看到列印包含關鍵字的文字檔案的路徑,如下

到此這篇關於Springboot通過lucene實現全文檢索詳解流程的文章就介紹到這了,更多相關Springboot lucene內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com