首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Lucene 4.0 基於 SMB 文件服務器的全文檢索實戰:索引構建、多格式解析與增量更新

     編輯:Java 綜合教學

Lucene 4.0 基於 SMB 文件服務器的全文檢索實戰:索引構建、多格式解析與增量更新

Apache Lucene 是 Java 生態最成熟的全文檢索庫。本文實戰記錄基於 Lucene 4.0 整合 JCIFS SMB 客戶端,實現遠端文件服務器多格式文檔 (txt、doc、docx、xls、xlsx、ppt、pdf) 的全量索引、增量更新、刪除、檢索功能,含完整代碼與關鍵技術點解析。

一、系統架構與依賴

核心依賴:
- lucene-core-4.x.jar
- lucene-analyzers-common-4.x.jar
- lucene-queryparser-4.x.jar
- jcifs-1.3.x.jar (SMB 客戶端)
- poi-3.x.jar (Office 解析)
- poi-ooxml-3.x.jar (Office 2007+ 解析)
- pdfbox-1.8.x.jar (PDF 解析)
- jxl-2.6.x.jar (舊版 Excel 解析)

二、核心索引操作類: FileIndex

1. 全量索引構建 (遞歸掃描 SMB 目錄)

public static boolean createIndex(String smbPath) throws Exception {
    // 1. 刪除舊索引目錄
    deleteDir(new File(INDEX_DIR));
    
    // 2. 遞歸掃描 SMB 文件
    SmbFile root = new SmbFile(smbPath);
    SmbFile[] files = searchFile(root);  // 過濾支援格式
    
    // 3. 逐文件解析內容並建立索引
    IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_CURRENT, 
        new StandardAnalyzer(Version.LUCENE_CURRENT));
    IndexWriter writer = new IndexWriter(FSDirectory.open(new File(INDEX_DIR)), config);
    
    for (SmbFile file : files) {
        String content = parseContent(file);  // 多格式解析
        Document doc = new Document();
        doc.add(new TextField("filename", file.getName(), Store.YES));
        doc.add(new TextField("content", content, Store.YES));
        doc.add(new TextField("path", file.getPath(), Store.YES));
        writer.addDocument(doc);
    }
    writer.commit();
    writer.close();
    return true;
}

2. 遞歸掃描 SMB 文件 (關鍵: 過濾支援格式)

public static SmbFile[] searchFile(SmbFile folder) throws SmbException {
    SmbFile[] subFiles = folder.listFiles(new SmbFileFilter() {
        @Override public boolean accept(SmbFile f) {
            try { return f.isDirectory() || isSupportedFormat(f.getName()); }
            catch (SmbException e) { return false; }
        }
    });
    
    List result = new ArrayList<>();
    for (SmbFile f : subFiles) {
        if (f.isFile()) result.add(f);
        else result.addAll(Arrays.asList(searchFile(f)));  // 遞歸
    }
    return result.toArray(new SmbFile[0]);
}

private static boolean isSupportedFormat(String name) {
    String ext = name.substring(name.lastIndexOf(".") + 1).toLowerCase();
    return Arrays.asList("txt","doc","docx","xls","xlsx","ppt","pdf").contains(ext);
}

3. 增量新增/更新/刪除

// 新增 (不刪除舊索引)
public static void insert(List paths) { ... writer.addDocument(doc); ... }

// 刪除 (按唯一 ID 或 filename)
public static void delete(String id) { 
    writer.deleteDocuments(new Term("id", id)); 
    writer.commit(); 
}

// 更新 (先刪後加)
public static void update(String filename, String newPath) {
    writer.updateDocument(new Term("filename", filename), newDoc);
    writer.commit();
}

4. 檢索查詢

public static List search(String keyword) throws Exception {
    DirectoryReader reader = DirectoryReader.open(FSDirectory.open(new File(INDEX_DIR)));
    IndexSearcher searcher = new IndexSearcher(reader);
    QueryParser parser = new QueryParser(Version.LUCENE_40, "content", 
        new StandardAnalyzer(Version.LUCENE_CURRENT));
    Query query = parser.parse(keyword);
    
    TopDocs topDocs = searcher.search(query, 10000);
    List results = new ArrayList<>();
    for (ScoreDoc sd : topDocs.scoreDocs) {
        Document doc = searcher.doc(sd.doc);
        results.add(doc.get("filename"));
    }
    reader.close();
    return results;
}

三、多格式文檔解析工具類: ReadFile

核心原則:統一使用 SmbFileInputStream 讀取遠端流,交給對應解析庫處理

1. 純文本

public static String readTxt(String path, String charset) throws IOException {
    try (BufferedReader br = new BufferedReader(
            new InputStreamReader(new SmbFileInputStream(path), charset))) {
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = br.readLine()) != null) sb.append(line);
        return sb.toString();
    }
}

2. Word 97-2003 (.doc) - Apache POI HWPF

public static String readWorddoc(String path) {
    try (HWPFDocument doc = new HWPFDocument(new SmbFileInputStream(path))) {
        StringBuilder sb = new StringBuilder();
        for (Paragraph p : doc.getRange().getParagraphs()) {
            sb.append(p.text());
        }
        return sb.toString();
    }
}

3. Word 2007+ (.docx) - Apache POI XWPF

public static String readWorddocx(String path) {
    try (XWPFDocument doc = new XWPFDocument(new SmbFileInputStream(path))) {
        StringBuilder sb = new StringBuilder();
        for (XWPFParagraph p : doc.getParagraphs()) {
            for (XWPFRun run : p.getRuns()) sb.append(run.getText(0));
        }
        return sb.toString();
    }
}

4. Excel 97-2003 (.xls) - JXL

public static String xls2String(String path) {
    try (Workbook wb = Workbook.getWorkbook(new SmbFileInputStream(path))) {
        StringBuilder sb = new StringBuilder();
        for (Sheet sheet : wb.getSheets()) {
            for (int r = 0; r < sheet.getRows(); r++) {
                for (Cell cell : sheet.getRow(r)) sb.append(cell.getContents()).append(" ");
            }
        }
        return sb.toString();
    }
}

5. Excel 2007+ (.xlsx) - Apache POI XSSF

public static String readExcel2007(String path) throws IOException {
    try (XSSFWorkbook wb = new XSSFWorkbook(new SmbFileInputStream(path))) {
        StringBuilder sb = new StringBuilder();
        for (XSSFSheet sheet : wb) {
            for (XSSFRow row : sheet) {
                for (XSSFCell cell : row) {
                    switch (cell.getCellType()) {
                        case STRING: sb.append(cell.getStringCellValue()); break;
                        case NUMERIC: sb.append(cell.getNumericCellValue()); break;
                        case BOOLEAN: sb.append(cell.getBooleanCellValue()); break;
                    }
                    sb.append(" ");
                }
            }
        }
        return sb.toString();
    }
}

6. PowerPoint (.ppt) - Apache POI HSLF

public static String readPowerPoint(String path) {
    try (SlideShow ss = new SlideShow(new HSLFSlideShow(new SmbFileInputStream(path)))) {
        StringBuilder sb = new StringBuilder();
        for (Slide slide : ss.getSlides()) {
            for (TextRun run : slide.getTextRuns()) sb.append(run.getText());
        }
        return sb.toString();
    }
}

7. PDF - PDFBox

public static String readPdf(String path) throws Exception {
    try (PDDocument doc = PDDocument.load(new SmbFileInputStream(path))) {
        return new PDFTextStripper().getText(doc);
    }
}

四、關鍵技術點總結

技術點說明注意事項
SMB 認證URL 格式: smb://user:pass@host/share/path特殊字符需 URL 編碼,密碼建議配置文件讀取
中文分詞StandardAnalyzer 單字切分生產建議整合 IKAnalyzer 或 SmartCN
大文件處理流式讀取,避免全量載入記憶體POI 支援 SAX 模式解析大 Excel
索引優化批量提交、合併策略、RAMBufferSizeMB大量數據建議 writer.setRAMBufferSizeMB(256)
並發安全IndexWriter 非執行緒安全單例模式或同步塊控制寫入
近實時搜尋NRT (Near Real-Time)使用 DirectoryReader.openIfChanged() 避免重開 reader

五、本地文件適配指南

若切換至本地文件系統,僅需替換輸入流來源:

// SMB 版本
new SmbFileInputStream(smbPath)

// 本地版本
new FileInputStream(localPath)

// 統一抽象: 定義 FileReader 介面,SMB/Local 兩種實現,工廠模式注入

六、生產環境升級建議

  1. 升級 Lucene 8/9:API 變化大 (移除 Version 參數、模組化),但效能與功能大幅提升
  2. 引入 Elasticsearch:分佈式、RESTful、自動分片副本、Kibana 可視化,企業級首選
  3. 中文分詞:整合 IK Analyzer 或 HanLP
  4. 異步索引:消息佇列 (Kafka/RocketMQ) 解耦業務與索引構建
  5. 監控告警:索引耗時、佇列積壓、磁盤空間、查詢 QPS/延遲

核心心法:Lucene 是檢索引擎核心庫,非開箱即用產品。理解「文檔→分析器→索引→查詢→評分」核心流程,掌握多格式解析、增量更新、近實時搜尋關鍵技術,才能構建穩定高效的檢索系統。生產環境強烈建議直接使用 Elasticsearch/OpenSearch。

本文代碼基於 Lucene 4.x 經典 API,核心邏輯在新版本中依然適用,僅 API 調用方式有變。建議讀者對照官方 Migration Guide 進行遷移適配。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved