Lucene
学习笔记
一、环境
需要导入
lucene.jar
包(在
lucene.apache.org
下载)
二、基本概念
1
.
Lucene
的工作流程:
(1)
使用
IndexWriter
,在指定的目录建立索引的文件
(2) 将需要检索的数据转换位 Document 的 Filed 对象,然后将 Document 用 IndexWriter 添加倒索引的文件中
(3) 处理索引信息,关闭 IndexWriter 流
(4) 创建搜索的 Query
(5) 给 IndexSearcher
(2) 将需要检索的数据转换位 Document 的 Filed 对象,然后将 Document 用 IndexWriter 添加倒索引的文件中
(3) 处理索引信息,关闭 IndexWriter 流
(4) 创建搜索的 Query
(5) 给 IndexSearcher
2
.
Lucene
的字段类型
Lucene
有四种不同的字段类型:
Keyword
,
UnIndexed
,
UnStored
和
Text
,用于指定建立最佳索引。
? Keyword 字段是指不需要分析器 解析但需要被编入索引并保存到索引中的部分。 JavaSourceCodeIndexer 类使用该字段来保存导入类的声明。
? UnIndexed 字段是既不被分析也不被索引,但是要被逐字逐句的将其值保存到索引中。由于我们一般要存储文件的位置但又很少用文件名作为关键字来搜索,所以用该字段来索引 Java 文件名。
? UnStored 字段和 UnIndexed 字段相反。该类型的 Field 要被分析并编入索引,但其值不会被保存到索引中。由于存储方法的全部源代码需要大量的空间。所以用 UnStored 字段来存储被索引的方法源代码。可以直接从 Java 源文件中取出方法的源代码,这样作可以控制我们的索引的大小。
? Text 字段在索引过程中是要被分析、索引并保存的。类名是作为 Text 字段来保存。下表展示了 JavaSourceCodeIndexer 类使用 Field 字段的一般情况。
? Keyword 字段是指不需要分析器 解析但需要被编入索引并保存到索引中的部分。 JavaSourceCodeIndexer 类使用该字段来保存导入类的声明。
? UnIndexed 字段是既不被分析也不被索引,但是要被逐字逐句的将其值保存到索引中。由于我们一般要存储文件的位置但又很少用文件名作为关键字来搜索,所以用该字段来索引 Java 文件名。
? UnStored 字段和 UnIndexed 字段相反。该类型的 Field 要被分析并编入索引,但其值不会被保存到索引中。由于存储方法的全部源代码需要大量的空间。所以用 UnStored 字段来存储被索引的方法源代码。可以直接从 Java 源文件中取出方法的源代码,这样作可以控制我们的索引的大小。
? Text 字段在索引过程中是要被分析、索引并保存的。类名是作为 Text 字段来保存。下表展示了 JavaSourceCodeIndexer 类使用 Field 字段的一般情况。
3
.基本概念(与传统表的对比):
Lucene
|
传统表
|
说明
|
IndexWriter
|
table
|
|
Document
|
一条记录
|
|
Field
|
每个字段
|
分为可被索引的,可切分的,不可被切分的,不可被索引的几种组合类型
|
Hits
|
RecoreSet
|
结果集
|
IndexWriter
提供了一些参数可供设置,列表如下
|
属性
|
默认值
|
说明
|
mergeFactor
|
org.apache.lucene.mergeFactor
|
10
|
控制
index
的大小和频率
,
两个作用
1.
一个段有多少
document
2.
多少个段合成一个大段
|
maxMergeDocs
|
org.apache.lucene.maxMergeDocs
|
Integer.MAX_VALUE
|
限制一个段中的
document
数目
|
minMergeDocs
|
org.apache.lucene.minMergeDocs
|
10
|
缓存在内存中的
document
数目,超过他以后会写入到磁盘
|
maxFieldLength
|
|
1000
|
一个
Field
中最大
Term
数目,超过部分忽略,不会
index
到
field
中,所以自然也就搜索不到
|
这些参数的的详细说明比较复杂:
mergeFactor
有双重作用
(1)
设置每
mergeFactor
个
document
写入一个段,比如每
10
个
document
写入一个段
(2)
设置每
mergeFacotr
个小段合并到一个大段,比如
10
个
document
的时候合并为
1
小段,以后有
10
个小段以后合并到一个大段,有
10
个大段以后再合并,实际的
document
数目会是
mergeFactor
的指数
简单的来说
mergeFactor
越大,系统会用更多的内存,更少磁盘处理,如果要打批量的作
index
,那么把
mergeFactor
设置大没错,
mergeFactor
小了以后,
index
数目也会增多,
searhing
的效率会降低,
但是
mergeFactor
增大一点一点,内存消耗会增大很多
(
指数关系
),
所以要留意不要”
out of memory”
把 maxMergeDocs 设置小,可以强制让达到一定数量的 document 写为一个段,这样可以抵消部分 mergeFactor 的作用 .
minMergeDocs 相当于设置一个小的 cache, 第一个这个数目的 document 会留在内存里面,不写入磁盘。这些参数同样是没有最佳值的, 必须根据实际情况一点点调整。
maxFieldLength 可以在任何时刻设置, 设置后,接下来的 index 的 Field 会按照新的 length 截取,之前已经 index 的部分不会改变。可以设置为 Integer.MAX_VALUE
把 maxMergeDocs 设置小,可以强制让达到一定数量的 document 写为一个段,这样可以抵消部分 mergeFactor 的作用 .
minMergeDocs 相当于设置一个小的 cache, 第一个这个数目的 document 会留在内存里面,不写入磁盘。这些参数同样是没有最佳值的, 必须根据实际情况一点点调整。
maxFieldLength 可以在任何时刻设置, 设置后,接下来的 index 的 Field 会按照新的 length 截取,之前已经 index 的部分不会改变。可以设置为 Integer.MAX_VALUE
4
.几种查询方式
查询方式
|
说明
|
TermQuery
|
条件查询
例如:
TermQuery tquery=new TermQuery(new Term("name","jerry"));
name:
字段名
jerry:
要搜索的字符串
|
MultiTermQuery
|
多个字段进行同一关键字的查询
Query query= null;
Query =MultiFieldQueryParser.parse("
我
",new String[] {"title","content"},analyzer);
Searcher searcher=new IndexSearcher(indexFilePath); Hits hits=searcher.search(query); |
BooleanQuery
|
例如:
BooleanQuery bquery=new BooleanQuery(); bquery.add(query,true,false); bquery.add(mquery,true,false); bquery.add(tquery,true,false); Searcher searcher=new IndexSearcher(indexFilePath); Hits hits=searcher.search(bquery); |
WildcardQuery
|
语义查询(通配符查询)
例:
Query query= new WildcardQuery(new Term("sender","*davy*"));
|
PhraseQuery
|
短语查询
|
PrefixQuery
|
前缀查询
|
PhrasePrefixQuery
|
短语前缀查询
|
FuzzyQuery
|
模糊查询
|
RangeQuery
|
范围查询
|
SpanQuery
|
范围查询
|
在全文检索时建议大家先采用语义时的搜索,先搜索出有意义的内容,之后再进行模糊之类的搜索
(1)
联合两个索引查询,已解决:
IndexSearcher[] searchers = new IndexSearcher[2];
searchers[0] = new IndexSearcher(m_indexpath);
searchers[1] = new IndexSearcher(m_outindexpath);
MultiSearcher multiSearcher = new MultiSearcher(searchers);
(2) 还有个进行多条件搜索 and 与 or 的操作————
用 MultiFieldQueryParser
建议重新封装
MultiFieldQueryParser.Parser(p[],d[],f[],analyer) 成 or 与 and 操作合一
或者
BooleanQuery m_BooleanQuery = new BooleanQuery();
Query query = QueryParser.Parse(m_SearchText, "INSTRUMENT_NAME", analyzer);
Query query2 = QueryParser.Parse(m_SearchText2, "INSTRUMENT_NAME2", analyzer);
m_BooleanQuery.Add(query, true, false);
m_BooleanQuery.Add(query2, true, false);
IndexSearcher[] searchers = new IndexSearcher[2];
searchers[0] = new IndexSearcher(m_indexpath);
searchers[1] = new IndexSearcher(m_outindexpath);
MultiSearcher multiSearcher = new MultiSearcher(searchers);
(2) 还有个进行多条件搜索 and 与 or 的操作————
用 MultiFieldQueryParser
建议重新封装
MultiFieldQueryParser.Parser(p[],d[],f[],analyer) 成 or 与 and 操作合一
或者
BooleanQuery m_BooleanQuery = new BooleanQuery();
Query query = QueryParser.Parse(m_SearchText, "INSTRUMENT_NAME", analyzer);
Query query2 = QueryParser.Parse(m_SearchText2, "INSTRUMENT_NAME2", analyzer);
m_BooleanQuery.Add(query, true, false);
m_BooleanQuery.Add(query2, true, false);
(3)
复合查询(多种查询条件的综合查询)
Query query=MultiFieldQueryParser.parse("
索引
”,new String[] {"title","content"},analyzer);
Searcher searcher=new IndexSearcher(indexFilePath);
Hits hits=searcher.search(query);
for (int i = 0; i < hits.length(); i++) {
System.out.println(hits.doc(i).get("name"));
}
Searcher searcher=new IndexSearcher(indexFilePath);
Hits hits=searcher.search(query);
for (int i = 0; i < hits.length(); i++) {
System.out.println(hits.doc(i).get("name"));
}
5.
为查询优化索引
(index)
Indexwriter.optimize()
方法可以为查询优化索引(
index
),之前提到的参数调优是为
indexing
过程本身优化,而这里是为查询优化,优化主要是减少
index
文件数,这样让查询的时候少打开文件,优化过程中,
lucene
会拷贝旧的
index
再合并,合并完成以后删除旧的
index
,所以在此期间,磁盘占用增加,
IO
符合也会增加,在优化完成瞬间,磁盘占用会是优化前的
2
倍
,
在
optimize
过程中可以同时作
search
。
4.org.apache.lucene.document.Field
即上文所说的“字段”,它是 Document 的片段 section 。
即上文所说的“字段”,它是 Document 的片段 section 。
Field
的构造函数:
Field(String name, String string, boolean store, boolean index, boolean token)
。
Indexed
:如果字段是
Indexed
的,表示这个字段是可检索的。
Stored
:如果字段是
Stored
的,表示这个字段的值可以从检索结果中得到。
Tokenized
:如果一个字段是
Tokenized
的,表示它是有经过
Analyzer
转变后成为一个
tokens
序列,在这个转变过程
tokenization
中,
Analyzer
提取出需要进行索引的文本,而剔除一些冗余的词句(例如:
a
,
the,they
等,详见
org.apache.lucene.analysis.StopAnalyzer.ENGLISH_STOP_WORDS
和
org.apache.lucene.analysis.standard.StandardAnalyzer(String[] stopWords)
的
API
)。
Token
是索引时候的
.
类型
|
Analyzed
|
Indexed
|
Stored
|
说明
|
Field.Keyword(String,String/Date)
|
N
|
Y
|
Y
|
这个
Field
用来储存会直接用来检索的比如
(
编号
,
姓名
,
日期等
)
|
Field.UnIndexed(String,String)
|
N
|
N
|
Y
|
不会用来检索的信息
,
但是检索后需要显示的
,
比如
,
硬件序列号
,
文档的
url
地址
|
Field.UnStored(String,String)
|
Y
|
Y
|
N
|
大段文本内容
,
会用来检索
,
但是检索后不需要从
index
中取内容
,
可以根据
url
去
load
真实的内容
|
Field.Text(String,String)
|
Y
|
Y
|
Y
|
检索
,
获取都需要的内容
,
直接放
index
中
,
不过这样会增大
index
|
Field.Text(String,Reader)
|
Y
|
Y
|
N
|
如果是一个
Reader, lucene
猜测内容比较多
,
会采用
Unstored
的策略
.
|
5.Lucene
的检索结果排序
Lucene
的排序主要是对
org.apache.lucene.search.Sort
的使用。
Sort
可以直接根据字段
Field
生成,也可以根据标准的
SortField
生成,但是作为
Sort
的字段,必须符合以下的条件:唯一值以及
Indexed
。可以对
Integers, Floats, Strings
三种类型排序。
对整数型的 ID 检索结果排序只要进行以下的简单操作:
对整数型的 ID 检索结果排序只要进行以下的简单操作:
Sort sort = new Sort("id");
Hits hits = searcher.search(query, sort);
Hits hits = searcher.search(query, sort);
用户还可以根据自己定义更加复杂的排序,详细请参考
API
。
6
.分析器
Lucene
使用分析器来处理被索引的文本。在将其存入索引之前,分析器用于将文本标记化、摘录有关的单词、丢弃共有的单词、处理派生词(把派生词还原到词根形式,意思是把
bowling
、
bowler
和
bowls
还原为
bowl
)和完成其它要做的处理。
Lucene
提供的通用分析器是:
SimpleAnalyzer :用字符串标记一组单词并且转化为小写字母。
StandardAnalyzer :用字符串标记一组单词,可识别缩写词、 email 地址、主机名称等等。并丢弃基于英语的 stop words (a, an, the, to) 等、处理派生词。
SimpleAnalyzer :用字符串标记一组单词并且转化为小写字母。
StandardAnalyzer :用字符串标记一组单词,可识别缩写词、 email 地址、主机名称等等。并丢弃基于英语的 stop words (a, an, the, to) 等、处理派生词。
ChineseAnalyzer.class,它是一个单字分析法,它把句子中的词全部分成一个一个的字符,以单个字为单位存储。
CJKAnalyzer.class,它是双字分析法,它把中文以双字为单位拆分得到结果,从而建立词条。当然这些得到的双字词中会有很多不符合中文语义单位的双字被送进索引。
十、需要注意的问题:
1 .IndexWriter
在添加新的
document
后,需要重新建立
Index
,则需要调用
writer.optimize();
方法
2. Lucene 没有 update 索引的方法,需要删除后重新建立,参考 remove 方法
3 . 用 IndexReader 删除 Document 后,需要重新用 IndexWriter 进行整理,否则无法在进行搜索(不知道是不是我设置问题)
2. Lucene 没有 update 索引的方法,需要删除后重新建立,参考 remove 方法
3 . 用 IndexReader 删除 Document 后,需要重新用 IndexWriter 进行整理,否则无法在进行搜索(不知道是不是我设置问题)
4.Lucene
先在内存中进行索引操作,并根据一定的批量进行文件的写入。这个批次的间隔越大,文件的写入次数越少,但占用内存会很多。反之占用内存少,但文件
IO
操作频繁,索引速度会很慢。在
IndexWriter
中有一个
MERGE_FACTOR
参数可以帮助你在构造索引器后根据应用环境的情况充分利用内存减少文件的操作。根据我的使用经验:缺省
Indexer
是每
20
条记录索引后写入一次,每将
MERGE_FACTOR
增加
50
倍,索引速度可以提高
1
倍左右。
5
.并发操作
Lucene
(1)
所有只读操作都可以并发
(2)
在
index
被修改期间,所有只读操作都可以并发
(3)
对
index
修改操作不能并发,一个
index
只能被一个线程占用
(4)ndex
的优化,合并,添加都是修改操作
(5)
但需要注意的是
,
在创建搜索的时候用
:
searcher = new IndexSearcher(IndexReader.open("E://lucene//test4//index"));
searcher.close();
这时候是不能关闭
searcher
的
.
如果想让
searcher
能关闭
,
就不要用
IndexReader
了
:
searcher = new IndexSearcher("E://lucene//test4//index");
6
.
Locking
机制
lucence
内部使用文件来
locking
,
默认的
locking
文件放在
java.io.tmpdir,
可以通过
-Dorg.apache.lucene.lockDir=xxx
指定新的
dir
,有
write.lock commit.lock
两个文件,
lock
文件用来防止并行操作
index
,如果并行操作,
lucene
会抛出异常,可以通过设置
-DdisableLuceneLocks=true
来禁止
locking
,这样做一般来说很危险,除非你有操作系统或者物理级别的只读保证,比如把
index
文件刻盘到
CDROM
上。
十一、
2.0
中新增特性
1.
新增类:
org.apache.lucene.index.IndexModifier
,它合并了
IndexWriter
和
IndexReader
,好处是我们可以增加和删除文档的时候不同担心
synchronisation/locking
的问题了。
2.
增加对
contrib/highlighter
的
NullFragmenter ,
这对全文本加亮很有用。
3.
增加了新类
MatchAllDocsQuery
用来匹配所有文档。
4..
增加
ParallelReader
,这个一种
IndexReader
他合并多个单独的索引到一个单独的虚拟索引上。
5.
增加
Hits.iterator()
方法和相应的
HitIterator
和
Hit
对象。
他提供了对 Hits 对象标准的 java.util.Iterator 叠代操作。
每个 iterator's next() 方法返回一个 Hit 对象。
他提供了对 Hits 对象标准的 java.util.Iterator 叠代操作。
每个 iterator's next() 方法返回一个 Hit 对象。
6.
在
term vectors
中增加了
位置和偏移信息。
(Grant Ingersoll & Christoph)
7.
增加了一个新的
DateTools
。允许用户格式化日期到一种更可读的格式,以便于更好的适应索引。
DateTools
不像
DateFields
类,它允许日期指定到
1970
年以前,但必须使用指定的日期格式。这样,在
RangeQuerys
中使用就更加有效率了。
8.
增加了对压缩字段存储的支持。
(patch #29370)
实例:
1.
判断索引文件是否存在
:
/**
* 检查索引是否存在 .
* @param indexDir
* @return
*/
public static boolean indexExist(String indexDir)
{
return IndexReader.indexExists(indexDir);
}
private IndexWriter getWriter(String indexFilePath) throws Exception {
boolean append=true;
File file=new File(indexFilePath+File.separator+"segments");
if(file.exists())
append=false;
return new IndexWriter(indexFilePath,analyzer,append);
}
* 检查索引是否存在 .
* @param indexDir
* @return
*/
public static boolean indexExist(String indexDir)
{
return IndexReader.indexExists(indexDir);
}
private IndexWriter getWriter(String indexFilePath) throws Exception {
boolean append=true;
File file=new File(indexFilePath+File.separator+"segments");
if(file.exists())
append=false;
return new IndexWriter(indexFilePath,analyzer,append);
}
2.
删除索引
/**
* 删除索引 .
* @param aTerm 索引删除条件
* @param indexDir 索引目录
*/
public static void deleteIndex(Term aTerm, String indexDir)
{
List aList = new ArrayList();
aList.add(aTerm);
deleteIndex(aList, indexDir);
}
* 删除索引 .
* @param aTerm 索引删除条件
* @param indexDir 索引目录
*/
public static void deleteIndex(Term aTerm, String indexDir)
{
List aList = new ArrayList();
aList.add(aTerm);
deleteIndex(aList, indexDir);
}
/**
* 删除索引 .
* @param aTerm 索引删除条件 .
* @param indexDir 索引目录 *
*/
public static void deleteIndex(List terms, String indexDir)
{
if (null == terms) {
return;
}
if(!indexExist(indexDir)) { return; }
* 删除索引 .
* @param aTerm 索引删除条件 .
* @param indexDir 索引目录 *
*/
public static void deleteIndex(List terms, String indexDir)
{
if (null == terms) {
return;
}
if(!indexExist(indexDir)) { return; }
IndexReader reader = null;
try {
reader = IndexReader.open(indexDir);
for (int i = 0; i < terms.size(); i++){
Term aTerm = (Term) terms.get(i);
if (null != aTerm){
reader.delete(aTerm);
}
}
} catch (IOException e){
LogMan.warn("Error in Delete Index", e);
} finally {
try{
if (null != reader){
reader.close();
}
}catch (IOException e){
LogMan.warn("Close reader Error");
}
}
}
try {
reader = IndexReader.open(indexDir);
for (int i = 0; i < terms.size(); i++){
Term aTerm = (Term) terms.get(i);
if (null != aTerm){
reader.delete(aTerm);
}
}
} catch (IOException e){
LogMan.warn("Error in Delete Index", e);
} finally {
try{
if (null != reader){
reader.close();
}
}catch (IOException e){
LogMan.warn("Close reader Error");
}
}
}
删除索引需要一个条件,类似数据库中的字段条件,例如删除一条新闻的代码如下:
public static void deleteNewsInfoIndex(int nid)
{
Term aTerm = new Term("nid", String.valueOf(nid));
deleteIndex(aTerm,indexDir);
}
public static void deleteNewsInfoIndex(int nid)
{
Term aTerm = new Term("nid", String.valueOf(nid));
deleteIndex(aTerm,indexDir);
}
注:本文有些知识是1.4下的,如果你用的是2.0可能这些例子不能很好的运行。不过我觉得看了以上的东西,再结合一些例子就能对lucene有一定的理解了,最起码可以开始干活了。在2.0版本中创建索引和进行多种搜索的例子我会陆继写出来与大家一起学习。