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 解析)
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;
}
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);
}
// 新增 (不刪除舊索引)
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();
}
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;
}
核心原則:統一使用 SmbFileInputStream 讀取遠端流,交給對應解析庫處理
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();
}
}
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();
}
}
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();
}
}
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();
}
}
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();
}
}
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();
}
}
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 兩種實現,工廠模式注入
核心心法:Lucene 是檢索引擎核心庫,非開箱即用產品。理解「文檔→分析器→索引→查詢→評分」核心流程,掌握多格式解析、增量更新、近實時搜尋關鍵技術,才能構建穩定高效的檢索系統。生產環境強烈建議直接使用 Elasticsearch/OpenSearch。
本文代碼基於 Lucene 4.x 經典 API,核心邏輯在新版本中依然適用,僅 API 調用方式有變。建議讀者對照官方 Migration Guide 進行遷移適配。