dotlucene

DotLucene搜索引擎Demo之:创建索引DotLucene的官方网站这样介绍:DotLucene是一个专门为NET设计的功能强大的搜索引擎!他上面也有一个在线demo,搜索3.5GB的文本数据所花费的时间在0.1秒左右!大家可以点这里测试。我也记得在我的一个网站 
99收藏夹(注1)里面有个在线帮助,他是用StreamReader来读文本数据的,其他的是读数据库,我发现不管是读数据库还是一xml的形式读xml文档,不管你的数据库如何优化,也不管你的机器培植如何之高,读的速度与读文本数据的速度是不可比的,大家也可到http:
//www.99scj.com测试下。点在线帮助,一闪就出来了。 
本文是按照DotLucene官方网站的一个demo 写的,本同点在于, 
1,本文的demo采用了DotLucene最新稳定版1.4.3 
2,开发环境是vs2005。 
3,demo被我划成了两个部分,一个部分是一个console程序,就是本文所讲的,主要就是怎么样创建索引,另 
      一个部分是个web程序,关键说的是搜索这个部分所建立的索引。 
4,源代码将在下个部分提供下载,因为这两个部分同属一个解决方案。 
好了,我们现在开始进入怎么用DotLucene来创建索引了。 
什么是索引呢?我也不太明白,我是这样理解的,索引就是用来加快查询数据的速度的,比如我们小时候读书的时候课本前面有那个第一课:什么什么的。。。。。。第几页,这应该就是索引吧。用DotLucene创建索引也就是说把某些文件内容编入某个目录下的索引。
首先运行vs2005,选择文件
--新建项目,在弹出的对话框选:其他项目类型里的Visual Studio解决方案,选右边的空白解决方案,输入名字:SearchDemo,位置选D:\确定。
再在解决方案SearchDemo右键选添加
---再选---新建解决方案文件夹,输入文件夹名字为Indexer.我再找到d:\SearchDemo,再这个文件夹里面新建个目录,叫wwwroot,大家就知道这个目录是放weB 程序的,我们在iis管理器里面新建设个虚拟目录,指向d:\SearchDemo\wwwroot目录,名字叫SearchDemo.
我们再在vs的解决方案SearchDemo单季右键选添加
--新建解决方案文件夹,输入文件夹名字为web,其实这两个文件夹名都是vs虚拟的,其实并不存在。我们然后在第一个项目Indexer上右键选:添加==新建项目,再弹出的面板左边选v c#--windows--右边选控制台应用程序,输入名字Indexer,确定,这个时候vs会在d:\searchdemo目录下添加个Indexer目录,然后我们再在新添加的名为web项目上右建--添加---现有网站---选择我们刚刚建立的SearchDemo就是。
现在我们建设好了两个项目,一个console控制台项目和一个SearchDemo的web项目,我们这部分只讲第一个项目怎么样建立索引,要建立索引,我们首先必须明白索引应该建立在什么地方?为了方便我把索引建立在D:\SearchDemo\wwwroot目录下新建一个index目录下;还有我们必须明白哪些文件将被编入索引?也为了方便,我把要被编入索引的文件放在d:\SearchDemo\wwwroot目录下新建个documents目录下,也就是说documents目录下的所有文件都将被我编入索引。因为我们这个demo 演示的是搜索DotLucene的帮助文档,文艺我们把所有下载来的帮助文档文件全部拷入d:\SearchDemo\wwwroot\documents目录下。同时我们还必须赋予index目录写的权利。
我们现在给Indexer控制台项目添加引用Lucene.Net.dll
我们现在在Indexer控制台项目里添加个类:IntrnetIndexer.cs;
先说明下doc.Add(Field.UnStored(
"text", ParseHtml(html)));
            doc.Add(Field.Keyword(
"path", relativePath));
            doc.Add(Field.Text(
"title", GetTitle(html)));
索引是由Docuemnt对象组成,而Docuemnt对象又是由字段对象组成.
Field.UnStored方法在其官方网站上的说明是:Constructs a String
-valued Field that is tokenized and indexed, but that is not stored in the index. Term vector will not be stored for this Field.搜价110的Eunge帮忙翻译下成这样:构造一个String类型的字段,它将被分词和索引,但是它不会被存储在索引中。关于这个字段的词向量不会被存储,我一直都未能够理解关于这个字段的词向量不会被存储的含义,汗。
代码如下:

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Index;


namespace Indexer
{
    
public class IntranetIndexer
    
{
        
//索引写入器
        private IndexWriter writer;
        
//要写入索引的文件的根目录
        private string docRootDirectory;
        
//要匹配的文件格式
        private string pattern;
        
/// <summary>
        
/// 初始化一个索引写入器writer,directory为创建索引的目录,true代表如果不存在索引文件将重新创建索引文件,如果已经存在索引文件将覆写索引文件,如果为true将代表打开已经存在的索引文件
        
/// </summary>
        
/// <param name="directory">传入的要创建索引的目录,注意是字符串值,如果目录不存在,他将会被自动创建</param>

        public IntranetIndexer(string directory)
        
{
            writer 
= new IndexWriter(directory,new StandardAnalyzer(),true);
            writer.SetUseCompoundFile(
true);
        }

        
public void AddDirection(DirectoryInfo directory,string pattern)
        
{
            
this.pattern = pattern;
            
this.docRootDirectory = directory.FullName;
            AddSubDirectory(directory);
        }

        
private void AddSubDirectory(DirectoryInfo directory)
        
{
            
foreach (FileInfo fi in directory.GetFiles(pattern))
            

               
//遍历要写入索引的目录的所有文件,把他先加入Docuemnt对象,再加入索引,因为索引都是有Document对象组成
                AddHtmlToDocument(fi.FullName);
            }

            
foreach (DirectoryInfo di in directory.GetDirectories())
            

                
//层层遍历递归,只到把所有的子目录子文件都搞完
                AddSubDirectory(di);
            }

        }

        
private void AddHtmlToDocument(string path)
        
{
            Document doc 
= new Document();
            
string html;
            
using (StreamReader sr = new StreamReader(path, System.Text.Encoding.Default))
            
{
                html 
= sr.ReadToEnd();
            }

            
int relativePathStartsAt = this.docRootDirectory.EndsWith("\\"? this.docRootDirectory.Length : this.docRootDirectory.Length + 1;
            
string relativePath = path.Substring(relativePathStartsAt);

            doc.Add(Field.UnStored(
"text", ParseHtml(html)));
            doc.Add(Field.Keyword(
"path", relativePath));
            doc.Add(Field.Text(
"title", GetTitle(html)));
            writer.AddDocument(doc);
        }

        
/// <summary>
        
/// 把读取的文件中的所有的html标记去掉,把&nbsp;替换成空格
        
/// </summary>
        
/// <param name="html"></param>
        
/// <returns></returns>

        private string ParseHtml(string html)
        
{
            
string temp = Regex.Replace(html, "<[^>]*>""");
            
return temp.Replace("&nbsp;"," ");
        }

        
/// <summary>
        
/// 获得读取的html文挡的标题
        
/// </summary>
        
/// <param name="html"></param>
        
/// <returns></returns>

        private string GetTitle(string html)
        
{
            Match m 
= Regex.Match(html,"<title>(.*)</title>");
            
if (m.Groups.Count == 2)
                
return m.Groups[1].Value;
            
return "此文挡标题未知";
        }


        
public void Close()
        
{
            writer.Optimize();
            writer.Close();
        }

    }

}



DotLucene搜索引擎Demo之:搜索索引在上篇文章我们说了怎么建立索引,现在说的是怎么搜索这个索引,最主要的我们是要理解startAt的含义,理解了他什么问题都解决了。还有这个例子的分页很经典,我发现google和baidu用的都是这个分页方法。主要就两个方法,一个search()方法,主要是显示当前页的搜索记录
 1
protected void search()
 
2    {
 
3        DateTime start = DateTime.Now;//搜索的开始时间
 4        //得到索引所在的目录,我们在上个console程序里把索引放到了index目录下
 5        string indexDirectory = Server.MapPath("index");
 
6        //创建个索引搜索器
 7        IndexSearcher searcher = new IndexSearcher(indexDirectory);
 
8        //分词并解析索引的text字段以便搜索
 9        Query thisQuery = QueryParser.Parse(this.Query,"text",new StandardAnalyzer());
10        //为要绑定输出到页面的results建立几列
11        this.Results.Columns.Add("path",typeof(string));
12        this.Results.Columns.Add("sample",typeof(string));
13        this.Results.Columns.Add("title",typeof(string));
14        //开始搜索
15        Hits hits = searcher.Search(thisQuery);
16        //得到搜索返回的记录总数
17        this.total = hits.Length();
18        //创建一个高亮
19        QueryHighlightExtractor highlighter = new QueryHighlightExtractor(thisQuery, new StandardAnalyzer(), "<B>""</B>");
20        //初始化startAt,以便得到要显示的结果集
21        this.startAt = initStartAt();
22        //得到当前页要显示的记录数量,包括以前所有页的记录数,这样把他与this.startAt结合就能够很好的知道当前页要显示的记录数了
23        int resultsCount = smallOf(this.total,this.startAt+this.maxResults);
24        //开始循环得到当前页要显示的记录
25        for (int i = this.startAt; i < resultsCount; i++)
26        
27           //得到每一行Hits的Document,因为Hits的没一行都是个Document对象
28            Document doc = hits.Doc(i);
29            //得到doc里面的列path的值
30            string path = doc.Get("path");
31            //再得到这个路径在web程序的路径,我们原来把文档放到了web根目录的documents目录下的
32            string location = Server.MapPath(@"documents\"+path);
33            //用StreamReader读取文档,因为我们不能够直接从索引中得到text字段的值,因为我们建立索引的时候没有存储他的
34            string plainText;
35            using (StreamReader sr = new StreamReader(location, System.Text.Encoding.Default))
36            {
37                plainText = ParseHtml(sr.ReadToEnd());
38            }

39            //为结果集DataTable,Results添加个新行
40            DataRow dr = this.Results.NewRow();
41            dr["title"= doc.Get("title");
42            dr["path"= @"documents/" + path;
43            dr["sample"= highlighter.GetBestFragment(plainText,80);
44            //把行添加进DataTable
45            this.Results.Rows.Add(dr);
46        }

47        //循环完毕,关闭搜索
48        searcher.Close();
49        //搜索花费多少时间
50        this.duration = DateTime.Now - start;
51        //给fromItem赋值,他总是startAt+1
52        this.fromItem = this.startAt + 1;
53        //给toItem赋值
54        this.toItem = smallOf(this.total,this.startAt+this.maxResults);
55
56    }
还有就是一个Paging属性,他的作用就是分页,输出分页的html这个属性很经典
 
1/**//// <summary>
 2    /// 开始分页
 3    /// </summary>
 4    /// <returns></returns>
 5    protected DataTable Paging
 
6    {
 
7        get
 
8        
 
9           //知道了startAt,分页也很容易了,现在根据startAt得到当前是第几页,注意,现在这里的页数也是暂时从0开始的
10            int pageNumber = (this.startAt + this.maxResults - 1/ this.maxResults;
11            DataTable dt = new DataTable();
12            dt.Columns.Add("html");
13            DataRow dr = dt.NewRow();
14            //暂时得到当前页的html连接,注意这里当真正显示页数的时候要+1
15            dr["html"= pagingNumberHtml(startAt,pageNumber+1,false);
16            dt.Rows.Add(dr);
17            //前面显示10页,如果有的话
18            int previousPagesCount = 10;
19            //循环把前面页的html连接插到前面去 
20            for (int i = pageNumber - 1; i >= 0 && i >= pageNumber - previousPagesCount; i--)
21            {
22                DataRow r = dt.NewRow();
23                r["html"= pagingNumberHtml(i*this.maxResults,i+1,true);
24                dt.Rows.InsertAt(r,0);;
25            }

26            //后面也显示10页,如果有的话
27            int nextPagesCount = 10;
28            for (int i = pageNumber + 1; i <= this.pageCount && i <= pageNumber + nextPagesCount; i++)
29            {
30                DataRow r = dt.NewRow();
31                r["html"= pagingNumberHtml(i*this.maxResults,i+1,true);
32                dt.Rows.Add(r);
33            }

34            //添加下一页的超级连接
35            DataRow lastRow = dt.NewRow();
36            lastRow["html"= "<a href='Search.aspx?q="+this.Query+"&start="+(pageNumber+1)*this.maxResults+"'>下一页</a>";
37            dt.Rows.Add(lastRow);
38            return dt;
39
40        }
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Threading;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using Lucene.Net.Index;
using Lucene.Net.Documents;
using Lucene.Net.Analysis.Cn;
using System.IO;
using Lucene.Net.QueryParsers;
using Lucene.Net.Search;
using Lucene.Net.Search.Highlight;
using System.Text.RegularExpressions;
using XK_Vote.DAL;
using System.Net;
namespace XK_Vote
{
 /// <summary>
 /// WebForm1 的摘要说明。
 /// </summary>
 public    class WebForm1 : System.Web.UI.Page
 {
  protected System.Web.UI.WebControls.Button Button2;
  protected System.Web.UI.WebControls.Button Button1;
  /// <summary>
  /// 搜索返回的结果集
  /// </summary>
  protected DataTable Results = new DataTable();
  /// <summary>
  /// 搜索返回的结果总数
  /// </summary>
  private int total;
  /// <summary>
  /// 搜索的结果每页显示多少记录条数
  /// </summary>
  private readonly int maxResults = 10;
  /// <summary>
  /// 本次搜索所花费的时间
  /// </summary>
  private TimeSpan duration;
  /// <summary>
  /// 本次搜索是从哪条数据开始的,这个东西很重要,通过他基本上可以得到一切
  /// </summary>
  private int startAt;
  /// <summary>
  /// 本页显示的数据是从哪条开始的,他总是startAt+1
  /// </summary>
  private int fromItem;
  /// <summary>
  /// 本页显示的数据记录到哪条结束
  /// </summary>
  private int toItem;
  protected System.Web.UI.WebControls.TextBox TextBox1;
        protected string pg;
  protected void Page_Load(object sender, System.EventArgs e)
  {
   // 在此处放置用户代码以初始化页面

  }

  #region Web 窗体设计器生成的代码
  override protected void OnInit(EventArgs e)
  {
   //
   // CODEGEN: 该调用是 ASP.NET Web 窗体设计器所必需的。
   //
   InitializeComponent();
   base.OnInit(e);
  }
  
  /// <summary>
  /// 设计器支持所需的方法 - 不要使用代码编辑器修改
  /// 此方法的内容。
  /// </summary>
  private void InitializeComponent()
  {   
   this.Button1.Click += new System.EventHandler(this.Button1_Click);
   this.Button2.Click += new System.EventHandler(this.Button2_Click);
   this.Load += new System.EventHandler(this.Page_Load);

  }
  #endregion
  protected void IndexBook(Result rs,IndexWriter writer)
  {
    Document doc = new Document();                  
    doc.Add(Field.Keyword("Title", rs.Title));
       doc.Add(Field.Keyword("CopyFrom",rs.CopyFrom));
       doc.Add(Field.Keyword("UpdateTime",rs.UpdateTime.ToString()));
    doc.Add(Field.Text("Content", rs.Content));
       doc.Add(Field.Text("DefaultPicUrl",rs.DefaultPicUrl));
       doc.Add(Field.Text("IncludePic",rs.InclucePic.ToString()));
    writer.AddDocument(doc);
  
  }
  private string ParseHtml(string html)
  {
   string temp = Regex.Replace(html,"<[^>]*>|<|>","");
   return temp.Replace("&nbsp;"," ");
  }
  protected void Button1_Click(object sender, System.EventArgs e)
  {
   for(int t=0;t<1;t++)
   {
    System.Threading.Thread thread=new Thread(new ThreadStart(InitIndex));
    thread.Start();
   }

  }
  private IndexWriter GetWriter(string physicalPath)
  {
   IndexWriter indexWriter = null;
   string segmentFile = System.IO.Path.Combine(physicalPath, "segments");
   if ( System.IO.File.Exists(segmentFile) )
    indexWriter = new IndexWriter(physicalPath, new Lucene.Net.Analysis.Cn.ChineseAnalyzer(), false);
   else
    indexWriter = new IndexWriter(physicalPath, new Lucene.Net.Analysis.Cn.ChineseAnalyzer(), true);

   return indexWriter;

  }
        private void InitIndex()
        {
   int index=0;
   int end = 0;
//   IndexWriter iw=new IndexWriter(@"D:\Index",ca,true); 
   IndexWriter iw=GetWriter(@"D:\study\XK_Vote\Index");
   iw.mergeFactor = 15;
   while(true)
   {
    DB db=new DB();
    System.Data.SqlClient.SqlDataReader sdr=null; 
    System.Data.SqlClient.SqlParameter [] p={db.MakeInParam("@id",SqlDbType.Int,4,index)};
    db.RunProc("GZ_SelectText",p,out sdr);
    int count = 0;
    while(sdr.Read())
    {
     Result rs=new Result();
     rs.Title=sdr["Title"].ToString();
     rs.Content=this.ParseHtml(sdr["Content"].ToString());
     rs.CopyFrom=sdr["CopyFrom"].ToString();
     if(Convert.ToInt32(sdr["IncludePic"])==1)
     {
      rs.InclucePic=Convert.ToInt32(sdr["IncludePic"]);
      rs.DefaultPicUrl=sdr["DefaultPicUrl"].ToString();
     }
     else
     {
      rs.InclucePic=0;
      rs.DefaultPicUrl="";
     }
     rs.UpdateTime=Convert.ToDateTime(sdr["UpdateTime"].ToString());
     IndexBook(rs,iw);
     index=Convert.ToInt32(sdr["ArticleID"]);
     count++;
     
    }
    end++;
    if(count < 100-1)
     break;
    if(end > 200)
     break;
              

   }
   iw.Optimize();
   iw.Close();

        }
  private void Button2_Click(object sender, System.EventArgs e)
  { 
   this.Query=Convert.ToString(Request.Form["Content"]);
//   Search();
   DataBind();

  }
  /*protected void Search()
  {
   DateTime start = DateTime.Now;//搜索的开始时间
   Lucene.Net.Search.IndexSearcher search=new Lucene.Net.Search.IndexSearcher(@"D:\LuceneIndex");
   string [] fields={"Content","Title"};
            Query multiquery = MultiFieldQueryParser.Parse(this.Query,fields,new ChineseAnalyzer());
   this.Results.Columns.Add("Title",typeof(string));
   this.Results.Columns.Add("Content",typeof(string));
//   this.Results.Columns.Add("CopyFrom",typeof(string));
//   this.Results.Columns.Add("UpdateTime",typeof(string));
   QueryHighlightExtractor highlighter = new QueryHighlightExtractor(multiquery, new ChineseAnalyzer(), "<font color=red>", "</font>");
   Lucene.Net.Search.Hits hit =search.Search(multiquery);
 
   //初始化startAt,以便得到要显示的结果集
   this.total=hit.Length();
   this.startAt = initStartAt();
   //得到当前页要显示的记录数量,包括以前所有页的记录数,这样把他与this.startAt结合就能够很好的知道当前页要显示的记录数了
   int resultsCount = smallOf(this.total,this.startAt+this.maxResults);
   //开始循环得到当前页要显示的记录ex
   for (int i = this.total - this.startAt-1; i >= this.total - resultsCount; i--)
   {  
    DataRow dr    = this.Results.NewRow();
    dr["Title"]   = highlighter.GetBestFragments(hit.Doc(i).GetField("Title").StringValue(),1,2,"...");
    dr["Content"] = highlighter.GetBestFragments(hit.Doc(i).GetField("Content").StringValue(),80,2,"...");
//                dr["CopyFrom"]= hit.Doc(i).GetField("CopyFrom").StringValue();
//    dr["UpdateTime"]=hit.Doc(i).GetField("UpdateTime").StringValue();
    //把行添加进DataTable
    this.Results.Rows.Add(dr);

   }
   //循环完毕,关闭搜索
   search.Close();
   //搜索花费多少时间
   this.duration = DateTime.Now - start;
   Response.Write(this.duration);
   //给fromItem赋值,他总是startAt+1
   this.fromItem = this.startAt + 1;
   //给toItem赋值
   this.toItem = smallOf(this.total,this.startAt+this.maxResults);
   
  }*/
  /// <summary>
  /// 开始分页
  /// </summary>
  /// <returns></returns>
  protected DataTable Paging
  {
   get
   {
    //知道了startAt,分页也很容易了,现在根据startAt得到当前是第几页,注意,现在这里的页数也是暂时从0开始的
    int pageNumber = (this.startAt + this.maxResults - 1) / this.maxResults;
    DataTable dt = new DataTable();
    dt.Columns.Add("html");
    DataRow dr = dt.NewRow();
    //暂时得到当前页的html连接,注意这里当真正显示页数的时候要+1
    dr["html"] = pagingNumberHtml(startAt,pageNumber+1,false);
    dt.Rows.Add(dr);
    //前面显示10页,如果有的话
    int previousPagesCount = 10;
    //循环把前面页的html连接插到前面去
    for (int i = pageNumber - 1; i >= 0 && i >= pageNumber - previousPagesCount; i--)
    {
     DataRow r = dt.NewRow();
     r["html"] = pagingNumberHtml(i*this.maxResults,i+1,true);
     dt.Rows.InsertAt(r,0);;
    }
    //后面也显示10页,如果有的话
    int nextPagesCount = 10;
    for (int i = pageNumber + 1; i <= this.pageCount && i <= pageNumber + nextPagesCount; i++)
    {
     DataRow r = dt.NewRow();
     r["html"] = pagingNumberHtml(i*this.maxResults,i+1,true);
     dt.Rows.Add(r);
    }
    //添加下一页的超级连接
    DataRow lastRow = dt.NewRow();
    lastRow["html"] = "<a href='WebForm1.aspx?q=" + HttpUtility.UrlEncode(this.Query) + "&start=" + (pageNumber + 1) * this.maxResults + "'>下一页</a>";
    dt.Rows.Add(lastRow);
    return dt;

   }
  }
  /// <summary>
  /// 得到某一页的html连接字符串
  /// </summary>
  /// <param name="start">页是从那条记录开始搜索的</param>
  /// <param name="number">页数</param>
  /// <param name="active">是不是个超级连接</param>
  /// <returns></returns>
  private string pagingNumberHtml(int start, int number, bool active)
  {
   if (active)
   {
    return "<a href='WebForm1.aspx?q="+HttpUtility.UrlEncode(this.Query)+"&start="+start+"'>"+number+"</a>";
   }
   else
   {
    return "<span style='font-weight:bold;'>'+number+'</span>'";
   }
  }
  private int smallOf(int first, int second)
  {
   return first < second ? first : second;//得到两个数之间的较少数
  }
  private int initStartAt()
  {
   try
   {
    int sa = Convert.ToInt32(this.Request.Params["start"]);
    if (sa < 0)
     return 0;
    if (sa >= this.total - 1)
     return lastPageStartAt;//如果不足以显示三条,从最后页的第0条开始显示
    return sa;
   }
   catch
   {
    return 0;//如果输入非法非数字,那么从第0条开始显示
   }
  }
  private int pageCount
  {
   get
   {
    return (this.total - 1) / this.maxResults;//总页数,记住都是从0开始的
   }
  }
  private int lastPageStartAt
  {
   get
   {
    return pageCount * this.maxResults;//返回最末页的第0条的序号
   }
  }
  private string query="";
  protected string Query
  {
   get
   {
    return query;
   }
   set
   {
    this.query=value;
   }
  }
  protected string Summary
  {
   get
   {
    if (this.total > 0)
     return "共有结果<span style='color:red;'>' + this.total + '</span>,当前从第<span style='color:red;'>' + this.fromItem + '</span>条到第<span style='color:red;'>' + this.toItem + '</span>条,本次搜索耗时<span style='color:red;'>' + this.duration.TotalSeconds + '</span>秒'";
      else
     return "对不起,本次搜索没有找到任何结果";
   }
  }

 


 }

}

你可能感兴趣的:(Lucene)