本文仅记录一些简单的使用方法,供初学者参考。
以下例子采用 Lucene.NET 1.9 版本,可取去 Lucene.Net 下载。
1. 基本应用
using System;
using System.Collections.Generic;
using System.Text;
using Lucene.Net;
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.QueryParsers;
using Lucene.Net.Search;
using Lucene.Net.Store;
using Lucene.Net.Util;
namespace ConsoleApplication1.Lucene
{
public class LuceneTest
{
private const string FieldName = "name";
private const string FieldValue = "value";
private Directory directory = new RAMDirectory();
private Analyzer analyzer = new StandardAnalyzer();
public LuceneTest()
{
}
private void Index()
{
IndexWriter writer = new IndexWriter(directory, analyzer, true);
writer.maxFieldLength = 1000;
for (int i = 1; i <= 100; i++)
{
Document document = new Document();
document.Add(new Field(FieldName, "name" + i, Field.Store.YES, Field.Index.UN_TOKENIZED));
document.Add(new Field(FieldValue, "Hello, World!", Field.Store.YES, Field.Index.TOKENIZED));
writer.AddDocument(document);
}
writer.Optimize();
writer.Close();
}
private void Search()
{
Query query = QueryParser.Parse("name*", FieldName, analyzer);
IndexSearcher searcher = new IndexSearcher(directory);
Hits hits = searcher.Search(query);
Console.WriteLine("符合条件记录:{0}; 索引库记录总数:{1}", hits.Length(), searcher.Reader.NumDocs());
for (int i = 0; i < hits.Length(); i++)
{
int docId = hits.Id(i);
string name = hits.Doc(i).Get(FieldName);
string value = hits.Doc(i).Get(FieldValue);
float score = hits.Score(i);
Console.WriteLine("{0}: DocId:{1}; Name:{2}; Value:{3}; Score:{4}",
i + 1, docId, name, value, score);
}
searcher.Close();
}
}
}
除了 RAMDirectory,还可以使用 FSDirectory。(注意 FSDirectory.GetDirectory 的 create 参数,为 true 时将删除已有索引库文件,可以通过 IndexReader.IndexExists() 方法判断。)
从指定目录打开已有索引库。
private Directory directory = FSDirectory.GetDirectory("c:\index", false);
将索引库载入内存,以提高搜索速度。
private Directory directory = new RAMDirectory(FSDirectory.GetDirectory(@"c:\index", false));
//或
//private Directory directory = new RAMDirectory(c:\index");
2. 多字段搜索
使用 MultiFieldQueryParser 可以指定多个搜索字段。
Query query = MultiFieldQueryParser.Parse("name*", new string[] { FieldName, FieldValue }, analyzer);
IndexReader reader = IndexReader.Open(directory);
IndexSearcher searcher = new IndexSearcher(reader);
Hits hits = searcher.Search(query);
3. 多条件搜索
除了使用 QueryParser.Parse 分解复杂的搜索语法外,还可以通过组合多个 Query 来达到目的。
Query query1 = new TermQuery(new Term(FieldValue, "name1")); // 词语搜索
Query query2 = new WildcardQuery(new Term(FieldName, "name*")); // 通配符
//Query query3 = new PrefixQuery(new Term(FieldName, "name1")); // 字段搜索 Field:Keyword,自动在结尾添加 *
//Query query4 = new RangeQuery(new Term(FieldNumber, NumberTools.LongToString(11L)), new Term(FieldNumber, NumberTools.LongToString(13L)), true); // 范围搜索
//Query query5 = new FilteredQuery(query, filter); // 带过滤条件的搜索
BooleanQuery query = new BooleanQuery();
query.Add(query1, BooleanClause.Occur.MUST);
query.Add(query2, BooleanClause.Occur.MUST);
IndexSearcher searcher = new IndexSearcher(reader);
Hits hits = searcher.Search(query);
4. 设置权重
可以给 Document 和 Field 增加权重(Boost),使其在搜索结果排名更加靠前。缺省情况下,搜索结果以 Document.Score 作为排序依据,该数值越大排名越靠前。Boost 缺省值为 1。
Score = Score * Boost
通过上面的公式,我们就可以设置不同的权重来影响排名。
如下面的例子中根据 VIP 级别设定不同的权重。
Document document = new Document();
switch (vip)
{
case VIP.Gold: document.SetBoost(2F); break;
case VIP.Argentine: document.SetBoost(1.5F); break;
}
只要 Boost 足够大,那么就可以让某个命中结果永远排第一位,这就是百度等网站的"收费排名"业务。明显有失公平,鄙视一把。
5. 排序
通过 SortField 的构造参数,我们可以设置排序字段,排序条件,以及倒排。
Sort sort = new Sort(new SortField(FieldName, SortField.DOC, false));
IndexSearcher searcher = new IndexSearcher(reader);
Hits hits = searcher.Search(query, sort);
排序对搜索速度影响还是很大的,尽可能不要使用多个排序条件。
6. 过滤
使用 Filter 对搜索结果进行过滤,可以获得更小范围内更精确的结果。
举个例子,我们搜索上架时间在 2005-10-1 到 2005-10-30 之间的商品。
对于日期时间,我们需要转换一下才能添加到索引库,同时还必须是索引字段。
// index
document.Add(FieldDate, DateField.DateToString(date), Field.Store.YES, Field.Index.UN_TOKENIZED);
//...
// search
Filter filter = new DateFilter(FieldDate, DateTime.Parse("2005-10-1"), DateTime.Parse("2005-10-30"));
Hits hits = searcher.Search(query, filter);
除了日期时间,还可以使用整数。比如搜索价格在 100 ~ 200 之间的商品。
Lucene.Net NumberTools 对于数字进行了补位处理,如果需要使用浮点数可以自己参考源码进行。
// index
document.Add(new Field(FieldNumber, NumberTools.LongToString((long)price), Field.Store.YES, Field.Index.UN_TOKENIZED));
//...
// search
Filter filter = new RangeFilter(FieldNumber, NumberTools.LongToString(100L), NumberTools.LongToString(200L), true, true);
Hits hits = searcher.Search(query, filter);
使用 Query 作为过滤条件。
QueryFilter filter = new QueryFilter(QueryParser.Parse("name2", FieldValue, analyzer));
我们还可以使用 FilteredQuery 进行多条件过滤。
Filter filter = new DateFilter(FieldDate, DateTime.Parse("2005-10-10"), DateTime.Parse("2005-10-15"));
Filter filter2 = new RangeFilter(FieldNumber, NumberTools.LongToString(11L), NumberTools.LongToString(13L), true, true);
Query query = QueryParser.Parse("name*", FieldName, analyzer);
query = new FilteredQuery(query, filter);
query = new FilteredQuery(query, filter2);
IndexSearcher searcher = new IndexSearcher(reader);
Hits hits = searcher.Search(query);
7. 分布搜索
我们可以使用 MultiReader 或 MultiSearcher 搜索多个索引库。
MultiReader reader = new MultiReader(new IndexReader[] { IndexReader.Open(@"c:\index"), IndexReader.Open(@"\\server\index") });
IndexSearcher searcher = new IndexSearcher(reader);
Hits hits = searcher.Search(query);
或
IndexSearcher searcher1 = new IndexSearcher(reader1);
IndexSearcher searcher2 = new IndexSearcher(reader2);
MultiSearcher searcher = new MultiSearcher(new Searchable[] { searcher1, searcher2 });
Hits hits = searcher.Search(query);
还可以使用 ParallelMultiSearcher 进行多线程并行搜索。
8. 合并索引库
将 directory1 合并到 directory2 中。
Directory directory1 = FSDirectory.GetDirectory("index1", false);
Directory directory2 = FSDirectory.GetDirectory("index2", false);
IndexWriter writer = new IndexWriter(directory2, analyzer, false);
writer.AddIndexes(new Directory[] { directory });
Console.WriteLine(writer.DocCount());
writer.Close();
9. 显示搜索语法字符串
我们组合了很多种搜索条件,或许想看看与其对等的搜索语法串是什么样的。
BooleanQuery query = new BooleanQuery();
query.Add(query1, true, false);
query.Add(query2, true, false);
//...
Console.WriteLine("Syntax: {0}", query.ToString());
输出:
Syntax: +(name:name* value:name*) +number:[0000000000000000b TO 0000000000000000d]
呵呵,就这么简单。
10. 操作索引库
删除 (软删除,仅添加了删除标记。调用 IndexWriter.Optimize() 后真正删除。)
IndexReader reader = IndexReader.Open(directory);
// 删除指定序号(DocId)的 Document。
reader.Delete(123);
// 删除包含指定 Term 的 Document。
reader.Delete(new Term(FieldValue, "Hello"));
// 恢复软删除。
reader.UndeleteAll();
reader.Close();
增量更新 (只需将 create 参数设为 false,即可往现有索引库添加新数据。)
Directory directory = FSDirectory.GetDirectory("index", false);
IndexWriter writer = new IndexWriter(directory, analyzer, false);
writer.AddDocument(doc1);
writer.AddDocument(doc2);
writer.Optimize();
writer.Close();
11. 优化
批量向 FSDirectory 增加索引时,增大合并因子(mergeFactor )和最小文档合并数(minMergeDocs)有助于提高性能,减少索引时间。
IndexWriter writer = new IndexWriter(directory, analyzer, true);
writer.maxFieldLength = 1000; // 字段最大长度
writer.mergeFactor = 1000;
writer.minMergeDocs = 1000;
for (int i = 0; i < 10000; i++)
{
// Add Documentes...
}
writer.Optimize();
writer.Close();
相关参数说明
转自《深入 Lucene 索引机制》
利用 Lucene,在创建索引的工程中你可以充分利用机器的硬件资源来提高索引的效率。当你需要索引大量的文件时,你会注意到索引过程的瓶颈是在往磁盘上写索引文件的过程中。为了解决这个问题, Lucene 在内存中持有一块缓冲区。但我们如何控制 Lucene 的缓冲区呢?幸运的是,Lucene 的类 IndexWriter 提供了三个参数用来调整缓冲区的大小以及往磁盘上写索引文件的频率。
1.合并因子 (mergeFactor)
这个参数决定了在 Lucene 的一个索引块中可以存放多少文档以及把磁盘上的索引块合并成一个大的索引块的频率。比如,如果合并因子的值是 10,那么当内存中的文档数达到 10 的时候所有的文档都必须写到磁盘上的一个新的索引块中。并且,如果磁盘上的索引块的隔数达到 10 的话,这 10 个索引块会被合并成一个新的索引块。这个参数的默认值是 10,如果需要索引的文档数非常多的话这个值将是非常不合适的。对批处理的索引来讲,为这个参数赋一个比较大的值会得到比较好的索引效果。
2.最小合并文档数 (minMergeDocs)
这个参数也会影响索引的性能。它决定了内存中的文档数至少达到多少才能将它们写回磁盘。这个参数的默认值是10,如果你有足够的内存,那么将这个值尽量设的比较大一些将会显著的提高索引性能。
3.最大合并文档数 (maxMergeDocs)
这个参数决定了一个索引块中的最大的文档数。它的默认值是 Integer.MAX_VALUE,将这个参数设置为比较大的值可以提高索引效率和检索速度,由于该参数的默认值是整型的最大值,所以我们一般不需要改动这个参数。
疑惑:
数据成员可以分静态变量、非静态变量两种.
静态成员:静态类中的成员加入static修饰符,即是静态成员.可以直接使用类名+静态成员名访问此静态成员,因为静态成员存在于内存,非静态成员需要实例化才会分配内存,所以静态成员不能访问非静态的成员..因为静态成员存在于内存,所以非静态成员可以直接访问类中静态的成员.
非成静态员:所有没有加Static的成员都是非静态成员,当类被实例化之后,可以通过实例化的类名进行访问..非静态成员的生存期决定于该类的生存期..而静态成员则不存在生存期的概念,因为静态成员始终驻留在内容中..
一个类中也可以包含静态成员和非静态成员,类中也包括静态构造函数和非静态构造函数..
对于winApp来说,静态成员对于程序员本身省了很多事,而且因为静态成员驻留内存,在方法与方法之间传递共享数据的时候,所以静态成员成了我的首选..但是不要因为方便,大量使用,尤其是在内存紧张或者
用静态方法操作一些共享值的时候.或者要写多用户系统的时候,要慎之又慎.比如:
static int id = 0;
sql = "select * from table where id=" + id;
如果这样写的话,在单机测试的时候没有问题,但是在多人同时对数据进行测试的时候,就会有问题了.假如,A用户访问他的id是20,则id的值在内存中为20,而此时B用户访问,他的id是30,则id在内存中的值是30..A用户的id值则被更改了..如果此时你将这个方法用非静态成员来写,则不会出现这样的情况..因为非静态成员是你声明的时候,实例化的时候才会分配内存..所以A用户访问的时候,App会因为A实例化而给A用户的请求分配内存..而B用户访问的时候也一样会因为B用户的访问而分配内存..所以两个用户访问的是不同的内存块..所以不会出现数据覆盖和错乱的现象...
我想这样的情况应该能很好的说明静态变量和非静态成员的区别..
相对于webApp而言,在winApp下使用static的时候要比webApp下考虑的因素要少的多,因为webApp本来就是一个多用户的系统,所以使用static的时候更应该小心..
而我对static在webApp下的使用存在一个疑问,如果一个静态方法,例如:
static string aa(string str){
//经过一系列操作..
return str;
}
或者返回一个DataSet的静态方法
static DataSet aa(string str){
//经过一系列操作..
return DataSet;
}
这个时候,在访问量大的时候,程序出现了并发,会不会发生错乱??我以前的项目使用的公用函数类中使用了大量的静态方法,不过好在访问量不大,一直没有问题..在发这个文章之前,我查找了MSDN,CSDN,搜索了一些关于静态成员的文章,但是都没有一个明确的说明..虽然,自己在项目中也测试了这么长时间也没有问题..但是总觉得有这个可能发生..
不知道大家是否在项目中碰到类似的疑惑呢??请有过这方面经验的朋友指教..
答案:
不说是否滥用,如果你出现冲突,说明你没有理解静态成员变量和静态方法的区别,静态方法本身只是一段代码,不管怎么调用他都不会出现问题。但静态成员变量就不行了,他被所有用户共享,如果一个用户改变了他,肯定会影响到别人,这就是常说的并发冲突问题,一般来说在修改共享成员变量时要lock!
关于静态方法和实例方法的一些误区。
一、 静态方法常驻内存,实例方法不是,所以静态方法效率高但占内存。
事实上,方法都是一样的,在加载时机和占用内存上,静态方法和实例方法是一样的,在类型第一次被使用时加载。调用的速度基本上没有差别。
二、 静态方法在堆上分配内存,实例方法在堆栈上。
事实上所有的方法都不可能在堆或者堆栈上分配内存,方法作为代码是被加载到特殊的代码内存区域,这个内存区域是不可写的。
三、 实例方法需要先创建实例才可以调用,比较麻烦,静态方法不用,比较简单。
事实上如果一个方法与他所在类型的实例无关,那么它就应该是静态的,决不会有人把它写成实例方法。所以所有的实例方法都与实例有关,既然与实例有关,那么创建实例就是必然的步骤,没有麻烦简单一说。实际上上你可以把所有的实例方法都写成静态的,将实例作为参数传入即可。
有些方法看似与所在的实例无关,如IComparer.Compare方法,但实际上每一个实现这个接口的类都只会负责自己类型实例的比较,这是C#1.x规范中没有泛型所带来的历史遗留问题。
大部分静态方法是与类的实例有关的,如各种Parse方法,他做成静态的原因是他没有实例作为参数。其他的大多是出于语义或者其他目的的考虑。