标签: it |
Lucene:
1. 举例: Eclipse的帮助;
2. 建立索引:只能为文本类型的数据建立索引
html(去掉标签), pdf等可以使用相应的工具转换为文本;
3. 原理:字典
key ---> recordNum
ab ---> 5,7,10
Document;
5
7
10
4. 使用:
4.1 建立java Project;
4.2 添加*.jar;[没有配置文件]目前为2.4版;
核心;分词器;高亮器;
4.3 写代码测试;
FirstTest
StringindexPath = "c:/luceneDemoIndex/";
Analyzeranalyzer = new StandardAnalyer();
@Test
testCreateIndex(){
Stringtitle = "xxx";
Stringcontent = "yyy";
//1.建立索引
MaxFieldLengthmaxFieldLength = MaxFieldLength.LIMITED;
//如果索引库不存在,则创建;
IndexWriterindexWriter = new IndexWriter(indexPath, analyzer,maxFieldLength);
//文档,字段
Documentdoc = new Document();
doc.add(newField("title", title, Store.YES, Index.ANALYZED));
doc.add(newField("content", content, Store.YES, Index.ANALYZED));
indexWriter.addDocument(doc);
//使用完一定要关闭
indexWriter.close();
}
voidtestSearch() {
StringqueryString ="document";
IndexSearcherindexSearcher = new IndexSearcher(indexPath);
StringdefaultFieldName = "content";
QueryParserqueryParser = new QueryParser(defaultFieldName, analyzer);
Query query =queryParser.parse(queryString); //查询条件
Filter filter =null; //过滤条件
int nDocs =100; //返回匹配的数目
//返回结果
TopDocstopDocs = indexSearcher.search(query, filter, nDocs);
System.out.println("共有【"+topDocs.totalHits+"条匹配记录");
List<Document>docs = newArrayList<Document>();
for( ScoreDoc scoreDoc : topDocs.scoreDocs ) {
intdocNum = scoreDoc.doc; //文档在索引库中的编号
Documentdoc = indexSearcher.doc(docNum); // 通过编号取出相应的文档
doc.add(doc);
}
indexSearcher.close();
for( Document doc : docs ) {
System.out.print("title:"+ doc.getField("title").stringValue());
System.out.println("\tcontent:"+ doc.getField("content").stringValue());
System.out.println("-------------------------------------------------");
}
}
------------------------------------------------------
(1) 测试建立索引文件;
(2) 对索引库的操作:
创建;搜索;
----
删除, 更新;
(3) IndexDAO {
// 创建索引
create(Documentdoc);
// 删除索引
delete();
// 更新索引
update(Documentdoc);
// 搜索
search(StringqueryString, int first, int max);
----------实现过程
StringindexPath = "c:/luceneDemoIndex/";
Analyzeranalyzer = new StandardAnalyer();
create(Documentdoc) {
IndexWriterindexWriter = new IndexWriter(indexPath, analyzer,maxFieldLength);
try{
indexWriter.addDocument(doc);
}
catch(e) {
}
finally{
try{
indexWriter.close();
}
}
delete(Termterm) {
//Termt = new Term("content", "document");
IndexWriterindexWriter = null;
try{
indexWriter= new IndexWriter(indexPath, analyzer,maxFieldLength);
indexWriter.deleteDocuments(term);
}
catch(e){}
finally{
indexWriter.close();
}
}
update(Termterm, Document doc) {
//Termt = new Term("content", "document");
IndexWriterindexWriter = null;
try{
indexWriter= new IndexWriter(indexPath, analyzer,maxFieldLength);
//先删除,再创建
indexWriter.updateDocument(term,doc);
}
catch(e){}
finally{
indexWriter.close();//try..catch
}
}
//支持分页的搜索;
List<Document>/SearchResult
search(StringqueryString, int first, int max) {
IndexSearcherindexSearcher = new IndexSearcher(indexPath);
StringdefaultFieldName = "content";
QueryParserqueryParser = new QueryParser(defaultFieldName, analyzer);
Query query =queryParser.parse(queryString); //查询条件
Filter filter =null; //过滤条件
int nDocs = first +max; //返回匹配的数目
//返回结果
TopDocstopDocs = indexSearcher.search(query, filter, nDocs);
System.out.println("共有【"+topDocs.totalHits+"条匹配记录");
List<Document>docs = newArrayList<Document>();
intstart = first;
intend = Math.min(first+max,topDocs.totalHits);
for( int i=start; i<end; i++ ) {
intdocNum = scoreDoc.doc; //文档在索引库中的编号
Documentdoc = indexSearcher.doc(docNum); // 通过编号取出相应的文档
docs.add(doc);
}
indexSearcher.close();
for( Document doc : docs ) {
System.out.println("title:"+ doc.getField("title").stringValue());
System.out.println("\tcontent:"+ doc.getField("content").stringValue());
System.out.println("-----------------------------------------------------------------------------");
}
returnnew SearchResult(topDocs.totalHits, docs);
}
(4) 测试IndexDAO;
testCreate(){
Stringtitle="";
Stringcontent="";
Documentdoc = new Document();
doc.add(newField("title", title, Store.YES, Index.ANALYZE));
doc.add(newField("content", content, Store.YES, Index.ANALYZE));
indexWrite.addDocment(doc);
}
testSearch(){
sr= indexDAO.search(queryString, 0, 10);
for(Docuemnt doc : sr.getDocs ) {
for(Object obj : doc.getFields() ) {
Fieldfield = ( Field )obj;
syso(field.name(),field.stringValue);
}
}
}
testDelete(){
Termterm = new Term("content", "document");
indexDAO.delete(term);
}
-------删除完之后,测试搜索就没有匹配记录
testUpdate(){
Termterm = new Term("content", "document");
doc.add(newField());
doc.add(newField());
indexDAO.update(term,doc);
}
---------
(5) 索引文件存放的位置:文件系统,内存;
DirectoryTest(){}
Directory dir= null;
dir =FSDirectory.getDirectory(path); // 与前面的实现一样
newRAMDirectory(); //在程序退出后,索引库就没了,速度快;
newIndexWriter(dir, analyzer, MaxFieldLength.LIMITED);
(6) RAMDirectory,Directory结合使用: 既可以提供效率又可以保存
3步操作;
// 1.创建索引
fsDir = FSDirectory.getDirectory(path);
remDir = newRAMDirectory(fsDir);
// 2. 处理代码 :使用remDir创建索引
IndexWriterramIndexWriter = new(remDir, ...);
doc.add();
doc.add();
...
ramIndexWriter.close();
// 3.把内存中的索引数据存到fsDir
IndexWriterfsIndexWriter = new(fsDir, ...);
fsIndexWriter.addIndexesNoOptimize(newDirectory[] {ramDir});
fsIndexWriter.close();
--- remDir ->fsDir 是追加操作;
--- 可以让remDir = new RAMDirectory(fsDir);
--- new IndexWriter(..., IsCreate=false,...);
IsCreate= true: 重新创建新的索引,老的索引就被删除了;
--总结:
索引可以放在2个位置;
索引文件可以创建新的,也可以使用原有的,取决于Create=true;
如果文件本身就不存在,就直接创建
5. 相关度排序与Boost
(1)匹配度:根据关键字在内容中出现次数,位置等等
(2) 使用Boost影响相关度排序
BoostTest
增加了2个Document,其中1个"Doument"4次,1个2次;
-- 实验看出出现次数影响排序,但是还有其他因素影响排序;
-- 给排在后面的文档下面的操作
doc2.setBoost(1.0f) -- 默认
doc2.setBoost(2.0f);
这样就可以让这个文档排在前面;
(3) 还可以在Field中设置;
标题中出现的关键字比内容中出现的重要;
目前只在内容中查询;
也可以再多个字段中查询;
6. Analyzer:分词器
英文与中文的不同;
中文:
a) 单字
b) 二分法
c) 词库
分词测试:
--标题内容使用中文;
--DAO中set/getAnalyzer方法;
可以在创建和搜素时设置分词器;
--将搜索的方法写成工具类,直接使用。
--先使用StandAnalyzer;
-- 下面两句的区别 :测试的结果没有什么区别
queryString= "content: 中国";(term, 中国)
queryString= "中国";("中国"扯开成"中 国")
----------------------------------------------------------------
分词器测试:
------------------------------------------------------------------
--单字分词器测试
nwStandardAnalyzer()
Termterm = new Term("content", "中");
Queryquery = new TermQuey();
printSearchResult(indexDao.getDirectory(),query);
创建索引时,使用的是单字分词,所以索引中没有"中国",
使用中可以搜索到记录,而中国搜索则为0条记录;
--二分法分词
测试代码同上,
创建索引时使用new CJKAnalyzer()
中国,是中 -- 可以
中 -- 无记录
--词库分词器
je分词器
new MMAnalyzer();
所有输入该词库的都可以搜索到;
7. 高亮器 : 对搜索的结果使用高亮效果;
(1) 测试: HighLighterTest() {
dao.search();//复制并简单修改
//何处设置高亮
Formatterformatter = new SimpleHTMLFormatter("<fontcolor='blue'>","</font>"); //高亮器的原理
Scorerscorer = new QueryScorer(query);
Highlighterhighlighter = new Highlighter(formatter, scorer);
//显示内容前
Stringht = highlighter.getBestFragment(analyzer, "content",doc.get("content"));
doc.getField("content").setValue(ht);
}
测试:content内容很长,搜索结果不是全部显示,而是显示出现关键字
的部分;
--------高亮器的另一个作用,显示关键字最多的地方的部分内容;
可以设置显示的内容数目,方法如下
Fragmenterfragmeter = new SimpleFragment(100);
setTextFragmenter(fragmenter);
(2)如果搜索结果为0,高亮器的结果返回null,而不是原始内容
if ( ht ==null ) {
//subString 会出错
maxEndIndex= doc.get("title").length();
endIndex= Math.min(50, maxEndIndex);
ht= doc.get("title").subString(0, endIndex);
doc.getField("content").setValue(ht);
}
8. 查询:
(1) 查询一个字段
Queryquery;
String queryString ="索引";
QueryParser queryParser = newQueryParser("content", analyzer);
query =queryParser.parser(queryString);
(2) 查询多个字段
QueryParser queryParser = newMultiFieldQueryParser(new String{field1, field2}, analyzer);
query =queryParser.parser(queryString);
---为字段指定Boost值:构造函数后面增加一个字段的Boost属性Map;
Map BoostMap;
BoostMap.put("title",3f);
BoostMap.put("conent",1f);
QueryParser queryParser = newMultiFieldQueryParser(..., BoostMap);
文章1: 标题中有"索引",内容中无;
文章2: 标题中无"索引",内容中有;
通过设置他们的Boost属性,测试他们的排序效果;
(3) TermQuery()
Term term =new Term("title", "测试");
Query query= new TermQuery(term);
syso:query;//== [title:测试]
(4) RangeQuery()
TermlowerTerm = new Term("size", "100");
TermupperTerm = new Term("size", "250");
Query query= new RangeQuery(lowerTerm, upperTerm, true);
//不包括边界的【size:{100 TO 250}】或包括边界的【size:[100 TO 250]】
//比较的时候是根据字符串,而不是数字;
//(0200,1000) 准备数据和上述Term时都应这样写;
//日期:yyyyMMddHHmmss
--为2篇文章准备size字段数据的时候,不要分词;
(5) PrefixQuery
Term term = new Term("title","lu");
Query query = newPrefixQuery(term);
syso:query // title:lu*,title:索*
(6) WildcardQuery
* :n个任意字符(n>=0)
? : 1个任意字符
// 之前的版本通配符不能出现在第一个字符
newWildcardQuery(term);
(7) BooleanQuery
rangeQuery = new RangeQuery();
termQuery =new TermQuery();
booleanQuery = newBooleanQuery();
booleanQuery.add(rangeQuery,Occur.MUST);
booleanQuery.add(rangeQuery,Occur.MUST);
// + : 必须出现;
// - : 必须不出现;
// 无:可以出现可以不出现
syso:booleanQuery //+size:[0100 TO 0250] + title:lucene
// Occur是个枚举,MUST, MUSTNOT, SHOULD
// 课堂演示:两个都必须出现
+contents:李白 +name:黄鹤楼
contents:李白AND name:黄鹤楼
//课堂演示:1个都必须出现, 1个必须不出现
contents:李白-name:黄鹤楼
contents:李白NOT name:黄鹤楼
//课堂演示:出现1个即可(默认)
contents:李白name:黄鹤楼
contents:李白OR name:黄鹤楼
====上面这个默认可以修改
queryParser.setDefaultOperator(Operator.AND);
queryParser.parser();
9. 使用Lucene
面向对象的Lucene的操作框架: Compass
(1) compass-2.2:
jar + lucenejar;
(2) 使用
-- 1.cn.itcast.demo.bena.searchableArticle
id,title, content, postTime, authorName
-- 2.写映射文件或注解
SearchableArticle @Searchable
getId方法加上 @SearchableId
getXXX方法加上 @SearchableProperty(store=Store.YES,index=Index.ANALYZED, boost=3f)
// 根据需要设置
-- 3.ArticleIndexDAO
课堂上没有写接口,实际编程需要写;
create(), delete(), update();
QueryResult search(Query, first, max); // 返回可以分页的结果集
QueryResult search(String, first, max);
--- 4. 实现DAO: 同Hibernate
CompassConfiguration cfg;
cfg.setConnection("c:/compassIndex/"); // cfg.xml
cfg.addClass(SearchableArticle.class); // hbm.xml
Compass compass = cfg.buildCompass();
CompassSession session = compass.openSession();
CompassTransaction tx = session.beginTransaction;
session.create(article);
session.delete(session.get(SearchableArticle.class, id));
session.save(article);
tx.commit();
session.close();
JdbcTemplate;
CompassTemplate ct = new CompassTemplate();
ct = new CompassTemplate(session);
ct.save(article);
CompassHitshits = session.find(queryString);
for( CompassHit hit : hits ) {
SearchableArticlearticle = (SearchableArticle)hit.data();
}
for( int i=first; i<end; i++) {
SearchableArticlearticle = (SearchableArticle)hits.data(i);
items.add(article);
}
returnnew QueryResult(hits.getLength(), items);
==== 从现在起,分页返回的结果不再只是内容列表,还要包括总记录数;
关于id,创建的时候不检查,可以创建多个相同的id的索引;
update的时候,会删除所有的相同id的内容,再将新的内容放入索引;
高亮器的使用:
//如果进行高亮的属性值中没有出现关键字,则返回null
String ht =hits.highlighter(i).fragment("content");
if ( ht != null ) {
article.setContent(ht);
}
else {
int endIndex = Math.min(100,article.length());
String newValue =article.subString(0, endIndex);
article.setContent(newValue);
}
items.add(article);
高亮器的使用:改变前后缀;可以在文档的附录中找
cfg.setSetting([highlighter].formatter.simple.pre,"<fontcolor='blue'>");
更改分词器:创建索引和搜索
cfg.setSetting("...analyzer.defalut.type", CJK);
默认是单字索引,这里用CJK;
CompassQuery compassQuery =CompassQueryBuilder.queryString("查询语句").toQuery();
//term查询
compassQuery= queryBuilder.term(name, value);
//范围查询
compassQuery= queryBuiler.between();
queryBuilder.lt/gt...
//...
CompassHitshits = compassQuery.hits();