终于见到汤阳光老师了,接下来的一段时间他要带我们学习Lucene、compass、以及一个OA项目。汤老师是现在传智播客唯一一个以前在传智播客学习后被留在传智播客任教的老师,也是传智播客最年轻的一位老师,跟我们的年龄差不多,别看老师年龄不大,但是讲起技术来时一点不含糊,没来传智学习之前写的第一个小应用程序俄罗斯方块就是通过学习汤老师网上的视频做出来的(那时候虽然是照猫画虎但是也费了老鼻子劲了,不堪回首啊)。为什么同样年龄却差距这么大呢?来到传智后一直想看看汤老师是何许人也,到底有什么过人之处,二十几岁就能到这样的程度,今天看到老师头上的白头发就明白了,还是努力程度不够啊,这让我压力骤增。
今天老师通过循序渐进的方式对Lucene进行了讲解,首先对Lucene进行了简要的概述,Lucene是什么,学完Lucene能够做什么,然后分析它的使用流程,通过一个helloword带我们入门,最后对Lucene索引库的管理进行了详细的讲解。
Lucene是什么?
Lucene是一项全文检索技术,全文检索(Full-Text Retrieval)是指以文本作为检索对象,找出含有指定词汇的文本。全面、准确和快速是衡量全文检索系统的关键指标。它只处理文本,不处理语义,搜索时英文不区分大小写,结果列表有相关度排序。在信息检索工具中,全文检索是最具通用性和实用性的。
学完之后能做什么?
给应用程序提供搜索功能,主要做系统内搜索。现在的好多的论坛、网站都有站内搜索功能。
使用Lucene流程:
一 导入Lucene的几个jar包
lucene-core-3.0.1.jar(核心包)
contrib/analyzers/common/lucene-analyzers-3.0.1.jar(分词器)
contrib/highlighter/lucene-highlighter-3.0.1.jar(高亮)
contrib/memory/lucene-memory-3.0.1.jar(高亮)
二 建立索引
public void createIndex() throws Exception {
//创建索引库存放目录
Directory directory = FSDirectory.open(new File("./indexDir/"));
//创建分词器 参数为使用Lucene的版本
Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_30);
// 模拟一条文章数据
Article article = new Article();
article.setId(1);
article.setContent("我们都要存到数据库中。对于要进行搜索的数据,还要存到索引。一份数据搜索同时存到数据库");
article.setTitle("全文检索2");
// 建立索引
// Lucene的数据结构是document和field,需要将Article 向Document转换,
Document doc = new Document();
// store是枚举类型,YES表示存储属性的值 NO表示不存,Index也是枚举类型,NO表示不建立索引,ANALYZED表示分词并建立索引,NOT_ANALYZED表示不分词但建立索引。
doc.add(new Field("id", article.getId().toString(), Store.YES, Index.NOT_ANALYZED));
doc.add(new Field("title", article.getTitle(), Store.YES, Index.ANALYZED));
doc.add(new Field("content", article.getContent(), Store.YES, Index.ANALYZED));
IndexWriter indexWriter = new IndexWriter(directory, analyzer, MaxFieldLength.LIMITED);
indexWriter.addDocument(doc);
indexWriter.close();
}
索引库的管理操作操作是通过类IndexWriter完成的。创建实例是使用构造方法:IndexWriter(Directory d, Analyzer a, MaxFieldLength mfl)。用完后要调用IndexWriter.close()方法释放资源。
1, 建立索引:保存文档到索引库中。
调用方法IndexWriter.addDocument(Document doc)
2, 删除索引:删除所有包含指定Term的文档。
a) 生成用于确定要删除的文档的Term
b) 调用方法IndexWriter.deleteDocuments(Term term)
说明:在生成Term时,一般。如果有多个文档含有指定的Term,则都会被删掉。
3, 更新索引:实际执行的是先删除,后创建的操作。
a) 生成用于确定要更新的文档的Term
b) 调用方法IndexWriter.updateDocument(Term term, Document doc)
说明:如果有多个文档含有指定的Term,更新后就只有一条记录(删掉所有,再创建一个)。如果没有文档含有指定的记录,不会报错,更新后有一条(新创建的)记录。
三 通过索引库进行搜索
public void search()throws Exception {
//创建索引存放目录和分词器
Directory directory = FSDirectory.open(new File("./indexDir/"));
Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_30);
// a, queryString --> Query
String queryString = "搜索";
// QueryParser是查询分析器,处理用户输入的查询条件。把用户输入的非格式化检索词转化成后台检索可以理解的Query对象。MultiFieldQueryParser是QueryParser的子类。与父类相比,MultiFieldQueryParser可以在多个属性中搜索。
QueryParser queryParser = new QueryParser(Version.LUCENE_30, "content", analyzer);
Query query = queryParser.parse(queryString);
// b, 进行搜索,得到结果
IndexSearcher indexSearcher = new IndexSearcher(directory);
TopDocs topDocs = indexSearcher.search(query, 100);
System.out.println("总记录数:" + topDocs.totalHits);
ScoreDoc[] scoreDocs = topDocs.scoreDocs; // 结果的数组。ScoreDoc代表一条结果
// c, 处理结果
List<Article> list = new ArrayList();
for (int i = 0; i < scoreDocs.length; i++) {
ScoreDoc scoreDoc = scoreDocs[i];
// scoreDoc.score; 相关度得分
// scoreDoc.doc; 文档的内部编号
Document doc = indexSearcher.doc(scoreDoc.doc);
Article article = new Article();
article.setId(Integer.parseInt(doc.get("id")));
article.setTitle(doc.get("title")); article.setContent(doc.get("content"));
list.add(article);
}
// 显示结果
for (Article article : list) {
System.out.println("id = " + article.getId());
System.out.println("title = " + article.getTitle());
System.out.println("content = " + article.getContent());
}
}
}
查询不到记录的几种错误情形:
1 建立索引时indexwriter或者搜索结果时indexsearcher没有关闭
2 new Field("title", article.getTitle(), Store.YES, Index.ANALYZED)中的index属性值设置错误。
3 new QueryParser(Version.LUCENE_30, "content", analyzer); 要搜索的内容与建立索引的位置不匹配。
优化索引库
1使用RAMDirectory将索引库建立在内存中,是模拟的文件夹与文件。与FSDirectory相比:1因为没有IO操作,所以速度快。2,因为在内存中,所以在程序退出后索引库数据就不存在了。
2 在indexwriter的add方法后面调用IndexWriter.optimize(),将索引创建在同一个索引文件下,避免文件过多产生的不必要的IO操作浪费系统资源。在Lucene中默认如果索引文件超过10个的时候自动进行文件合并,这个数字我们可以通过indexWriter.setMergeFactor(3)方法自己设定.