Springboot通過lucene實現全文檢索詳解流程
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內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- springboot微服務Lucence實現Mysql全文檢索功能
- Java面試重點中的重點之Elasticsearch核心原理
- Elasticsearch中store field與non-store field的區別說明
- Java解析xml文件遇到特殊符號異常的情況(處理方案)
- vue如何動態修改meta的title