??xml version="1.0" encoding="utf-8" standalone="yes"?> 今天用了下LuceneQ发现网上虽然也有不介l它的文档,不过很多都偏向介l概念呀、设计或者是一些更为深入的东西Q对于其入门使用的介l性的文档q不多,写了这么一?br />
Lucene
基本使用介绍
本文的目的不在于对Lucene的概念和设计q些q行介绍Q仅在于介绍怎么样去使用Lucene来达到自己想要的几种常见的全文检索的需求,如果x入了解Lucene的话本文不会带给你什么收L。看完本文后x深入的了解Lucene误问:http://lucene.apache.org 一. 概述 随着pȝ信息的越来越多,怎么样从q些信息h中捞赯己想要的那一栚w变得非帔R要了Q全文检索是通常用于解决此类问题的方案,而Lucene则ؓ实现全文索的工具QQ何应用都可通过嵌入它来实现全文索?/p>
? 环境搭徏 从lucene.apache.org上下载最新版本的lucene.jarQ将此jar作ؓ目的build pathQ那么在目中就可以直接使用lucene了?/p>
? 使用说明 3.1. 基本概念 q里介绍的主要ؓ在用中l常到一些概念,以大安比较熟悉的数据库来进行类比的讲解Q用Luceneq行全文索的q程有点cM数据库的q个q程Qtable---à查询相应的字D|查询条g----àq回相应的记录,首先是IndexWriterQ通过它徏立相应的索引表,相当于数据库中的tableQ在构徏此烦引表旉指定的ؓ该烦引表采用何种方式q行构徏Q也是说对于其中的记录的字D以什么方式来q行格式的划分,q个在Lucene中称为AnalyzerQLucene提供了几U环境下使用的AnalyzerQSimpleAnalyzer、StandardAnalyzer、GermanAnalyzer{,其中StandardAnalyzer是经怋用的Q因为它提供了对于中文的支持Q在表徏好后我们需要往里面插入用于索引的记录,在Lucene中这个称为DocumentQ有点类似数据库中table的一行记录,记录中的字段的添加方法,在Lucene中称为FieldQ这个和数据库中基本一P对于Field Lucene分ؓ可被索引的,可切分的Q不可被切分的,不可被烦引的几种l合cdQ通过q几个元素基本上可以徏立v索引了。在查询时经常碰到的为另外几个概念,首先是QueryQLucene提供了几U经常可以用到的QueryQTermQuery、MultiTermQuery、BooleanQuery、WildcardQuery、PhraseQuery、PrefixQuery、PhrasePrefixQuery、FuzzyQuery、RangeQuery、SpanQueryQQuery其实也就是指对于需要查询的字段采用什么样的方式进行查询,如模p查询、语义查询、短语查询、范围查询、组合查询等Q还有就是QueryParserQQueryParser可用于创Z同的QueryQ还有一个MultiFieldQueryParser支持对于多个字段q行同一关键字的查询QIndexSearcher概念指的为需要对何目录下的烦引文件进行何U方式的分析的查询,有点象对数据库的哪种索引表进行查询ƈ按一定方式进行记录中字段的分解查询的概念Q通过IndexSearcher以及Query卛_查询出需要的l果QLuceneq回的ؓHits.通过遍历Hits可获取返回的l果的DocumentQ通过Document则可获取Field中的相关信息了?/p>
通过对于上面在徏立烦引和全文索的基本概念的介l希望能让你对Lucene建立一定的了解?/p>
3.2. 全文索需求的实现 索引建立部分的代码:
IndexWriter iwriter=getWriter(indexFilePath); Document doc=new Document(); doc.add(Field.Keyword("name","jerry")); doc.add(Field.Text("sender","bluedavy@gmail.com")); doc.add(Field.Text("receiver","google@gmail.com")); doc.add(Field.Text("title","用于索引的标?)); doc.add(Field.UnIndexed("content","不徏立烦引的内容")); Document doc2=new Document(); doc2.add(Field.Keyword("name","jerry.lin")); doc2.add(Field.Text("sender","bluedavy@hotmail.com")); doc2.add(Field.Text("receiver","msn@hotmail.com")); doc2.add(Field.Text("title","用于索引的第二个标题")); doc2.add(Field.Text("content","建立索引的内?)); iwriter.addDocument(doc); iwriter.addDocument(doc2); iwriter.optimize(); iwriter.close(); } 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); }
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")); }
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")); }
Query mquery=new WildcardQuery(new Term("sender","bluedavy*")); TermQuery tquery=new TermQuery(new Term("name","jerry")); 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); for (int i = 0; i < hits.length(); i++) { System.out.println(hits.doc(i).get("name")); }
怿大家通过上面的说明能知道Lucene的一个基本的使用ҎQ在全文索时大家先采用语义时的搜索,先搜索出有意义的内容Q之后再q行模糊之类的搜索,^_^Q这个还是需要根据搜索的需求才能定了,Luceneq提供了很多其他更好用的ҎQ这个就{待大家在用的q程中自己去q一步的摸烦了,比如对于Lucene本n提供的Query的更熟练的掌握,对于Filter、Sorter的用,自己扩展实现AnalyzerQ自己实现Query{等Q甚臛_以去了解一些关于搜索引擎的技?切词、烦引排?etc){等?br />
]]>
]]>
private void createIndex(String indexFilePath) throws Exception{
3.2.1. 对于某字D늚关键字的模糊查询
Query query=new WildcardQuery(new Term("sender","*davy*"));
3.2.2. 对于某字D늚关键字的语义查询
Query query=QueryParser.parse("索引","title",analyzer);
3.2.3. 对于多字D늚关键字的查询
Query query=MultiFieldQueryParser.parse("索引",new String[]{"title","content"},analyzer);
3.2.4. 复合查询(多种查询条g的综合查?
Query query=MultiFieldQueryParser.parse("索引",new String[]{"title","content"},analyzer);
? ȝ
]]>
在indexingq程?要把需要indexing的text分析处理一? l过处理和切?然后建立index. 而不通的Analyzer有不同的分析规则, 因此在程序中使用Lucene?选择正确的Analyzer是很重要?
1.Using Analyzers
在用Analyzer以前 先来看看textl过Analyzer分析后的效果?
Listing 4.1 Visualizing analyzer effects
Analyzing "The quick brown fox jumped over the lazy dogs"
WhitespaceAnalyzer:
[The] [quick] [brown] [fox] [jumped] [over] [the] [lazy] [dogs]
SimpleAnalyzer:
[the] [quick] [brown] [fox] [jumped] [over] [the] [lazy] [dogs]
StopAnalyzer:
[quick] [brown] [fox] [jumped] [over] [lazy] [dogs]
StandardAnalyzer:
[quick] [brown] [fox] [jumped] [over] [lazy] [dogs]
Analyzing "XY&Z Corporation - xyz@example.com"
WhitespaceAnalyzer:
[XY&Z] [Corporation] [-] [xyz@example.com]
SimpleAnalyzer:
[xy] [z] [corporation] [xyz] [example] [com]
StopAnalyzer:
[xy] [z] [corporation] [xyz] [example] [com]
StandardAnalyzer:
[xy&z] [corporation] [xyz@example.com]
上面是在下面我们要提到的一个例子的q行l果. 可以看出不同的Analyzer 是如何来分析text?在分析The quick brown fox jumped over the lazy dogs ? WhitespaceAnalyzer?SimpleAnalyzer只是单的把词分开,建立Term可以了;而另外两个Analyzer则去掉了stop word. 而在分析XY&Z Corporation - xyz@example.com 的时?不同的Analyzer 对待 & ?- 的方式也是不一L . 现在对Analysis有个感性的了解,下面来看看不同处理阶D늚分析q程.
I. Indexing Analysis
q记得在ch2 indexing ?讲到 ,在徏立index?使用IndexWriter 在构造IndexWriter?要用到Analyser.如下所C?
Analyzer analyzer = new StandardAnalyzer();
IndexWriter writer = new IndexWriter(directory,
analyzer, true);然后可以用writer?document 来indexing?如下
Document doc = new Document();
doc.add(
Field.Text("title", "This is the title"));doc.add(
Field.UnStored("contents", "...document contents..."));writer.addDocument(doc);
使用的是在构造IndexWriter?指定的Analyzer. 如果要给一个文档单独指定一个Analyzer 可以用下面的一个方?
writer.addDocument(doc,analyzer);
II.QueryParser Analysis
Analysis 是term搜烦的关?要确保经qAnalyzer分析后的term和被索引的一?q样才可以得到搜索结?在用QueryParser parse 用户输入的搜索表辑ּ时可?指定一个Analyzer 如下所C?
Query query = QueryParser.parse(expression, "contents",
analyzer);通过QueryParser的静态方法实? 如果使用QueryParser实例, 则可以在构造QueryParser时?提供一个Analyzer 如下:
QueryParser parser = new QueryParser("contents",
analyzer);query = parser.parse(expression);
QueryParser
analyzes individual pieces of the expression, not the expression as awhole, which may include operators, parenthesis, and other special expression
syntax to denote range, wildcard, and fuzzy searches.
QueryParser q等的分析所有的text,她ƈ不知道他们是如何每indxed, q时如果当搜索一个被索引为Keyword的filed?可能会遇到问题.
q有一个问题就是在分析一些包含其他元素的text时该如何处理 ,?Html xml 文档, 他们都带有元素标{?而这些标{一般是不烦引的.以及如何处理分域(field)索引, ?Html 有Header ?Body?如何分开搜烦 q个问题Analyzer现在也不能解决的, 因ؓ在每ơAnalyzer都处理单个域. 在后面我们在q一步讨问题.
2. Analyzing the Analyzer
要详l了解Lucene分析文本的过E就要知道Analyzer是如何工作的,下面来看看Analyzer是怎么工作的吧. Analyzer是各个XXXAnalyzer的基c?,该类出奇的简?比我惌的要单多? 只要一个方?tokenStream(String fieldName, Reader reader); fieldName 参数Ҏ些Analyzer实现是没有作用的,如SimpleAnalyzer, 该类的代码如?
public final class SimpleAnalyzer extends Analyzer {
public TokenStream tokenStream(String fieldName, Reader reader) {
return new LowerCaseTokenizer(reader);
}
}
可以看到该类也是出奇的简? 只用CLowerCaseTokenizer; 但LowerCaseTokenizer是干什么的? 看看名字可以猜个差不多?,该类把Text 中非字母(nonletters)的字W去?q把所有Text转换为小?
而返回的
TokenStream 是一?enumerator-like class ,通过她可以得到连l的 Tokens,当到达末时候返回null.
1. 实现一个简单的search feature
在本章中只限于讨论简单Lucene 搜烦API, 有下面几个相关的c?
Lucene 基本搜烦API:
c?/p> | 功能 |
IndexSearcher | 搜烦一个index的入?所有的searches都是通过IndexSearcher 实例的几个重载的Ҏ实现? |
Query (and subclasses) | 各个子类装了特定搜索类型的逻辑(logic),Query实例传递给IndexSearcher的searchҎ. |
QueryParser | 处理一个可ȝ表达?转换Z个具体的Query实例. |
Hits | 包含了搜索的l果.有IndexSearcher的search函数q回. |
下面我们来看几个书中的例?
LiaTestCase.java 一个承自TestCase q且扩展了TestCase的类, 下面的几个例子都l承自该c?
01 package lia.common;
02
03 import junit.framework.TestCase;
04 import org.apache.lucene.store.FSDirectory;
05 import org.apache.lucene.store.Directory;
06 import org.apache.lucene.search.Hits;
07 import org.apache.lucene.document.Document;
08
09 import java.io.IOException;
10 import java.util.Date;
11 import java.text.ParseException;
12 import java.text.SimpleDateFormat;
13
14 /**
15 * LIA base class for test cases.
16 */
17 public abstract class LiaTestCase extends TestCase {
18 private String indexDir = System.getProperty("index.dir"); // 试 index 已经建立好了
19 protected Directory directory;
20
21 protected void setUp() throws Exception {
22 directory = FSDirectory.getDirectory(indexDir, false);
23 }
24
25 protected void tearDown() throws Exception {
26 directory.close();
27 }
28
29 /**
30 * For troubleshooting Z 解决问题的方?/font>
31 */
32 protected final void dumpHits(Hits hits) throws IOException {
33 if (hits.length() == 0) {
34 System.out.println("No hits");
35 }
36
37 for (int i=0; i < hits.length(); i++) {
38 Document doc = hits.doc(i);
39 System.out.println(hits.score(i) + ":" + doc.get("title"));
40 }
41 }
42
43 protected final void assertHitsIncludeTitle(
44 Hits hits, String title)
45 throws IOException {
46 for (int i=0; i < hits.length(); i++) {
47 Document doc = hits.doc(i);
48 if (title.equals(doc.get("title"))) {
49 assertTrue(true);
50 return;
51 }
52 }
53
54 fail("title '" + title + "' not found");
55 }
56
57 protected final Date parseDate(String s) throws ParseException {
58 return new SimpleDateFormat("yyyy-MM-dd").parse(s);
59 }
60 }
I.搜烦一个特定的Term 和利用QueryParser 解析用户输入的表辑ּ
要利用一个特定的term搜烦,使用QueryTerm可以了,单个term 其适合Keyword搜烦. 解析用户输入的表辑ּ可以更适合用户的用方?搜烦表达式的解析有QueryParser来完?如果表达式解析错?会有异常抛出, 可以取得怿的错误信?以便l用户适当的提C?在解析表辑ּ?q需要一个Analyzer 来分析用L输入, q根据不同的Analyzer来生产相应的Term然后构成Query实例.
下面看个例子?BasicSearchingTest.java
01 package lia.searching;
02
03 import lia.common.LiaTestCase;
04 import org.apache.lucene.analysis.SimpleAnalyzer;
05 import org.apache.lucene.document.Document;
06 import org.apache.lucene.index.Term;
07 import org.apache.lucene.queryParser.QueryParser;
08 import org.apache.lucene.search.Hits;
09 import org.apache.lucene.search.IndexSearcher;
10 import org.apache.lucene.search.Query;
11 import org.apache.lucene.search.TermQuery;
12
13 public class BasicSearchingTest extends LiaTestCase {
14
15 public void testTerm() throws Exception {
16 IndexSearcher searcher = new IndexSearcher(directory);
17 Term t = new Term("subject", "ant"); // 构造一个Term
18 Query query = new TermQuery(t);
19 Hits hits = searcher.search(query); // 搜烦
20 assertEquals("JDwA", 1, hits.length()); //试l果
21
22 t = new Term("subject", "junit");
23 hits = searcher.search(new TermQuery(t));
24 assertEquals(2, hits.length());
25
26 searcher.close();
27 }
28
29 public void testKeyword() throws Exception { // 试关键字搜?/font>
30 IndexSearcher searcher = new IndexSearcher(directory);
31 Term t = new Term("isbn", "1930110995"); // 关键?term
32 Query query = new TermQuery(t);
33 Hits hits = searcher.search(query);
34 assertEquals("JUnit in Action", 1, hits.length());
35 }
36
37 public void testQueryParser() throws Exception { // 试 QueryParser.
38 IndexSearcher searcher = new IndexSearcher(directory);
39
40 Query query = QueryParser.parse("+JUNIT +ANT -MOCK",
41 "contents",
42 new SimpleAnalyzer()); // 通过解析搜烦表达?q回一个Query实例
43 Hits hits = searcher.search(query);
44 assertEquals(1, hits.length());
45 Document d = hits.doc(0);
46 assertEquals("Java Development with Ant", d.get("title"));
47
48 query = QueryParser.parse("mock OR junit",
49 "contents",
50 new SimpleAnalyzer()); // 通过解析搜烦表达?q回一个Query实例
51 hits = searcher.search(query);
52 assertEquals("JDwA and JIA", 2, hits.length());
53 }
54 }
1,indexing的处理过E?
首先要把indexing的数据{换ؓtext,因ؓLucene只能索引text,然后由Analysis来过虑text,把一些ch1中提到的所谓的stop words qo? 然后建立index.建立的index?font face="NewBaskervilleITCbyBT-Italic" size="3">inverted index 也就是所谓的倒排索引.
2,基本的ingex操作
基本的操?包括 :d 删除 更新.
I . d
下面我们看个例子代码 BaseIndexingTestCase.class
01 package lia.indexing; Heterogeneous Documents
|
q是一个测试超c?可以被其他的试用例l承 来测试不同的功能.上面带有详细的注?
在添加Field? 会遇到同义词的情?d同义词由两种方式:
a.创徏一个同义词词组,循环d到Single Strng的不同Field?
b.把同义词dC个Base word的field?如下:
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]));
}
q样 ?/font>Lucene内部把每个词都添加的一个名为word的Field?在搜索时 你可以用Q何一个给定的词语.
我们主要来看?q个 indexing and searching 例子 然后了解一些基本概?
package lia.meetlucene;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import java.io.File;
import java.io.IOException;
import java.io.FileReader;
import java.util.Date;
/**
* This code was originally written for
* Erik's Lucene intro java.net article
*/
public class Indexer {
public static void main(String[] args) throws Exception {
if (args.length != 2) {
throw new Exception("Usage: java " + Indexer.class.getName()
+ " <index dir> <data dir>");
}
File indexDir = new File(args[0]); // 在该目录中创建Lucene Incex
File dataDir = new File(args[1]); // 该目录中存放备烦引的文g
long start = new Date().getTime();
int numIndexed = index(indexDir, dataDir);
long end = new Date().getTime();
System.out.println("Indexing " + numIndexed + " files took "
+ (end - start) + " milliseconds");
}
public static int index(File indexDir, File dataDir)
throws IOException {
if (!dataDir.exists() || !dataDir.isDirectory()) {
throw new IOException(dataDir
+ " does not exist or is not a directory");
}
IndexWriter writer = new IndexWriter(indexDir,
new StandardAnalyzer(), true); //(1)创徏 Lucene Index
writer.setUseCompoundFile(false);
indexDirectory(writer, dataDir);
int numIndexed = writer.docCount();
writer.optimize();
writer.close(); // close index
return numIndexed;
}
private static void indexDirectory(IndexWriter writer, File dir)
throws IOException {
File[] files = dir.listFiles();
for (int i = 0; i < files.length; i++) {
File f = files[i];
if (f.isDirectory()) {
indexDirectory(writer, f); //(2) recurse
} else if (f.getName().endsWith(".txt")) {
indexFile(writer, f);
}
}
}
private static void indexFile(IndexWriter writer, File f)
throws IOException {
if (f.isHidden() || !f.exists() || !f.canRead()) {
return;
}
System.out.println("Indexing " + f.getCanonicalPath());
Document doc = new Document();
doc.add(Field.Text("contents", new FileReader(f))); // (3) index file content
doc.add(Field.Keyword("filename", f.getCanonicalPath())); // (4) index file name
writer.addDocument(doc); //(5) add document in Lucene index
}
}
上面的Indexer 使用了几?Lucene的API, 来indexing 一个目录下面的文g. q行时?需要两个参?, 一个保存index的目录和要烦引的文g目录.
在上面的cM,需要下面的一些Lucene classes 来执?indexing 处理:
?
IndexWriter?
Directory?
Analyzer?
Document?
FieldIndexWriter 是indexing 处理时用到的中心lg,该类create 新index q且ddocuments 到已l存在的index, BTW,在Lucene中还有别的方法来更新index.
Directory: 用来存放index文g的文件目?该类是个抽象c?用几个子cd以?上面使用了File来代表文件\?在Lucene中用两个主要的Directory子类,一个FSDirectory,一?RAMDirectory,前者是把index保存到硬盘中?后者是保存在内存中?在内存中处理数度当然q应的快一?了但只适合于小文g.
Analyzer: 在文件备索引以前要先通过Analyzer分析,L一些对search无用的词?如英语中 的小?in at a {等,在Lucene中被UCؓstop words 的词),q可以处理大写的问?是大写相关?q是不相?,使用Lucene时?选择Analyzer是关?
Document: 代表一些Fields的集?可以惌Z些数据的集合.
Field: 在index中的每一个Document中都包含一?命名的Fields 用Field来构? 每一个field都是的搜索是W合要求和不W合要求的index中的一些数?Lucene提供了四U不同的Field,
1,Keyword 不分?只烦引和保存,象一些特D信?不可以分割的 ?电话L |站 Email {?
2,UnIndexed 既不索引也不分析,只是把g存在index?该类型适合用来昄搜烦l果的field,但是你从来不搜烦该显C的数据,如URL
3,UnStored UnIndexed的对立面, 分析和烦引但是不保存在index?适合大型数据 只搜索但是不昄原始数据.
4,Test 分析且烦?如果索引数据是String则也保存在index? 如果是Reader则不保存.