昊天

Lucene 的学习

 通过这几天的看书和学习,对Lucene有了更进一步的认识,所以总结一下这些天的学习成果把Lucene的学习心得也学出来。

1          Lucene的认识

提到Lucene很多人都知道这个开源的搜索工具,其魅力也是很大的。它让我们对搜索引擎的认识不在那么神秘,也不会在觉得百度和google的技术多么的高深没测,其实其原理都是一样的,只是他们要做的更好,走的更远罢了。

Lucene可以对任何的数据做索引和搜索,说这样的话其实不过分,真的就是这样,只要你能处理好这些数据,交给Lucene去建立索引它都可以帮你把这些数据给检索出来,是不是很好玩了。真正好玩的地方还在后面呢。

2          Lucene的学习

前面已经对Lucene有了一些了解,现在我们想象它怎么去搜索这些数据呢,如果知道倒排索引,你就知道了,其实lucene检索的是它自己建立的索引,从索引中的到数据的指针,从而得到数据。其实就这么简单。

提到索引,现在的索引技术中有:倒排索引、后缀数组和签名文件这三种,其中后缀数组这种技术虽然检索速度也很快,但是它的数据结构构造和维护都是相当麻烦的所以不可取了。我也懒得去看了。至于签名文件嘛,那是80年代的玩意了,现在已经过时了。现在可是倒排索引的天下啊!相信百度和google都是这种技术。

3          索引的建立

我们从索引的建立入手:

我们建立一个lucene的索引时必须先建立该索引文件存放的位置,看一下代码:


IndexWriter writer = null;

writer 
= new IndexWriter("c:\\index"new CJKAnalyzer(), true);


这段代码就时建立一个索引前所必须的操作,先声明这个
IndexWriter,实例化它你必须传入三个参数。他们分别代表:


你要建立索引文件的存放位置
你要使用索引建立的分词方法
是否重新建立索引


你要建立索引文件的存放位置、你要使用索引建立的分词方法、是否重新建立索引。这样你就告诉lucene我要在c盘的index目录下建立索引文件,我要使用车东老师的二分词算法做分析器、我要在这个目录下删除以前的索引或任何文件创立我的索引文件。

索引的建立有三种方式,让我一一道来:

1在内存中建立索引,速度最快但是耗资源,而且重启就没了。

new IndexWriter(new RAMDirectory(), new StandardAnalyzer(), true);


   2在文件系统中建立索引,这里有两个参数,分别是:建立索引的路径、是否要删除当前目录下的文件重新建立索引。


new IndexWriter(FSDirectory.getDirectory(path, true), new StandardAnalyzer(), true);


3最常见的一种,在制定目录下建立索引,看了源码你就知道这种方法也是用的第二种方式。Lucene的源码:


new IndexWriter("c:\\index"new CJKAnalyzer(), true);


 

Indexwriter性能调整参数:


第一个优化的参数
mergeFactor 这个参数用于控制lucene在把索引从内存写入到磁盘上的文件系统时内存最大的Document对象的数量。这个数要根据你的计算机设置,默认情况下是10

   
第二个优化的参数maxMergeFactor 这个参数用来设置当有多少个Segment时进行合并操作。当然我们知道当索引文件太多的话其检索的速度就会很慢,所以我们要当文件数量一定时让它进行索引的合并。这样就可以加快索引速度,但是这个值要根据你的情况而定。当文档数量较多时我们将值设大些,当文档数量较少时我们将值设小些。


第三个优化的参数
minMergeDocs 这个参数用于控制内存中文档的数量。

 


这样我们建立索引已经完成,接下来我们要建立
Document对象,因为你必须告诉我要搜索什么吧!好了,看看源码:


File file = new File("1.txt"); 
Document doc 
= new Document();
doc.add(Field.UnIndexed(
"filename", file.getName()));
FileInputStream fis 
= new FileInputStream(file);
byte[] b = new byte[fis.available()];
fis.read(b);
String content 
= new String(b);
doc.add(Field.Text(
"content", content));
fis.close();


以上我们就完成了将
1.txt文件放到我们的Document对象了。这里我们用了Field.Text();这样的操作和doc.add();这样的方法建立的。这也是建立索引的必须。

稍微介绍一下Field,它就是你要建立索引的字段。它分别有

类型/方法

是否分词

是否索引

是否存储

常用实例

Keyword(String,String)

Keyword(String,Date)

电话号码,身份证,人名,地名,日期

Unindexed(String,String)

文档类型,文档名称

UnStored(String,String)

文档的标题和内容

Text(String,String)

文档的标题和内容

Text(String,Reader)

文档的标题和内容

   
这样我们要建什么样的索引就对号入座吧,只要最后我们使用doc.add(Field.Text("content", content));把它添加到Document中就可以了。


   
这时我们的文档已经建立好了,现在就开始向索引中添加文档吧!这里我们使用writer.addDocument(doc);来向Indexwriter索引中添加构造好的文档。

这样我们是不是就可以说我们已经建立完了索引呢,其实不然,我们还要优化优化,这样才快嘛!对不对?

    writer.optimize();这样一句话就可以实现索引优化了,具体的优化过程我就不说了,是不是很简单。但是一定不要忘了哦。调用这个方法时最好建立一个合适的周期。定期进行优化。

    好了,这样我们就完成了索引的建立了。


   
下面我们看看缩影的合并吧!

当我们在很多地方建立了很多的索引后,想要合并这些索引我们怎么办呢?

    使用IndexWriter.assIndexs(New Directory[]{path});就可以对path路径下的索引合并到当前的索引中了。

    下面再看看索引的删除吧!

    有一些过时的索引我们需要删除,怎么办呢?


   IndexReader reader = IndexReader.open("c:\\index");

      reader.delete(
0);


这样我们就可以按照文档的顺序删除对应的文档了,但是这样不太现实,不对吗?我们怎么会知道文档的顺序呢?

下面我们看看第二中方法:


IndexReader reader = IndexReader.open("c:\\index");

reader.delete(
new Term("name","word1"));

reader.close();


按照字段来删除对应的文档,这样合理多了。以后要删除时就按照词条的方式去删除吧
!

索引锁:write.lock , commit.lock.

write.lock 是为了避免几个线程同时修改一个索引文档而设置。当实例一个indexwrite时建立和使用indexReader删除文档时建立。

Commit.lock 该锁主要在segment在建立,合并或读取时生成。

4          Lucene的搜索 

以上完成了索引的建立和一些关于索引的知识,但是光有索引是不行的,我们真正要做的检索,这才是我们的关键。现在我们看看lucene的检索吧。

认识检索从检索的工具开始吧!IndexSearcher类是lucene用于检索的工具类,我们在检索之前要得到这个类的实例。

第一步我们看以下代码:

 

IndexSearcher searcher = new IndexSearcher("c:\\index");

 

创建IndexSearcher实例需要告诉lucene索引的位置,就是你IndexWrite的文件路径。


Query query = null;
Hits hits 
= null;
query 
= QueryParser.parse(key1, "name"new StandardAnalyzer());
hits 
= searcher.search(query);
if (hits != null{
           
if (hits.length() == 0{
              System.out.println(
"没有找到任何结果");
           }
 else {
              System.out.print(
"找到");
              
for (int i = 0; i < hits.length(); i++{
                  Document d 
= hits.doc(i);
                  String dname 
= d.get("title");
                  System.out.print(dname 
+ "   " );
              }

           }

       }

}


以上就是一个完整的检索过程,这里我们看见了个QueryHits,这两个类就是比较关键的了,我们先从检索结果的Hits类说起。

我们使用Hits经常使用的几个方法有:


length() :  返回搜索结果的总数量。
Doc(
int n) : 放回第n个文档。
Id(
int n) :返回第n个文档的内部编号。
Sorce(
int n) : 返回第n个文档的得分。


看见这个
Sorce(int n) 这个方法,是不是就可以联想到搜索引擎的排序问题呢,像百度的推广是怎么做出来的呢,可想而知吧,那就说明必定存在一中方法可以动态的改变某片文档的得分。对了,lucene中可以使用DocumentsetBoost方法可以改变当前文档的boost因子。

下面我们看看:

Document doc1 = new Document();
doc1.add(Field.Text(
"contents""word1 word"));
doc1.add(Field.Keyword(
"path""path\\document1.txt"));
doc1.setBoost(
1.0f);

 

 这样我们就在改变了篇文档的评分了,当boost的值越大它的分值就越高,其出现的位置就越靠前。

让我们再来看看lucene为我们提供的各种Query吧。

第一、   按词条搜索TermQuery

query = new TermQuery(new Term("name","word1"));
hits 
= searcher.search(query);


这样就可以把
fieldname的所有包含word1的文档检索出来了。

第二、  “与或”搜索BooleanQuery

它实际是一个组合query 看看下面的代码:


query1 = new TermQuery(new Term("name","word1"));
query2 
= new TermQuery(new Term("name","word2"));
query 
= new BooleanQuery();
query.add(query1, 
falsefalse);
query.add(query2, 
falsefalse);
hits 
= searcher.search(query);


看看
booleanQuery的用法吧:


true & true : 表明当前加入的字句是必须要满足的。相当于逻辑与。
false & true : 表明当前加入的字句是不可一被满足的,相当于逻辑非。
false & false : 表明当前加入的字句是可选的,相当于逻辑或。
true & true : 错误的情况。


Lucene
可以最多支持连续1024query的组合。

第三、  在某一范围内搜索RangeQuery


IndexSearcher searcher = new IndexSearcher("c:\\index");
Term beginTime 
= new Term("time","200001");
Term endTime 
= new Term("time","200005");
Hits hits 
= null;
RangeQuery query 
= new RangeQuery(beginTime, endTime, false);
hits 
= searcher.search(query);


RangeQuery
的构造函数的参数分别代表起始、结束、是否包括边界。这样我们就可以按照要求检索了。

第四、  使用前缀检索PrefixQuery

这个检索的机制有点类似于indexOf() 从前缀查找。这个常在英文中使用,中文中就很少使用了。代码如下:

IndexSearcher searcher = new IndexSearcher("c:\\index");
Term pre1 
= new Term("name""Da");
query 
= new PrefixQuery(pre1);
hits 
= searcher.search(query);

 

第五、  多关键字的搜索PhraseQuery

可以多个关键字同时查询。使用如下:


       query = new PhraseQuery();
       query.add(word1);
       query.add(word2);
       query.setSlop(
0);
       hits 
= searcher.search(query);
       printResult(hits, 
"'david'与'mary'紧紧相隔的Document");
       query.setSlop(
2);
       hits 
= searcher.search(query);
       printResult(hits, 
"'david'与'mary'中相隔两个词的短语");

   
这里我们要注意query.setSlop();这个方法的含义。

query.setSlop(0);  紧紧相连(这个的条件比较苛刻)

query.setSlop(2);  相隔

第六、  使用短语缀搜索PharsePrefixQuery

使用PharsePrefixQuery可以很容易的实现相关短语的检索功能。

实例:

query = new PhrasePrefixQuery();
// 加入可能的所有不确定的词
Term word1 = new Term("content""david");
Term word2 
= new Term("content""mary");
Term word3 
= new Term("content""smith");
Term word4 
= new Term("content""robert");
query.add(
new Term[]{word1, word2});
// 加入确定的词
query.add(word4);
query.setSlop(
2);
hits 
= searcher.search(query);
printResult(hits, 
"存在短语'david robert'或'mary robert'的文档");

第七、  相近词语的搜索fuzzyQuery

可以通俗的说它是一种模糊查询。 

实例:

Term word1 = new Term("content""david");
Hits hits 
= null;
FuzzyQuery query 
= null;
query 
= new FuzzyQuery(word1);
hits 
= searcher.search(query);
printResult(hits,
"与'david'相似的词");

第八、  使用通配符搜索WildcardQuery

实例:

Term word1 = new Term("content""*ever");
Term word2 
= new Term("content""wh?ever");
Term word3 
= new Term("content""h??ever");
Term word4 
= new Term("content""ever*");
WildcardQuery query 
= null;
Hits hits 
= null;
query 
= new WildcardQuery(word1);
hits 
= searcher.search(query);
printResult(hits, 
"*ever");
query 
= new WildcardQuery(word2);
hits 
= searcher.search(query);
printResult(hits, 
"wh?ever");  
query 
= new WildcardQuery(word3);
hits 
= searcher.search(query);
printResult(hits, 
"h??ever");  
query 
= new WildcardQuery(word4);
hits 
= searcher.search(query);
printResult(hits, 
"ever*");



由上可以看出通配符?代便
1个字符,*代表0到多个字符。

Lucene现在支持以上八中的搜索方式,我们可以根据需要选择适合自己的搜索方式。当然上面提供的一些可能对英文还是比较有效,中文就不可取了,所以我们开始想想百度,我们只在一个输入框中搜索结果。有了这个疑问我们揭开下一章的讨论吧!

查询字符串的解析:这个就是我们经常在一个输入框中输入我们要检索的文字,交给搜索引擎去帮我们分词。

QueryParser类就是对查询字符串的解析类。

看看它的用法: 


query = QueryParser.parse(key1, "name"new StandardAnalyzer());
hits 
= searcher.search(query);


它直接返回一个
Query 对象。需要传入的参数分别是:

用户需要查询的字符串、需要检索的对应字段名称、采用的分词类。


Analyzer analyzer = new CJKAnalyzer();
String[] fields 
= {"filename""content"};
Query query 
= MultiFieldQueryParser.parse(searchword, fields, analyzer);
Hits hits 
= searcher.search(query);


QueryParser
的“与”“或”:

QueryParser之间默认是或,我们想改变为与的话加入以下代码:


QueryParser.setOperator(QueryParser.DEFAULT_OPERATOR_AND);

就可以了。

5          高级搜索技巧

前面我们已经介绍了一般情况下lucene的使用技巧,现在我们探讨一下高级搜索的技巧吧!

1、对搜索结果进行排序:

1)使用sort类排序:


    Sort sort = new Sort();
    hits 
= searcher.search(query,sort);


这种方式是使用默认的
sort排序方式进行排序。默认的sort排序是按照相关度进行排序。即通过luence的评分机制进行排序。

2) 对某一字段进行排序


Sort sort = new Sort(“content”);
hits 
= searcher.search(query,sort);


   3)
对多个字段进行排序


Sort sort = new Sort(new SortField[]{new SortField("title"),new SortField("contents")});
hits 
= searcher.search(query,sort);


2、
多域搜索和多索引搜索:

在使用luecene时,如果查询的只是某些terms,而不关心这些词条到时来自那个字段中时。这时可以使用MultiFieldQueryParser类。这个用于用户搜索含有某个关键字是否存在在字段中,他们之间的关系使用OR连接。即不管存在在哪一个字段都会显示显示出来。

使用MultiSearcher可以满足同时多索引的搜索需求。


Searcher[] searchers = new Searcher[2]; 
searchers[
0= new IndexSearcher(indexStoreB);
searchers[
1= new IndexSearcher(indexStoreA);
// 创建一个多索引检索器
Searcher mSearcher = new MultiSearcher(searchers);

3、   对搜索结果进行过滤:

1)    对时间进行过滤

        通常情况下我们对搜索结果要进行过滤显示,即只显示过滤后的结果。

doc.add(Field.Keyword("datefield", DateField.timeToString(now - 1000)));

DateFilter df1 = DateFilter.Before("datefield", now);

2)        查询过滤器

通过查询过滤器可以过滤一部分的信息。

Filter filter = new Filter()

        {

       public BitSet bits (IndexReader reader) throws IOException

          {

            BitSet bitset = new BitSet(5);

            bitset.set (1);

            bitset.set (3);

            return bitset;

          }

        };

        //生成带有过滤器的查询对象

        Query filteredquery = new FilteredQuery (query, filter);

       //返回检索结果

        Hits hits = searcher.search (filteredquery);

 

这样我们就可以使用自己定义的过滤方式去过滤信息了。

3)    带缓存的过滤器:

使用待缓存的过滤器我们可以重用过滤功能,如下:

MockFilter filter = new MockFilter();

 CachingWrapperFilter cacher = new CachingWrapperFilter(filter);

        cacher.bits(reader);

以上介绍完了现在学习luence,没有太详细的介绍它的实现,因为它对于我们来说是一个工具,既然是工具我们就要会用就可以了。

posted on 2012-02-09 14:51 昊天 阅读(557) 评论(0)  编辑  收藏 所属分类: lucence


只有注册用户登录后才能发表评论。


网站导航:
 

导航

<2012年2月>
2930311234
567891011
12131415161718
19202122232425
26272829123
45678910

统计

留言簿(1)

随笔分类

随笔档案

搜索

最新评论

阅读排行榜

评论排行榜