全文检索的概念:从大量的信息中快速、准确地查找出要的信息;只处理文本,不处理语义;全面、快速、准确是衡量全文检索系统的关键指标。
全文检索的应用场景:站内搜索;垂直搜索。
全文检索和数据库搜索的区别:
中文姓名匹配:([\u4E00-\u9FA5]{2,4})</a>[ ]+(\u5148\u751F|\u5973\u58EB)
lucene是实现了全文检索的一个框架。
1、Directory.class 描述索引库的一个类,相当于数据库。
2、Document 描述索引库中的数据格式,相当于数据库中的表。
3、Document(List<Field>)
4、Field里存放的是一个字符串形式的键值对。
5、对索引库的索引的操作实际上是对Document的
所需jar包
搭建lucene的开发环境,要准备lucene的jar包,要加入的jar包至少有
-
lucene-core-3.1.0.jar (核心包)
-
lucene-analyzers-3.1.0.jar (分词器)
-
lucene-highlighter-3.1.0.jar (高亮器)
-
lucene-memory-3.1.0.jar (高亮器)
建立索引和搜索代码示例
public class ArticleIndex {
/**
* 1、创建一个对象,并设置属性
* 2、创建IndexWriter
* 3、利用Indexwriter吧该对象放入到索引库中
* 4、关闭IndexWriter
* @throws IOException
*/
//可以执行两次建立索引成功,说明javabean中的id不是确定索引的唯一标示,目录ID由lucene内部生成。
@Test
public void testCreatIndex() throws IOException{
Article article = new Article();
article.setId(1l);
article.setTitle("百度搜索是怎么做的呢?");
article.setContent("百度一下,你就发现,百度还不错呦,信不信由你,反正我信了!");
Directory directory = FSDirectory.open(new File("./newpath"));
Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_30);
IndexWriter indexWriter = new IndexWriter(directory, analyzer, MaxFieldLength.UNLIMITED);
//把article转化为document
Document document = new Document();
//store表示是否将内容放到索引库中
//Index表示是否将关键字放到索引库中
Field field1 = new Field("id", article.getId().toString(), Store.YES, Index.NOT_ANALYZED);
Field field2 = new Field("title", article.getTitle(), Store.YES, Index.ANALYZED);
Field field3 = new Field("content", article.getContent(), Store.YES, Index.ANALYZED);
document.add(field1);
document.add(field2);
document.add(field3);
indexWriter.addDocument(document);
indexWriter.close();
}
/**
* 搜索代码
* @throws IOException
* @throws ParseException
*/
//搜索时,Analyzer分词器会把输入的关键字都变成小写
@Test
public void testSearchIndex() throws IOException, ParseException{
Directory directory = FSDirectory.open(new File("./newpath"));
//创建IndexSearcher
IndexSearcher indexSearcher = new IndexSearcher(directory);
//创建Query对象
Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_30);
QueryParser queryParser = new QueryParser(Version.LUCENE_30,"title",analyzer);
Query query = queryParser.parse("百度");
//搜索:query表示搜索条件 1表示一条记录
TopDocs topDocs = indexSearcher.search(query, 2);
int totalRecords = topDocs.totalHits;//获取总记录数
System.out.println(totalRecords);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;//获取前n行的目录ID
List<Article> articles = new ArrayList<Article>();
for(ScoreDoc scoreDoc : scoreDocs){
float score = scoreDoc.score;//相关度得分
int index = scoreDoc.doc;//目录列表ID
Document document = indexSearcher.doc(index);
Article article = new Article();
article.setId(Long.parseLong(document.get("id")));
article.setTitle(document.get("title"));
article.setContent(document.get("content"));
articles.add(article);
}
for(Article article : articles){
System.out.println(article.getContent());
}
}
}
对索引库的操作
1、保持数据库和索引库的同步,在操作数据库是同时更新索引库。
Index:no——不向目录库中存;not_analyzer ——存,不分词;analyzer —— 存并且分词。
Store:yes——会存到内容库中;no——不存到内容库中。
IndexWriter.addDocument(doc);
DocumentUtils.java
在对索引库进行操作时,增、删、改过程要把一个JavaBean封装成Document,而查询的过程是要把一个Document转化成JavaBean。在进行维护的工作中,要反复进行这样的操作,所以我们有必要建立一个工具类来重用代码。
对索引库的删除和更新操作:
/**
* 删除
* 并不是把原来的cfs文件删除掉了,而是在原来的基础上多了一个del文件
*/
@Test
public void testDelete() throws Exception{
IndexWriter indexWriter = new IndexWriter(LuceneUtils.directory,LuceneUtils.analyzer,MaxFieldLength.LIMITED);
/**
* Term
* 关键词对象 把关键词封装在了对象中
*/
Term term = new Term("title","lucene");
indexWriter.deleteDocuments(term);
indexWriter.close();
}
/**
* 更新
* 先删除后增加
*/
@Test
public void testUpdate() throws Exception{
IndexWriter indexWriter = new IndexWriter(LuceneUtils.directory,LuceneUtils.analyzer,MaxFieldLength.LIMITED);
Term term = new Term("title","lucene");
Article article = new Article();
article.setId(1L);
article.setTitle("lucene可以做搜索引擎");
article.setContent("aaaaa");
/**
* Term为删除
* Document为增加
*/
indexWriter.updateDocument(term, DocumentUtils.article2Document(article));
indexWriter.close();
}
因为当一个IndexWriter在进行读索引库操作的时候,lucene会为索引库上锁,以防止其他IndexWriter访问索引库而导致数据不一致,直到IndexWriter关闭为止。结论:同一个索引库只能有一个IndexWriter进行操作。
/**
* 1、当刚创建完一个 indexWriter的时候,那么indexWriter所指向的索引库就被上锁了,这个时候,另外的indexWriter还是indexSearch的操作是无效的
* 2、当indexWriter关闭的时候,释放IO流的资源,释放锁的过程
* 3、索引库的最多的操作是检索,后台维护的操作是比较少的
* @author Think
*
*/
public class IndexWriterTest {
@Test
public void testIndexWriter() throws Exception{
IndexWriter writer = new IndexWriter(LuceneUtils.directory,LuceneUtils.analyzer,MaxFieldLength.LIMITED);
writer.close();
IndexWriter writer2 = new IndexWriter(LuceneUtils.directory,LuceneUtils.analyzer,MaxFieldLength.LIMITED);
}
}
索引库的优化
indexWriter.optimize(); 手动合并文件
indexWriter().setMergeFactor(3); 当文件的个数达到3的时候,会自动合并成一个文件。默认的情况:10
每次建立索引,都会增加一个cfs文件,每次删除,都会增加del文件和cfs文件,如果增加、删除很多次,文件大量增加,这样检索的速度也会下降,所以有必要去优化索引结构,使文件的结构发生改变从而提高效率。
内存索引库和文件索引库
把内存索引库和文件索引库结合提高效率。
//为true时,表示重新创建或者覆盖,为false表示追加。默认为false
IndexWriter ramIndexWriter = new IndexWriter(ramDirectory,LuceneUtils.analyzer,MaxFieldLength.LIMITED);
IndexWriter fileIndexWriter = new IndexWriter(fileDirectory,LuceneUtils.analyzer,true,MaxFieldLength.LIMITED);
/**
* 内存索引库的特点
* 1、查询效率比较快
* 2、数据不是持久化数据
* 文件索引库的特点
* 1、查询效率比较慢
* 2、数据是持久化类的
* 内存索引库和文件索引库的结合
* 百万级别的数据,使用一个索引库效率很低,可以建立多个索引库。
* lucene提供了一些方法可以做很多个索引库出来(在一个项目中),
* 可以对某一个索引库进行检索,还可以针对合并的索引库进行检索
* 方法: fileIndexWriter.addIndexesNoOptimize(ramDirectory);//合并操作
*
* @author Think
*
*/
public class DirectoryTest {
@Test
public void testRamDirectory() throws Exception{
/**
* 创建内存索引库
*/
Directory ramDirectory = new RAMDirectory();
IndexWriter indexWriter = new IndexWriter(ramDirectory,LuceneUtils.analyzer,MaxFieldLength.LIMITED);
Article article = new Article();
article.setId(1L);
article.setTitle("lucene可以做搜索引擎");
article.setContent("baidu,google是很好的搜索引擎");
indexWriter.addDocument(DocumentUtils.article2Document(article));
indexWriter.close();
this.showData(ramDirectory);
}
private void showData(Directory directory) throws Exception{
IndexSearcher indexSearcher = new IndexSearcher(directory);
QueryParser queryParser = new MultiFieldQueryParser(Version.LUCENE_30,new String[]{"title","content"},LuceneUtils.analyzer);
Query query = queryParser.parse("lucene");
TopDocs topDocs = indexSearcher.search(query, 20);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
List<Article> articles = new ArrayList<Article>();
for(int i=0;i<scoreDocs.length;i++){
Document document = indexSearcher.doc(scoreDocs[i].doc);
Article article = DocumentUtils.document2Article(document);
articles.add(article);
}
for(Article article:articles){
System.out.println(article.getId());
System.out.println(article.getTitle());
System.out.println(article.getContent());
}
}
/**
* 文件索引库和内存索引库的合并的操作
*/
@Test
public void testFileAndRam() throws Exception{
/**
* 1、创建两个indexWriter
* 一个对应文件索引库
* 一个对应内存索引库
* 2、把文件索引库中的内容复制到内存索引库
* 3、内存索引库和应用程序交互
* 4、内存索引库的内容同步到文件索引库
*/
Directory fileDirectory = FSDirectory.open(new File("./indexDir"));
/**
* 把文件索引库中的内容复制到内存索引库
*/
Directory ramDirectory = new RAMDirectory(fileDirectory);
IndexWriter ramIndexWriter = new IndexWriter(ramDirectory,LuceneUtils.analyzer,MaxFieldLength.LIMITED);
IndexWriter fileIndexWriter = new IndexWriter(fileDirectory,LuceneUtils.analyzer,true,MaxFieldLength.LIMITED);
/**
* 应用程序和内存索引库交互
*/
Article article = new Article();
article.setId(1L);
article.setTitle("lucene可以做搜索引擎");
article.setContent("baidu,google是很好的搜索引擎");
ramIndexWriter.addDocument(DocumentUtils.article2Document(article));
ramIndexWriter.close();
/**
* 把内存索引库中的内容同步到文件索引库
*/
fileIndexWriter.addIndexesNoOptimize(ramDirectory);
fileIndexWriter.close();
this.showData(fileDirectory);
}
}
分词器Analyzer
英文分词器把关键词由大写变成小写。
在向索引库和目录库中存数据时都用到分词器。
一定要使用UTF-8编码
/**
* 分词器
* @author Think
*
*/
public class AnalyzerTest {
@Test
public void testAnalyzer_En() throws Exception{
Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_30);
String text = "Creates a searcher searching the index in the named directory";
/**
* 英文分词器
* creates
searcher
searching
index
named
directory
*/
/**英文分词器的执行过程
* 1、切分关键词
* 2、去掉停用词
* 3、把大写变成小写
*/
this.testAnalyzer(analyzer, text);
}
/**
* lucene内置的两个中文分词器,都不好用
* 单字分词
* @throws Exception
*/
@Test
public void testCH_1() throws Exception{
Analyzer analyzer = new ChineseAnalyzer();
String text = "这个论坛很不错";
this.testAnalyzer(analyzer, text);
}
/**
* 二分法分词
* @throws Exception
*/
@Test
public void testCH_2() throws Exception{
Analyzer analyzer = new CJKAnalyzer(Version.LUCENE_30);
String text = "这个论坛很不错";
this.testAnalyzer(analyzer, text);
}
/**
* IK分词器,中文分词器,支持自定义词典
* @throws Exception
*/
@Test
public void testCh_3() throws Exception{
Analyzer analyzer = new IKAnalyzer();
String text = "lucene可以做搜索引擎";
this.testAnalyzer(analyzer, text);
}
/**
* 测试分词器代码,输出分词结果
* @param analyzer 分词器对象
* @param text 检索的文本,字符串形式
* @throws Exception
*/
private void testAnalyzer(Analyzer analyzer,String text)throws Exception{
TokenStream tokenStream = analyzer.tokenStream("content", new StringReader(text));
tokenStream.addAttribute(TermAttribute.class);
while(tokenStream.incrementToken()){
TermAttribute termAttribute = tokenStream.getAttribute(TermAttribute.class);
System.out.println(termAttribute.term());
}
}
}
高亮器
测试时,建立索引库和查询需要使用同一个分词器。
/**
* 1、使关键词高亮
* 2、控制摘要的大小
*
* @author Think
*
*/
public class HighlighterTest {
@Test
public void testSearchIndex() throws Exception {
IndexSearcher indexSearcher = new IndexSearcher(LuceneUtils.directory);
QueryParser queryParser = new MultiFieldQueryParser(Version.LUCENE_30,
new String[] { "title", "content" }, LuceneUtils.analyzer);
Query query = queryParser.parse("百度");
/**
* 设置高亮器
* 规定要高亮的文本的前缀和后缀 只适合于网页
* <font color='red'>方立勋</font>
*/
Formatter formatter = new SimpleHTMLFormatter("<font color='red'>","</font>");
Scorer scorer = new QueryScorer(query);
Highlighter highlighter = new Highlighter(formatter,scorer);
/**
* 控制摘要的大小
*/
Fragmenter fragmenter = new SimpleFragmenter(20);
highlighter.setTextFragmenter(fragmenter);
TopDocs topDocs = indexSearcher.search(query, 10);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
List<Article> articles = new ArrayList<Article>();
for (int i = 0; i < scoreDocs.length; i++) {
Document document = indexSearcher.doc(scoreDocs[i].doc);
/**
* 使用高亮器:参数
* LuceneUtils.analyzer
* 用分词器把高亮部分的词分出来
* field
* 针对那个字段进行高亮
* document.get("title")
* 获取要高亮的字段
*/
String titleText = highlighter.getBestFragment(LuceneUtils.analyzer, "title", document.get("title"));
String contentText = highlighter.getBestFragment(LuceneUtils.analyzer, "content", document.get("content"));
if(titleText!=null){
document.getField("title").setValue(titleText);
}
if(contentText!=null){
document.getField("content").setValue(contentText);
}
Article article = DocumentUtils.document2Article(document);
articles.add(article);
}
for (Article article : articles) {
System.out.println(article.getId());
System.out.println(article.getTitle());
System.out.println(article.getContent());
}
}
}
检索结果分页
public class DispageTest {
public void testSearchIndex(int firstResult,int maxResult) throws Exception{
IndexSearcher indexSearcher = new IndexSearcher(LuceneUtils.directory);
QueryParser queryParser = new MultiFieldQueryParser(Version.LUCENE_30,new String[]{"title","content"},LuceneUtils.analyzer);
Query query = queryParser.parse("lucene");
TopDocs topDocs = indexSearcher.search(query, 25);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
List<Article> articles = new ArrayList<Article>();
//防止出现角标越界
int length = Math.min(topDocs.totalHits, firstResult+maxResult);
/**
* 进行分页
*/
for(int i=firstResult;i<length;i++){
Document document = indexSearcher.doc(scoreDocs[i].doc);
Article article = DocumentUtils.document2Article(document);
articles.add(article);
}
for(Article article:articles){
System.out.println(article.getId());
System.out.println(article.getTitle());
System.out.println(article.getContent());
}
}
@Test
public void testDispage() throws Exception{
this.testSearchIndex(20, 10);
}
}
查询
通配符查询:百度,左匹配
/**
* 查询方式
* 关键词查询
* 查询所有的文档
* 范围查询
* 通配符查询 重点
* 短语查询
* boolean查询 重点
*
* @author Think
*
*/
public class QueryTest {
/**
* 1、关键词查询就是把一个关键词封装在了一个对象中,根据该关键词进行查询
* 2、因为没有分词器,所以区分大小写
* @throws Exception
*/
@Test
public void testTermQuery() throws Exception {
Term term = new Term("title","lucene");
Query query = new TermQuery(term);
this.testSearchIndex(query);
}
private void testSearchIndex(Query query) throws Exception {
IndexSearcher indexSearcher = new IndexSearcher(LuceneUtils.directory);
TopDocs topDocs = indexSearcher.search(query, 28);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
List<Article> articles = new ArrayList<Article>();
for (int i = 0; i < scoreDocs.length; i++) {
Document document = indexSearcher.doc(scoreDocs[i].doc);
Article article = DocumentUtils.document2Article(document);
articles.add(article);
}
for (Article article : articles) {
System.out.println(article.getId());
System.out.println(article.getTitle());
System.out.println(article.getContent());
}
}
@Test
public void testQueryAllDocs() throws Exception{
Query query = new MatchAllDocsQuery();
this.testSearchIndex(query);
}
/**
* * 代表任意多个任意字符
* ? 代表任意一个字符
* @throws Exception
*/
@Test
public void testQueryWildCard() throws Exception{
Term term = new Term("title","l*?");
Query query = new WildcardQuery(term);
this.testSearchIndex(query);
}
/**
* 短语查询
* 1、所有的短语查询针对的是相同的字段
* 2、两个以上的短语查询,要指出该关键词分词后的位置
*/
@Test
public void testQueryPharse() throws Exception{
Term term = new Term("title","lucene");
Term term2 = new Term("title","搜索");
PhraseQuery phraseQuery = new PhraseQuery();
phraseQuery.add(term,0);
phraseQuery.add(term2,4);
this.testSearchIndex(phraseQuery);
}
/**
* boolean查询
* Occur.MUST 必须满足该条件
* Occur.MUST_NOT 必须不能出现
* Occur.SHOULD 可以有可以没有 or
*/
@Test
public void testBooleanQuery() throws Exception{
Term term = new Term("title","北京");
Query query = new WildcardQuery(term);
Term term2 = new Term("title","美女");
Query query2 = new WildcardQuery(term2);
Term term3 = new Term("title","北京美女");
Query query3 = new WildcardQuery(term3);
BooleanQuery booleanQuery = new BooleanQuery();
booleanQuery.add(query, Occur.SHOULD);
booleanQuery.add(query2,Occur.SHOULD);
booleanQuery.add(query3,Occur.SHOULD);
this.testSearchIndex(booleanQuery);
}
/**
* 范围查询
*/
@Test
public void testQueryRange() throws Exception{
Query query = NumericRangeQuery.newLongRange("id", 5L, 15L, true, true);
this.testSearchIndex(query);
}
}
排序,根据相关度得分
/**
* 1、相同的关键词,相同的结构
* 得分一样
* 2、相同的结构,不同的关键词
* 得分不一样(lucene和搜索的得分是不一样的, 一般情况下,中文比英文的得分高)
* 3、不同的结构,相同的关键词
* 关键词出现的次数越多,得分越高
* 4、竞价排名,在往索引库中放时,通过设置ducument的boost数值大小,相关度得分会乘以这个数值,从而提高相关度得分。
* @author Think
*
*/
public class SortTest {
@Test
public void testSearchIndex() throws Exception{
IndexSearcher indexSearcher = new IndexSearcher(LuceneUtils.directory);
QueryParser queryParser = new MultiFieldQueryParser(Version.LUCENE_30,new String[]{"title","content"},LuceneUtils.analyzer);
Query query = queryParser.parse("lucene");
TopDocs topDocs = indexSearcher.search(query, 28);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
List<Article> articles = new ArrayList<Article>();
for(int i=0;i<scoreDocs.length;i++){
System.out.println(scoreDocs[i].score);
Document document = indexSearcher.doc(scoreDocs[i].doc);
Article article = DocumentUtils.document2Article(document);
articles.add(article);
}
for(Article article:articles){
System.out.println(article.getId());
System.out.println(article.getTitle());
System.out.println(article.getContent());
}
}
}
bbs项目异常,经检查代码没有问题,工 作空间设置成UTF-8解决。