预览模式: 普通 | 列表

lucene.net系列三

--- index 中本文将进一步讨论有关lucene.NET建立索引的问题:

主要包含以下主题:
1.
索引的权重
2.
利用IndexWriter 属性对建立索引进行高级管理
3.
利用RAMDirectory充分发挥内存的优势
4.
利用RAMDirectory并行建立索引
5.
控制索引内容的长度
6.Optimize
优化的是什么?

源代码下载

本文将进一步讨论有关Lucene.net建立索引的问题:

索引的权重
根据文档的重要性的不同,显然对于某些文档你希望提高权重以便将来搜索的时候,更符合你想要的结果. 下面的代码演示了如何提高符合某些条件的文档的权重.

比如对公司内很多的邮件做了索引,你当然希望主要查看和公司有关的邮件,而不是员工的个人邮件.这点根据邮件的地址就可以做出判断比如包含@alphatom.com的就是公司邮件,@gmail.com等等就是私人邮件.如何提高相应邮件的权重? 代码如下:

     public static  String COMPANY_DOMAIN = "alphatom.com";
     Document doc = new Document();
     String senderEmail = GetSenderEmail();
     String senderName = getSenderName();
     String subject = GetSubject();
     String body = GetBody();
     doc.Add(Field.Keyword("senderEmail”, senderEmail));
     doc.Add(Field.Text("senderName", senderName));
     doc.Add(Field.Text("subject", subject));
     doc.Add(Field.UnStored("body", body));

     if (GetSenderDomain().EndsWith(COMPANY_DOMAIN)) 

    //如果是公司邮件,提高权重,默认权重是1.0
           doc.SetBoost(1.5);                      
     else                         //如果是私人邮件,降低权重.
           doc.SetBoost(0.1);

     writer.AddDocument(doc);

不仅如此你还可以对Field也设置权重.比如你对邮件的主题更感兴趣.就可以提高它的权重.   

    Field senderNameField = Field.Text("senderName", senderName);

     Field subjectField = Field.Text("subject", subject);
     subjectField.SetBoost(1.2);
lucene搜索的时候会对符合条件的文档按匹配的程度打分,这点就和googlePageRank有点类似, SetBoost中的Boost就是其中的一个因素,当然还有其他的因素.这要放到搜索里再说.

利用IndexWriter 变量对建立索引进行高级管理
在建立索引的时候对性能影响最大的地方就是在将索引写入文件的时候, 所以在具体应用的时候就需要对此加以控制.

在建立索引的时候对性能影响最大的地方就是在将索引写入文件的时候所以在具体应用的时候就需要对此加以控制

IndexWriter属性  

默认值

描述

MergeFactory

10

控制segment合并的频率和大小

MaxMergeDocs

Int32.MaxValue

限制每个segment中包含的文档数

MinMergeDocs

10

当内存中的文档达到多少的时候再写入segment

 

Lucene默认情况是每加入10份文档就从内存往index文件写入并生成一个segement,然后每10segment就合并成一个segment.通过MergeFactory这个变量就可以对此进行控制.

MaxMergeDocs用于控制一个segment文件中最多包含的Document.比如限制为100的话,即使当前有10segment也不会合并,因为合并后的segmnet将包含1000个文档,超过了限制.

MinMergeDocs用于确定一个当内存中文档达到多少的时候才写入文件,该项对segment的数量和大小不会有什么影响,它仅仅影响内存的使用,进一步影响写索引的效率.

为了生动的体现这些变量对性能的影响,用一个小程序对此做了说明.

这里有点不可思议.Lucene in Action书上的结果比我用dotLucene做的结果快了近千倍.这里给出书中用Lucene的数据,希望大家比较一下看看是不是我的问题.

Lucene in Action书中的数据:

% java lia.indexing.IndexTuningDemo 100000 10 9999999 10
Merge factor: 10
Max merge docs: 9999999
Min merge docs: 10
Time: 74136 ms
% java lia.indexing.IndexTuningDemo 100000 100 9999999 10
Merge factor: 100
Max merge docs: 9999999
Min merge docs: 10
Time: 68307 ms
我的数据: 336684128 ms
可以看出MinMergeDocs(主要用于控制内存)MergeFactory(控制合并的次数和合并后的大小) 对建立索引有显著的影响.但是并不是MergeFactory越大越好,因为如果一个segment的文档数很多的话,在搜索的时候必然也会影响效率,所以这里MergeFactory的取值是一个需要平衡的问题.MinMergeDocs主要受限于内存.

利用RAMDirectory充分发挥内存的优势

从上面来看充分利用内存的空间,减少读写文件(写入index)的次数是优化建立索引的重要方法.其实在Lucene中提供了更强大的方法来利用内存建立索引.使用RAMDirectory来替代FSDirectory. 这时所有的索引都将建立在内存当中,这种方法对于数据量小的搜索业务很有帮助,同时可以使用它来进行一些小的测试,避免在测试时频繁建立删除索引文件.

在实际应用中RAMDirectoryFSDirectory协作可以更好的利用内存来优化建立索引的时间.

具体方法如下:

1.建立一个使用FSDirectoryIndexWriter

2 .建立一个使用RAMDirectoryIndexWriter

3 Document添加到RAMDirectory

4 当达到某种条件将RAMDirectory 中的Document写入FSDirectory.

5 重复第三步

示意代码:
    
 private FSDirectory fsDir = FSDirectory.GetDirectory("index",true); 

       private RAMDirectory ramDir = new RAMDirectory();

       private IndexWriter fsWriter = IndexWriter(fsDir,new SimpleAnalyzer(), true);
       private IndexWriter ramWriter = new IndexWriter(ramDir,new SimpleAnalyzer(), true);
       while (there are documents to index)
      {
         ramWriter.addDocument(doc);
         if (condition for flushing memory to disk has been met)
         {
           fsWriter.AddIndexes(Directory[]{ramDir}) ;
           ramWriter.Close();          //why not support flush?
           ramWriter =new IndexWriter(ramDir,new SimpleAnalyzer(),true);
         }
     }

这里的条件完全由用户控制,而不是FSDirectory采用对Document计数的方式控制何时写入文件.相比之下有更大的自由性,更能提升性能.

利用RAMDirectory并行建立索引

RAMDirectory还提供了使用多线程来建立索引的可能性.下面这副图很好的说明了这一点.

 

 

甚至你可以在一个高速的网络里使用多台计算机来同时建立索引.就像下面这种图所示.

 

虽然有关并行同步的问题需要你自己进行处理,不过通过这种方式可以大大提高对大量数据建立索引的能力.

 

控制索引内容的长度.

在我的一篇速递介绍过Google Desktop Search只能搜索到文本中第5000个字的.也就是google在建立索引的时候只考虑前5000个字,Lucene中同样也有这个配置功能.

Lucene对一份文本建立索引时默认的索引长度是10,000. 你可以通过IndexWriter MaxFieldLength属性对此加以修改.还是用一个例子说明问题

     [Test]
     public void FieldSize()      
     // AddDocuments GetHitCount都是自定义的方法,详见源代码
     {
         AddDocuments(dir, 10);      
         //第一个参数是目录,第二个配置是索引的长度
         Assert.AreEqual(1, GetHitCount("contents", "bridges"))
         //原文档的contentsAmsterdam has lots of bridges
         //当索引长度为10个字时能找到bridge
         AddDocuments(dir, 1);
         Assert.AreEqual(0, GetHitCount("contents", "bridges"));
         //当索引长度限制为1个字时就无法发现第5个字bridges
     }

   对索引内容限长往往是处于效率和空间大小的考虑.能够对此进行配置是建立索引必备的一个功能.

Optimize 优化的是什么?

在以前的例子里,你可能已经多次见过writer.Optimize()这段代码.Optimize到底做了什么?

让你吃惊的是这里的优化对于建立索引不仅没有起到加速的作用,反而是延长了建立索引的时间.为什么?

因为这里的优化不是为建立索引做的,而是为搜索做.之前我们提到Lucene默认每遇到10Segment就合并一次,尽管如此在索引完成后仍然会留下几个segmnets,比如6,7.

Optimize的过程就是要减少剩下的Segment的数量,尽量让它们处于一个文件中.

它的过程很简单,就是新建一个空的Segmnet,然后把原来的几个segmnet全合并到这一个segmnet,在此过程中,你的硬盘空间会变大,因为同时存在两份一样大小的索引.不过在优化完成后,Lucene会自动将原来的多份Segments删除,只保留最后生成的一份包含原来所有索引的segment.

尽量减少segments的个数主要是为了增加查询的效率.假设你有一个Server,同时有很多的Client建立了各自不同的索引,如果此时搜索,那么必然要同时打开很多的索引文件,这样显然会受到很大的限制,对性能产生影响.

当然也不是随时做Optimize就好,如前所述做优化时要花费更多的时间和空间,而且在做优化的时候是不能进行查询的.所以索引建立的后期,并且索引的内容不会再发生太多的变化的时候做优化是一个比较好的时段.

分类:其他 | 固定链接 | 评论: 0 | 引用: 0 | 查看次数: 1267

lucene.net系列二

index 一详细介绍了有关lucene.NET索引添加删除更新的详细内容.并给出了所有的TestCase供学习参考.

Lucene建立Index的过程:

1.       抽取文本.

   比如将PDF以及Word中的内容以纯文本的形式提取出来.Lucene所支持的类型主要为String,为了方便同时也支持Date 以及Reader.其实如果使用这两个类型lucene会自动进行类型转换.

2.       文本分析.

   Lucene将针对所给的文本进行一些最基本的分析,并从中去除一些不必要的信息,比如一些常用字a ,an, the 等等,如果搜索的时候不在乎字母的大小写, 又可以去掉一些不必要的信息.总而言之你可以把这个过程想象成一个文本的过滤器,所有的文本内容通过分析, 将过滤掉一些内容,剩下最有用的信息.

3.       写入index.

google等常用的索引技术一样lucene在写index的时候都是采用的倒排索引技术(inverted index.) 简而言之,就是通过某种方法(类似hash?)将常见的一篇文档中含有哪些词这个问题转成哪篇文档中有这些词. 而各个搜索引擎的索引机制的不同主要在于如何为这张倒排表添加更准确的描述.比如google有名的PageRank因素.Lucene当然也有自己的技术,希望在以后的文章中能为大家加以介绍.

在上一篇文章中,使用了最基本的建立索引的方法.在这里将对某些问题加以详细的讨论.

1. 添加Document至索引
上次添加的每份文档的信息是一样的,都是文档的filenamecontents.

doc.Add(Field.Keyword("filename", file.FullName));
doc.Add(Field.Text("contents", new StreamReader(file.FullName)));

Lucene中对每个文档的描述是可以不同的,比如,两份文档都是描述一个人,其中一个添加的是name, age 另一个添加的是id, sex ,这种不规则的文档描述在Lucene中是允许的.
还有一点Lucene支持对Field进行Append , 如下:

string baseWord = "fast";
string synonyms[] = String {"quick", "rapid", "speedy"};
Document doc = new Document();
doc.Add(Field.Text("word", baseWord));
for (int i = 0; i < synonyms.length; i++)
    doc.Add(Field.Text("word", synonyms[i]));

这点纯粹是为了方便用户的使用.在内部Lucene自动做了转化,效果和将它们拼接好再存是一样.

2. 删除索引中的文档

    这一点Lucene所采取的方式比较怪,它使用IndexReader来对要删除的项进行标记,然后在Reader Close的时候一起删除.
这里简要介绍几个方法.

[TestFixture]
public class DocumentDeleteTest : BaseIndexingTestCase   // BaseIndexingTestCase
中的SetUp方法                                               //建立了索引其中加入了两个Document
{
    [Test]
    public void testDeleteBeforeIndexMerge()
    {
   
        IndexReader reader = IndexReader.Open(dir);  //
当前索引中有两个Document

        Assert.AreEqual(2, reader.MaxDoc());   //文档从0开始计数,MaxDoc表示下一个文档的序号

        Assert.AreEqual(2, reader.NumDocs());  //NumDocs表示当前索引中文档的个数 
        reader.Delete(1);                   //
对标号为1的文档标记为待删除,逻辑删除
        Assert.IsTrue(reader.IsDeleted(1));         //检测某个序号的文档是否被标记删除
        Assert.IsTrue(reader.HasDeletions());       //检测索引中是否有Document被标记删除
        Assert.AreEqual(2, reader.MaxDoc());        //
当前下一个文档序号仍然为2
        Assert.AreEqual(1, reader.NumDocs());       //
当前索引中文档数变成1
        reader.Close();                             //
此时真正从物理上删除之前被标记的文档
        reader = IndexReader.Open(dir);
        Assert.AreEqual(2, reader.MaxDoc());        
        Assert.AreEqual(1, reader.NumDocs());
        reader.Close();
    }

    [Test]
    public void DeleteAfterIndexMerge()    //
在索引重排之后
    {
        IndexReader reader = IndexReader.Open(dir);
        Assert.AreEqual(2, reader.MaxDoc());
        Assert.AreEqual(2, reader.NumDocs());
        reader.Delete(1);
        reader.Close();
        IndexWriter writer = new IndexWriter(dir, GetAnalyzer(), false);
        writer.Optimize();                 //
索引重排
        writer.Close();
        reader = IndexReader.Open(dir);
        Assert.IsFalse(reader.IsDeleted(1));
        Assert.IsFalse(reader.HasDeletions());
        Assert.AreEqual(1, reader.MaxDoc());       //
索引重排后,下一个文档序号变为1
        Assert.AreEqual(1, reader.NumDocs());
        reader.Close();
    }
}


当然你也可以不通过文档序号进行删除工作.采用下面的方法,可以从索引中删除包含特定的内容文档.

IndexReader reader = IndexReader.Open(dir);
reader.Delete(new Term("city", "Amsterdam"));
reader.Close();

你还可以通过reader.UndeleteAll()这个方法取消前面所做的标记,即在read.Close()调用之前取消所有的删除工作

3. 更新索引中的文档

   这个功能Lucene没有支持, 只有通过删除后在添加来实现. 看看代码,很好理解的.

[TestFixture]
public class DocumentUpdateTest : BaseIndexingTestCase
{
    [Test]
    public void Update()
    {
        Assert.AreEqual(1, GetHitCount("city", "Amsterdam"));
        IndexReader reader = IndexReader.Open(dir);
        reader.Delete(new Term("city", "Amsterdam"));
        reader.Close();
        Assert.AreEqual(0, GetHitCount("city", "Amsterdam"));
        IndexWriter writer = new IndexWriter(dir, GetAnalyzer(),false);
        Document doc = new Document();
        doc.Add(Field.Keyword("id", "1"));

 

        doc.Add(Field.UnIndexed("country", "Netherlands"));
        doc.Add(Field.UnStored("contents","Amsterdam has lots of bridges"));
        doc.Add(Field.Text("city", "Haag"));
        writer.AddDocument(doc);
        writer.Optimize();
        writer.Close();
        Assert.AreEqual(1, GetHitCount("city", "Haag"));
    }

 

    protected override Analyzer GetAnalyzer()
    {
        return new WhitespaceAnalyzer();  //
注意此处如果用SimpleAnalyzer搜索会失败,因为建立索引的时候使用的SimpleAnalyse它会将所有字母变成小写.

    }

    private int GetHitCount(String fieldName, String searchString)
    {
        IndexSearcher searcher = new IndexSearcher(dir);
        Term t = new Term(fieldName, searchString);
        Query query = new TermQuery(t);
        Hits hits = searcher.Search(query);
        int hitCount = hits.Length();
        searcher.Close();
        return hitCount;
    }
}

    需要注意的是以上所有有关索引的操作,为了避免频繁的打开和关闭WriterReader.又由于添加和删除是不同的连接(Writer, Reader)做的.所以应该尽可能的将添加文档的操作放在一起批量执行,然后将删除文档的操作也放在一起批量执行.避免添加删除交替进行.

分类:其他 | 固定链接 | 评论: 0 | 引用: 0 | 查看次数: 1265

lucene.net系列一

本文介绍了什么是lucene,Lucene能做什么.

如何从一个文件夹下的所有txt文件中查找特定的词?

本文将围绕该个实例介绍了lucene.NET的索引的建立以及如何针对索引进行搜索.最后还将给出源代码供大家学习.

源代码下载

What’s Lucene

Lucene是一个信息检索的函数库(Library),利用它你可以为你的应用加上索引和搜索的功能.

Lucene的使用者不需要深入了解有关全文检索的知识,仅仅学会使用库中的一个类,你就为你的应用实现全文检索的功能.

不过千万别以为Lucene是一个象google那样的搜索引擎,Lucene甚至不是一个应用程序,它仅仅是一个工具,一个Library.你也可以把它理解为一个将索引,搜索功能封装的很好的一套简单易用的API.利用这套API你可以做很多有关搜索的事情,而且很方便.

What Can Lucene Do

Lucene可以对任何的数据做索引和搜索. Lucene不管数据源是什么格式,只要它能被转化为文字的形式,就可以被Lucene所分析利用.也就是说不管是MS word, HTML ,pdf还是其他什么形式的文件只要你可以从中抽取出文字形式的内容就可以被Lucene所用.你就可以用Lucene对它们进行索引以及搜索.

How To Use Lucene --- A Simple Example

示例介绍:

为作为输入参数的文件夹下的所有txt类型的文件做索引,做好的索引文件放入index文件夹.

然后在索引的基础上对文件进行全文搜索.

1.       建立索引

IndexWriter writer = new IndexWriter("index", new StandardAnalyzer(), true);

IndexDocs(writer, new System.IO.FileInfo(args[0]));              

writer.Optimize();

writer.Close();

IndexWriter是对索引进行写操作的一个类,利用它可以创建一个索引对象然后往其中添加文件.需要注意它并不是唯一可以修改索引的类.在索引建好后利用其他类还可以对其进行修改.

构造函数第一个参数是建立的索引所要放的文件夹的名字.第二个参数是一个分析对象,主要用于从文本中抽取那些需要建立索引的内容,把不需要参与建索引的文本内容去掉.比如去掉一些a the之类的常用词,还有决定是否大小写敏感.不同的选项通过指定不同的分析对象控制.第三个参数用于确定是否覆盖原有索引的.

第二步就是利用这个writer往索引中添加文件.具体后面再说.

第三步进行优化.

第四步关闭writer.

 

下面具体看看第二步:

   public static void IndexDirectory(IndexWriter writer, FileInfo file)

         {

              if (Directory.Exists(file.FullName))

              {

                   String[] files = Directory.GetFileSystemEntries(file.FullName);

                   // an IO error could occur

                   if (files != null)

                   {

                       for (int i = 0; i < files.Length; i++)

                       {

                            IndexDirectory(writer, new FileInfo(files[i]));  //这里是一个递归

                       }

                   }

              }

              else if (file.Extension == ".txt")

              {

                   IndexFile(file, writer);

              }

         }

 

         private static void IndexFile(FileInfo file, IndexWriter writer)

         {

              Console.Out.WriteLine("adding " + file);

              try

              {

                   Document doc = new Document();                   

                   doc.Add(Field.Keyword("filename", file.FullName));

                   doc.Add(Field.Text("contents", new StreamReader(file.FullName)));

                   writer.AddDocument(doc);

              }

             

              catch (FileNotFoundException fnfe)

              {

                  

              }

     }

主要就是两个函数一个用于处理文件夹(不是为文件夹建立索引),一个用于真正为文件建立索引.

因此主要集中看一下IndexFile这个方法.首先建立Document对象,然后为Document对象添加一些属性Field.你可以把Document对象看成是虚拟文件,将来将从此获取信息.而Field则看成是描述此虚拟文件的元数据(metadata).

其中Field包括四个类型:

 

Keywork

 

该类型的数据将不被分析,而会被索引并保存保存在索引中.

 

UnIndexed

 

该类型的数据不会被分析也不会被索引,但是会保存在索引.

查看更多...

分类:其他 | 固定链接 | 评论: 0 | 引用: 0 | 查看次数: 1314

JAVA 强制数据类型转换

class Change{
   public static void main(String[] args){
  
   short shortvar=0;
   int intvar=0;
   String stringvar;
   float floatvar=9.99f;
   double doublevar=99999999.99;
   char charvar='9';
   String intstring="10";
   String floatstring="10.1f";
   String longstring="99999999";
   String doubleString="99999999.9";
   String s1="0";
   String s2="abc";
   String s3="true";
  
   //1.short-->int
   intvar=shortvar;
   System.out.println(intvar);

//2.int-->short
shortvar= (short) intvar ;
   System.out.println(shortvar);
  
//3.int->String
   intvar=1;
   stringvar=String.valueOf (intvar);
   System.out.println(stringvar);
  
   //4.float->String
   stringvar=String.valueOf (floatvar);
   System.out.println(stringvar);
  
   //5. double->String
   stringvar=String.valueOf (doublevar);
   System.out.println(stringvar);
  
   //6. char->String
   stringvar=String.valueOf (charvar);
   System.out.println(stringvar);
  
   //7 String->int、float、long、double
   int i=Integer.parseInt (intstring);
   float f= Float.parseFloat (floatstring);
   long lo=Long. parseLong (longstring);
   double d=Double. parseDouble(doubleString);
   System.out.println(i+"\n"+f+"\n"+lo+"\n"+d);
  
   //8 String->byte、short
   byte b=Byte.parseByte(s1);
   short sh=Short.parseShort(s1);
   System.out.println(b+"\n"+sh) ;
  
   //9 String->char
   char a=s2.charAt(0);
   System.out.println(a);
  
   //10 String-->boolean
   boolean flag=Boolean.getBoolean(s3);
   System.out.println(s3);
  
  
   }
  }  
分类:其他 | 固定链接 | 评论: 0 | 引用: 0 | 查看次数: 1285

Lucene 2.1研究:文件存储

一.           lucene原始存储数据类型(Primitive Types)

 

类型名称

查看更多...

分类:其他 | 固定链接 | 评论: 0 | 引用: 0 | 查看次数: 1217

初识 Lucene

本文首先介绍了Lucene的一些基本概念,然后开发了一个应用程序演示了利用Lucene建立索引并在该索引上进行搜索的过程。
Lucene 简介

Lucene 是一个基于 Java 的全文信息检索工具包,它不是一个完整的搜索应用程序,而是为你的应用程序提供索引和搜索功能。Lucene 目前是 Apache Jakarta 家族中的一个开源项目。也是目前最为流行的基于 Java 开源全文检索工具包。

查看更多...

分类:其他 | 固定链接 | 评论: 0 | 引用: 0 | 查看次数: 1071

中文全文检索的实现以及一些经验

最近在项目中面临中文全文检索的需求,关键需求如下:
1 支持中文、英文字词的全文检索,待检索文本是古文言文。
2 全文检索表达式支持: AND,OR,NOT,NEAR,BEFORE 运算符,支持()。
3 速度要求:400M文本,要求在2-5秒内能够检索完毕。

查看更多...

Tags: 中文 全文检索

北京线点科技 专业垂直搜索引擎解决方案提供商 http://www.xd-tech.com.cn

分类:全文检索 | 固定链接 | 评论: 0 | 引用: 0 | 查看次数: 1302

浅谈图片搜索引擎的实现

 leo在他的《博客营销》中说不清楚全文检索博客的领域是什么,呵呵,整个Minidx.com其实都只不过是一个自己随便涂鸦的地方,倒还真没考虑过什么领域,更加没有考虑过自己的博客应该专注于哪一方面……如果非要划分,大概http://minidx.com勉强还能算得上一点“领域”吧。呵呵,无所谓了,随便涂鸦吧……OODA SAN说他在研究图片搜索引擎,所以就和他探讨了一些图片搜索的实现的问题,这里也记录一下自己的思路,OODA是这方面的专家,而我只能算是“新手上路”,下面说的有什么不对的还望包涵指出,:)

简单的说,图片搜索是搜索引擎针对网络上的图片所提供的服务。包括Google, Yahoo!, Ask, MSN和AOL以及国内的Baidu这些大型搜索引擎都提供有图片搜索,还有号称图片搜索专用的picsearch,但它们并非真正地对文件中的图像进行搜索,而是对附加在图片中的文字(比如img标签的alt属性)以及文件名进行搜索,也就是通常意义上的关键字索引,所以实质上用的还是基于文本内容的检索,因此也只能搜索数量较少的文件,IBM 公司的研究人员也曾经开发一种名为Marvel的可以实现音像资料搜索的搜索引擎,它能够获取目前在互联网上很难获取的音像资料,只是没有关注过目前的进展。目前微软也正在研究可以通过头像来查找某人的信息这样的搜索引擎,微软将之成为Photo2Search,“a picture is worth a thousand words”,真正意义上的多媒体搜索引擎的实现,带给人们的影响,将可以与蒸汽机,电脑相媲美……

查看更多...

分类:其他 | 固定链接 | 评论: 0 | 引用: 0 | 查看次数: 1292