Lucene学习笔记

最近项目要用到Lucene,网上搜索了一些基础作为学习记录。

1 、整体结构说明

索引和搜索过程图:


API 使用示例图:







1. 索引过程:

1) 有一系列被索引文件

2) 被索引文件经过语法分析和语言处理形成一系列词 (Term) 。

3) 经过索引创建形成词典和反向索引表。

4) 通过索引存储将索引写入硬盘。

2. 搜索过程:

a) 用户输入查询语句。

b) 对查询语句经过语法分析和语言分析得到一系列词 (Term) 。

c) 通过语法分析得到一个查询树。

d) 通过索引存储将索引读入到内存。

e) 利用查询树搜索索引,从而得到每个词 (Term) 的 文档链表,对文档链表进行交,差,并得到结果文档。

f) 将搜索到的结果文档对查询的相关性进行排序。

g) 返回查询结果给用户。






2 、基本使用说明

lucene 索引的单元对象是 Document, 它可以是一个文本、一封 email 或者一个网页等,其中 Document 包含多个 Field ,一个 Field 就是它的一个属性,比如“文件路径”、“作者”,“文件内容”等都是 Field


1 )索引用类:
Java代码

1. /**
2. * 对目录进行Lucene索引
3. * @author roy
4. *
5. */
6. public class Indexer {
7. private static String INDEX_DIR = "G:\\ROY的各种笔记\\索引"; //索引结果存放目录
8. private static String DATA_DIR = "G:\\ROY的各种笔记"; //被索引文件存放的目录
9.
10. /**
11. * 测试主函数
12. * @param args
13. * @throws Exception
14. */
15. public static void main(String[] args) throws Exception{
16. long start = new Date().getTime();
17. int numIndexed = index(new File(INDEX_DIR),new File(DATA_DIR));//调用index方法
18. long end = new Date().getTime();
19. System.out.println("Indexing " + numIndexed + " files took " + (end - start) + " milliseconds");
20. }
21.
22. /**
23. * 索引dataDir下的.txt文件,并储存在indexDir下,返回索引的文件数量
24. * @param indexDir
25. * @param dataDir
26. * @return
27. * @throws IOException
28. */
29. private static int index(File indexDir, File dataDir) throws Exception{
30. if(!dataDir.exists() || !dataDir.isDirectory()){
31. throw new Exception("被索引的文件不存在!");
32. }
33. if(!indexDir.exists() || !indexDir.isDirectory()){
34. indexDir.mkdirs();
35. }
36. IndexWriter writer = new IndexWriter(
37. FSDirectory.open(indexDir),
38. new StandardAnalyzer(Version.LUCENE_30),
39. true,
40. IndexWriter.MaxFieldLength.UNLIMITED
41. );
42. //按照目录进行递归索引
43. indexDirectory(writer,dataDir);
44. int numIndexed = writer.numDocs();
45. //对索引后的结果加以优化
46. writer.optimize();
47. writer.close();
48. return numIndexed;
49. }
50. /**
51. * 循环遍历目录下的所有.doc文件并进行索引
52. * @param writer
53. * @param dir
54. * @throws IOException
55. */
56. private static void indexDirectory(IndexWriter writer, File dir) throws IOException {
57. File[] files = dir.listFiles();
58. for (int i = 0; i < files.length; i++) {
59. File f = files[i];
60. if (f.isDirectory()) {
61. indexDirectory(writer, f); // recurse
62. } else if (f.getName().endsWith(".doc")) {
63. indexFile(writer, f);
64. }
65. }
66. }
67. /**
68. * 对单个doc文件进行索引
69. * @param writer
70. * @param f
71. * @throws IOException
72. */
73. private static void indexFile(IndexWriter writer, File f)
74. throws IOException {
75. if (f.isHidden() || !f.exists() || !f.canRead()) {
76. return;
77. }
78. System.out.println("Indexing " + f.getCanonicalPath());
79. Document doc = new Document();
80. doc.add(new Field("content",new FileReader(f)));
81. doc.add(new Field("filename",f.getName(),Field.Store.YES, Field.Index.ANALYZED));
82. doc.add(new Field("path",f.getCanonicalPath(),Field.Store.YES, Field.Index.ANALYZED));
83. writer.addDocument(doc);
84. }
85. }

/**
* 对目录进行Lucene索引
* @author roy
*
*/
public class Indexer {
private static String INDEX_DIR = "G:\\ROY的各种笔记\\索引"; //索引结果存放目录
private static String DATA_DIR = "G:\\ROY的各种笔记"; //被索引文件存放的目录

/**
* 测试主函数
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception{
long start = new Date().getTime();
int numIndexed = index(new File(INDEX_DIR),new File(DATA_DIR));//调用index方法
long end = new Date().getTime();
System.out.println("Indexing " + numIndexed + " files took " + (end - start) + " milliseconds");
}

/**
* 索引dataDir下的.txt文件,并储存在indexDir下,返回索引的文件数量
* @param indexDir
* @param dataDir
* @return
* @throws IOException
*/
private static int index(File indexDir, File dataDir) throws Exception{
if(!dataDir.exists() || !dataDir.isDirectory()){
throw new Exception("被索引的文件不存在!");
}
if(!indexDir.exists() || !indexDir.isDirectory()){
indexDir.mkdirs();
}
IndexWriter writer = new IndexWriter(
FSDirectory.open(indexDir),
new StandardAnalyzer(Version.LUCENE_30),
true,
IndexWriter.MaxFieldLength.UNLIMITED
);
//按照目录进行递归索引
indexDirectory(writer,dataDir);
int numIndexed = writer.numDocs();
//对索引后的结果加以优化
writer.optimize();
writer.close();
return numIndexed;
}
/**
* 循环遍历目录下的所有.doc文件并进行索引
* @param writer
* @param dir
* @throws IOException
*/
private static void indexDirectory(IndexWriter writer, File dir) throws IOException {
File[] files = dir.listFiles();
for (int i = 0; i < files.length; i++) {
File f = files[i];
if (f.isDirectory()) {
indexDirectory(writer, f); // recurse
} else if (f.getName().endsWith(".doc")) {
indexFile(writer, f);
}
}
}
/**
* 对单个doc文件进行索引
* @param writer
* @param f
* @throws IOException
*/
private static void indexFile(IndexWriter writer, File f)
throws IOException {
if (f.isHidden() || !f.exists() || !f.canRead()) {
return;
}
System.out.println("Indexing " + f.getCanonicalPath());
Document doc = new Document();
doc.add(new Field("content",new FileReader(f)));
doc.add(new Field("filename",f.getName(),Field.Store.YES, Field.Index.ANALYZED));
doc.add(new Field("path",f.getCanonicalPath(),Field.Store.YES, Field.Index.ANALYZED));
writer.addDocument(doc);
}
}



2 )查询用类:
Java代码

1. /**
2. * 对索引结果进行搜索
3. * @author roy
4. *
5. */
6. public class Searcher {
7. private static String INDEX_DIR = "G:\\ROY的各种笔记\\索引"; //索引所在的路径
8. private static String KEYWORD = "自己"; //关键词
9. private static int TOP_NUM = 100; //显示前100条结果
10. /**
11. * 测试主函数
12. * @param args
13. * @throws Exception
14. */
15. public static void main(String[] args) throws Exception{
16. File indexDir = new File(INDEX_DIR);
17. if (!indexDir.exists() || !indexDir.isDirectory()){
18. throw new Exception(indexDir + "索引目录不存在!");
19. }
20. search(indexDir,KEYWORD); //调用search方法进行查询
21. }
22. /**
23. * 索引查找方法
24. * @param indexDir 索引所在的目录
25. * @param q 查询的字符串
26. * @throws Exception
27. */
28. private static void search(File indexDir, String q) throws Exception {
29. IndexSearcher is = new IndexSearcher(FSDirectory.open(indexDir),true); //read-only
30. String field = "content";
31. //创建查询解析器
32. QueryParser parser = new QueryParser(Version.LUCENE_30, field, new StandardAnalyzer(Version.LUCENE_30));
33. Query query = parser.parse(q);
34. //创建结果收集器
35. TopScoreDocCollector collector = TopScoreDocCollector.create(TOP_NUM ,false);//有变化的地方
36.
37. long start = new Date().getTime();// start time
38. is.search(query, collector);
39. ScoreDoc[] hits = collector.topDocs().scoreDocs;
40.
41. System.out.println(hits.length);
42. for (int i = 0; i < hits.length; i++) {
43. Document doc = is.doc(hits[i].doc);//new method is.doc()
44. System.out.println(doc.getField("filename")+" "+hits[i].toString()+" ");
45. }
46. long end = new Date().getTime();//end time
47.
48. System.out.println("Found " + collector.getTotalHits() +
49. " document(s) (in " + (end - start) +
50. " milliseconds) that matched query '" +
51. q + "':");
52. }
53. }

/**
* 对索引结果进行搜索
* @author roy
*
*/
public class Searcher {
private static String INDEX_DIR = "G:\\ROY的各种笔记\\索引"; //索引所在的路径
private static String KEYWORD = "自己"; //关键词
private static int TOP_NUM = 100; //显示前100条结果
/**
* 测试主函数
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception{
File indexDir = new File(INDEX_DIR);
if (!indexDir.exists() || !indexDir.isDirectory()){
throw new Exception(indexDir + "索引目录不存在!");
}
search(indexDir,KEYWORD); //调用search方法进行查询
}
/**
* 索引查找方法
* @param indexDir 索引所在的目录
* @param q 查询的字符串
* @throws Exception
*/
private static void search(File indexDir, String q) throws Exception {
IndexSearcher is = new IndexSearcher(FSDirectory.open(indexDir),true); //read-only
String field = "content";
//创建查询解析器
QueryParser parser = new QueryParser(Version.LUCENE_30, field, new StandardAnalyzer(Version.LUCENE_30));
Query query = parser.parse(q);
//创建结果收集器
TopScoreDocCollector collector = TopScoreDocCollector.create(TOP_NUM ,false);//有变化的地方

long start = new Date().getTime();// start time
is.search(query, collector);
ScoreDoc[] hits = collector.topDocs().scoreDocs;

System.out.println(hits.length);
for (int i = 0; i < hits.length; i++) {
Document doc = is.doc(hits[i].doc);//new method is.doc()
System.out.println(doc.getField("filename")+" "+hits[i].toString()+" ");
}
long end = new Date().getTime();//end time

System.out.println("Found " + collector.getTotalHits() +
" document(s) (in " + (end - start) +
" milliseconds) that matched query '" +
q + "':");
}
}


3 )选择分词器:



分词器是一个搜索引擎的核心之一,好的分词器可以把关键字分解为更接近于用户的搜索目标的词汇。



下面的方法可以显示分词器的分词结果:
Java代码

1. /**
2. * 查看分词后的结果
3. * @param analyzer 分词器
4. * @param s 需要分词的字符串
5. * @throws Exception
6. */
7. public static void showAnalyzerResult(Analyzer analyzer, String s) throws Exception {
8. StringReader reader = new StringReader(s);
9. TokenStream tokenStream = analyzer.tokenStream(s,reader);
10. tokenStream.addAttribute(TermAttribute.class);
11.
12. while (tokenStream.incrementToken()) {
13. TermAttribute ta = tokenStream.getAttribute(TermAttribute.class);
14. System.out.println(ta.term());
15. }
16. System.out.println();
17. }

/**
* 查看分词后的结果
* @param analyzer 分词器
* @param s 需要分词的字符串
* @throws Exception
*/
public static void showAnalyzerResult(Analyzer analyzer, String s) throws Exception {
StringReader reader = new StringReader(s);
TokenStream tokenStream = analyzer.tokenStream(s,reader);
tokenStream.addAttribute(TermAttribute.class);

while (tokenStream.incrementToken()) {
TermAttribute ta = tokenStream.getAttribute(TermAttribute.class);
System.out.println(ta.term());
}
System.out.println();
}


4 )索引doc,pdf等文件:



默认情况下 lucene 会索引 lucene 文件,其他文件直接索引会出现大量错误。所以我们需要对 doc,pdf 这些文件进行预处理,先生成 txt 文件才可。

1、doc文件:

先下载最新的POI组件,使用其中的poi-3.6-20091214.jar和poi- scratchpad-3.6-20091214即可实现读取word文件中的纯文本内容
Java代码

1. InputStream is = new FileInputStream(f);
2. WordExtractor wordExtractor = new WordExtractor(is);
3. return wordExtractor.getText();

InputStream is = new FileInputStream(f);
WordExtractor wordExtractor = new WordExtractor(is);
return wordExtractor.getText();

2、pdf文件:

下载最新的pdf-box,把fontbox-1.1.0.jar、jempbox-1.1.0.jar、pdfbox-1.1.0.jar三个包放进项目路径中


Java代码

1. InputStream is = new FileInputStream(f);
2. PDFTextStripper ts = new PDFTextStripper();
3. PDDocument pDocument = PDDocument.load(is);
4. StringWriter writer = new StringWriter();
5. ts.writeText(pDocument,writer);
6. is.close();
7. pDocument.close();
8. return writer.getBuffer().toString();

InputStream is = new FileInputStream(f);
PDFTextStripper ts = new PDFTextStripper();
PDDocument pDocument = PDDocument.load(is);
StringWriter writer = new StringWriter();
ts.writeText(pDocument,writer);
is.close();
pDocument.close();
return writer.getBuffer().toString();

5、查看当前索引库的内容

下载最新Luke工具,解压到lucene文件夹下,在命令行输入:
java -classpath lukeall-0.7.1.jar;lucene-2.0.jar org.getopt.luke.Luke
即可


5 )索引的添加、删除和更新:

1、添加新的索引记录
Java代码

1. IndexWriter writer = new IndexWriter(
2. FSDirectory.open(indexFile),
3. analyzer,
4. false,
5. IndexWriter.MaxFieldLength.UNLIMITED
6. );

IndexWriter writer = new IndexWriter(
FSDirectory.open(indexFile),
analyzer,
false,
IndexWriter.MaxFieldLength.UNLIMITED
);

在创建索引器的时候指定create参数为true表示新建立索引,false表示使用当前目录下已有的索引。此时为writer添加 document并optimize()即可



2、删除索引
Java代码

1. /**
2. * 暂时删除某个索引
3. * @param f
4. * @throws Exception
5. */
6. public static void deleteIndexTmp(File indexFile,String targetPath) throws Exception{
7. IndexReader ir = IndexReader.open(FSDirectory.open(indexFile));
8. ir.deleteDocuments(new Term("path",targetPath));
9. ir.close();
10. }
11. /**
12. * 恢复某个临时删除的索引
13. * @param f
14. * @throws Exception
15. */
16. public static void rollbackDelete(File indexFile) throws Exception{
17. IndexReader ir = IndexReader.open(FSDirectory.open(indexFile));
18. ir.undeleteAll();
19. ir.close();
20. }
21. /**
22. * 将标记为删除的索引真正删除
23. * @param f
24. * @throws Exception
25. */
26. public static void optimizeIndex(File indexFile) throws Exception{
27. IndexWriter writer = new IndexWriter(
28. FSDirectory.open(indexFile),
29. analyzer,
30. false,
31. IndexWriter.MaxFieldLength.UNLIMITED
32. );
33. writer.optimize();
34. writer.close();
35. }

/**
* 暂时删除某个索引
* @param f
* @throws Exception
*/
public static void deleteIndexTmp(File indexFile,String targetPath) throws Exception{
IndexReader ir = IndexReader.open(FSDirectory.open(indexFile));
ir.deleteDocuments(new Term("path",targetPath));
ir.close();
}
/**
* 恢复某个临时删除的索引
* @param f
* @throws Exception
*/
public static void rollbackDelete(File indexFile) throws Exception{
IndexReader ir = IndexReader.open(FSDirectory.open(indexFile));
ir.undeleteAll();
ir.close();
}
/**
* 将标记为删除的索引真正删除
* @param f
* @throws Exception
*/
public static void optimizeIndex(File indexFile) throws Exception{
IndexWriter writer = new IndexWriter(
FSDirectory.open(indexFile),
analyzer,
false,
IndexWriter.MaxFieldLength.UNLIMITED
);
writer.optimize();
writer.close();
}

3、更新索引:

可以先查到旧索引将其删除,然后再新建索引即可。
Lucene3.0以后提供了新的update(Term,Document)方法,封装了上述两个操作。
6 )高级查询:
Java代码

1. /** *** 一个关键字,对一个字段进行查询 **** */
2. QueryParser qp = new QueryParser("content",analyzer);
3. query = qp.parse(keyword);
4. Hits hits = searcher.search(query);
5.
6. /** *** 模糊查询 **** */
7. Term term = new Term("content",keyword);
8. FuzzyQuery fq = new FuzzyQuery(term);
9. Hits hits = searcher.search(fq);
10.
11. /** *** 一个关键字,在两个字段中查询 **** */
12. /*
13. * 1.BooleanClause.Occur[] 的三种 类型: MUST : + and MUST_NOT : - not SHOULD : or
14. * 2.下面查询的意思是:content中 必须包含该关键字,而title有 没有都无所谓
15. * 3.下面的这个查询中,Occur[]的 长度必须和Fields[]的 长度一致。每个限制条件对应一个字段
16. */
17. BooleanClause.Occur[] flags = new BooleanClause.Occur[]{BooleanClause.Occur.SHOULD,BooleanClause.Occur.MUST};
18. query=MultiFieldQueryParser.parse(keyword,new String[]{"title","content"},flags,analyzer);
19.
20.
21. /** *** 两个(多个)关键 字对两个(多 个)字 段进行查询,默 认匹配规则 **** */
22. /*
23. * 1. 关键字的个数必须和字段的个数相等
24. * 2. 由于没有指定匹配规定,默认为"SHOULD" 因 此,下面查询的意思是:"title"中 含有keyword1 或 "content"含有 keyword2.
25. * 在此例中,把 keyword1和keyword2相 同
26. */
27. query=MultiFieldQueryParser.parse(new String[]{keyword,keyword},new
28. String[]{"title","content"},analyzer);
29.
30.
31. /** ** 两个(多个)关键 字对两个(多 个)字 段进行查询,手 工指定匹配规则 ****/
32. /*
33. * 1. 必须 关键字的个数 == 字 段名的个数 == 匹 配规则的个数
34. * 2.下面查询的意思是:"title"必 须不含有keyword1,并 且"content"中 必须含有 keyword2
35. */
36. BooleanClause.Occur[] flags = new
37. BooleanClause.Occur[]{BooleanClause.Occur.MUST_NOT,BooleanClause.Occur.MUST};
38. query=MultiFieldQueryParser.parse(new String[]{keyword,keyword},new
39. String[]{"title","content"},flags,analyzer);
40.
41.
42. /** *** 对日期型字段进行查询 **** */
43.
44. /** *** 对数字范围进行查询 **** */
45. /*
46. * 1. 两个条件必须是同一个字段
47. * 2.前面一个条件必须比后面一个条件小,否则找不到数据
48. * 3.new RangeQuery中 的第三个参数,表示是否包含"=" true: >= 或 & lt;= false: > 或 <
49. * 4.找出 55>=id>=53 or 60>=id>=57:
50. */
51. Term lowerTerm1 = new Term("id","53");
52. Term upperTerm1 = new Term("id","55");
53. RangeQuery rq1 = new RangeQuery(lowerTerm1,upperTerm1,true);
54.
55. Term lowerTerm2 = new Term("id","57");
56. Term upperTerm2 = new Term("id","60");
57. RangeQuery rq2 = new RangeQuery(lowerTerm2,upperTerm2,true);
58.
59. BooleanQuery bq = new BooleanQuery();
60. bq.add(rq1,BooleanClause.Occur.SHOULD);
61. bq.add(rq2,BooleanClause.Occur.SHOULD);
62. Hits hits = searcher.search(bq);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值