LUCENE

一、什么是Lucene
二、Lucece全文检索和数据库检索的区别
三、Lucene的原理
四、Lucene开发原理(索引库与数据库同步)
五、开发步骤
六、Lucene优化

正文

师兄推荐我学习Lucene这门技术,用了两天时间,大概整理了一下相关知识点。

回到顶部
一、什么是Lucene
Lucene即全文检索。全文检索是计算机程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置。当用户查询时根据建立的索引查找,类似于通过字典的检索字表查字的过程。

回到顶部
二、Lucece全文检索和数据库检索的区别

回到顶部
三、Lucene的原理
(1)索引库操作原理

注意:这里面有两个关键的对象:分别是IndexWriter和IndexSearcher。

执行增删改操作用的是IndexWriter对象,执行查询操作用的是IndexSearcher对象。

(2)索引库存放数据原理

注意:Lucece库在4.0之前和4.0之后的API发生了很大变化,这个图中的Index到后面的API已经不建议再用了。后面有相应的替代方法。

比如:原来的写法:

String id = NumericUtils.intToPrefixCoded(1);

new Field(“id”,id,Store.YES,Index.NOT_ANALYZED);

new Field(“title”, “我是陈驰”,Store.YES, Index.NOT_ANALYZED);

new Field("“content”,
“国防科学技术大学(National University of Defense Technology),是中华人民共和国中央军事委员会直属的一所涵盖理学、工学、军事学、管理学、经济学、哲学、文学、教育学、法学、历史学等十大学科门类的综合性全国重点大学”,

Store.YES,

Index.ANALYZED);

后来的写法:

new IntField(“id”, 1, Store.YES);

new StringField(“title”, “我是陈驰”, Store.YES);

new TextField(
“content”,
“国防科学技术大学(National University of Defense Technology),是中华人民共和国中央军事委员会直属的一所涵盖理学、工学、军事学、管理学、经济学、哲学、文学、教育学、法学、历史学等十大学科门类的综合性全国重点大学”,
Store.YES);

回到顶部
四、Lucene开发原理(索引库与数据库同步)

数据库与索引库中存放相同的数据,可以使用数据库中存放的ID用来表示和区分同一条数据。

–数据库中用来存放数据

–索引库中用来查询、检索

检索库支持查询检索多种方式,

特点:

1:由于是索引查询(通过索引查询数据),检索速度快,搜索的结果更加准确

2:生成文本摘要,摘要截取搜索的文字出现最多的地方

3:显示查询的文字高亮

4:分词查询等

注意:添加了索引库,并不意味着不往数据库中存放数据,数据库的所有操作仍和以前一样。只不过现在多维护一个索引库,在查询的时候可以提高效率。

所有的数据(对象),我们都要存到数据库中。对于要进行搜索的数据,还要存到索引库中,以供搜索。一份数据同时存到数据库与索引库中(格式不同),就要想办法保证他们的状态一致。否则,就会影响搜索结果。

对于上一段提出的问题:保证索引库中与数据库中的数据一致(只要针对要进行搜索的数据)。我们采用的方法是,在数据库中做了相应的操作后,在索引库中也做相应的操作。具体的索引库操作,是通过调用相应的IndexDao方法完成的。IndexDao类似于数据库层的Dao。

索引库中存放的数据要转换成Document对象(每条数据就是一个Document对象),并向Document对象中存放Field对象(每条数据对应的字段,例如主键ID、所属单位、图纸类别、文件名称、备注等),将每个字段中的值都存放到Field对象中。

回到顶部
五、开发步骤
(1)需要的jar包

(2)需要的配置文件(注:这里我用的是IKAnalyzer,是第三方的中文分词器,庖丁分词,中文分词,特点:扩展新的词,自定义停用词。只有用该分词器才用到以下配置文件。)

l IKAnalyzer.cfg.xml

复制代码

<?xml version="1.0" encoding="UTF-8"?> IK Analyzer 扩展配置 mydict.dic;
 <!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords">ext_stopword.dic</entry> 
复制代码 l ext.dic(扩展词库)

l stopword.dic(停用词库)

(3)一个简单的例子(用的标准分词器StandardAnalyzer,所以暂时没有用到上面的配置文件,标准分词器是汉字一个一个分的,英语还是按单词)

复制代码
import java.io.File;
import java.io.IOException;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.IntField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.Field.Index;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.junit.Test;

/**

  • 使用lucene对数据创建索引
  • @author chenchi

*/
public class TestLucene {

/**
 * 使用IndexWriter对数据创建索引
 * @throws IOException
 */
@Test
public void testCreateIndex() throws IOException {
    // 索引存放的位置...
    Directory d = FSDirectory.open(new File("indexDir/"));

    // 索引写入的配置
    Version matchVersion = Version.LUCENE_CURRENT;// lucene当前匹配的版本
    Analyzer analyzer = new StandardAnalyzer(matchVersion);// 分词器
    IndexWriterConfig conf = new IndexWriterConfig(matchVersion, analyzer);
    // 构建用于操作索引的类
    IndexWriter indexWriter = new IndexWriter(d, conf);

    // 通过IndexWriter来创建索引
    // 索引库里面的数据 要遵守一定的结构(索引结构,document)
    Document doc = new Document();
    /**
     * 1.字段的名称 2.该字段的值 3.字段在数据库中是否存储
     * StringField是一体的
     * TextField是可分的
     */
    IndexableField field = new IntField("id", 1, Store.YES);
    IndexableField title = new StringField("title", "java培训零基础开始 从入门到精通",
            Store.YES);
    IndexableField content = new TextField(
            "content",
            "java培训,中软国际独创实训模式,三免一终身,学java多项保障让您无后顾之忧。中软国际java培训,全日制教学,真实项目实战,名企定制培训,四个月速成java工程师!",
            Store.YES);

    doc.add(field);
    doc.add(title);
    doc.add(content);
    // document里面也有很多字段
    indexWriter.addDocument(doc);

    indexWriter.close();
}

/**
 * 使用IndexSearcher对数据创建索引
 * 
 * @throws IOException
 */
@Test
public void testSearcher() throws IOException {
    // 索引存放的位置...
    Directory d = FSDirectory.open(new File("indexDir/"));

    // 通过indexSearcher去检索索引目录
    IndexReader indexReader = DirectoryReader.open(d);
    IndexSearcher indexSearcher = new IndexSearcher(indexReader);

    // 这是一个搜索条件,根据这个搜索条件我们来进行查找
    // term是根据哪个字段进行检索,以及字段对应值
    //================================================
    //注意:这样是查询不出,只有单字才能查询出来
    Query query = new TermQuery(new Term("content", "培训"));

    // 搜索先搜索索引目录
    // 找到符合条件的前100条数据
    TopDocs topDocs = indexSearcher.search(query, 100);
    System.out.println("总记录数:" + topDocs.totalHits);
    ScoreDoc[] scoreDocs = topDocs.scoreDocs;
    for (ScoreDoc scoreDoc : scoreDocs) {
        //得分采用的是VSM算法
        System.out.println("相关度得分:" + scoreDoc.score);
        //获取查询结果的文档的惟一编号,只有获取惟一编号,才能获取该编号对应的数据
        int doc = scoreDoc.doc;
        //使用编号,获取真正的数据
        Document document = indexSearcher.doc(doc);
        System.out.println(document.get("id"));
        System.out.println(document.get("title"));
        System.out.println(document.get("content"));
    }
}

}
复制代码
(4)实现Lucene的CURD操作

先写一个LuceneUtils类:

复制代码
import java.io.File;
import java.io.IOException;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;

public class LuceneUtils {

public static Directory d = null;
public static IndexWriterConfig conf = null;
public static Version matchVersion = null;
public static Analyzer analyzer = null;

static{
    try {
        d = FSDirectory.open(new File(Constant.FILEURL));
        matchVersion = Version.LUCENE_44;
        //注意:该分词器是单字分词
        analyzer = new StandardAnalyzer(matchVersion);
        
        conf = new IndexWriterConfig(matchVersion, analyzer);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

/**
 * 
 * @return 返回版本信息
 */
public static Version getMatchVersion() {
    return matchVersion;
}

/**
 * 
 * @return 返回分词器
 */
public static Analyzer getAnalyzer() {
    return analyzer;
}

/**
 * 
 * @return 返回用于操作索引的对象
 * @throws IOException
 */
public static IndexWriter getIndexWriter() throws IOException{
    IndexWriter indexWriter = new IndexWriter(d, conf);
    return indexWriter;
}

/**
 * 
 * @return 返回用于读取索引的对象
 * @throws IOException
 */
public static IndexSearcher getIndexSearcher() throws IOException{
    IndexReader r = DirectoryReader.open(d);
    IndexSearcher indexSearcher = new IndexSearcher(r);
    return indexSearcher;
}

}
复制代码
再写一个Constant常量类:

public class Constant {
public static String FILEURL = “E://indexDir/news”;
}
封装一个实体类Article:

复制代码
public class Article {
private int id;
private String title;
private String author;
private String content;
private String link;

public int getId() {
    return id;
}

public void setId(int id) {
    this.id = id;
}

public String getTitle() {
    return title;
}

public void setTitle(String title) {
    this.title = title;
}

public String getAuthor() {
    return author;
}

public void setAuthor(String author) {
    this.author = author;
}

public String getContent() {
    return content;
}

public void setContent(String content) {
    this.content = content;
}

public String getLink() {
    return link;
}

public void setLink(String link) {
    this.link = link;
}

}
复制代码
由于需要实体类和document对象之间彼此转换,所以再写一个转换的工具类ArticleUtils:

复制代码
import org.apache.lucene.document.Document;
import org.apache.lucene.document.TextField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.IntField;

import com.tarena.bean.Article;

public class ArticleUtils {
/**
* 将artitle转换成document
* @param article
* @return
*/
public static Document articleToDocument(Article article) {
Document document = new Document();
IntField idField = new IntField(“id”, article.getId(), Store.YES);
//StringField不进行分词(整体算一个)
StringField authoField = new StringField(“author”, article.getAuthor(), Store.YES);
StringField linkField = new StringField(“link”, article.getLink(), Store.YES);
//TextField进行分词
TextField titleField = new TextField(“title”, article.getTitle(), Store.YES);
//==============================================
//注意:这里可以添加权重值,默认是1f,添加之后检索的时候排名就会靠前
titleField.setBoost(4f);
TextField contentField = new TextField(“content”, article.getContent(), Store.YES);

    document.add(idField);
    document.add(authoField);
    document.add(linkField);
    document.add(titleField);
    document.add(contentField);
    return document;
}

/**
 * 将document转换成article
 * @param document
 * @return
 */
public static Article documentToArticle(Document document){
    Article article = new Article();
    article.setId(Integer.parseInt(document.get("id")));
    article.setAuthor(document.get("author"));
    article.setLink(document.get("link"));
    article.setTitle(document.get("title"));
    article.setContent(document.get("content"));
    
    return article;
}

}
复制代码
以上准备工作都做完,接下来我们就可以写一个LuceneDao,进行增删改查(分页)操作。

复制代码
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.security.auth.login.Configuration;

import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.queryparser.flexible.core.util.StringUtils;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;

import com.tarena.bean.Article;
import com.tarena.utils.ArticleUtils;
import com.tarena.utils.LuceneUtils;

/**

  • 相关度得分:
  • 内容一样,搜索关键字一样,得分也是一样的
  • 我们可以人工干预得分,就是通过ArticleUtils类中的titleField.setBoost(4f);这样
  • 得分跟搜索关键字在文章当中出现的频率、次数、位置有关系
  • @author chenchi

/
public class LuceneDao {
/
*
* 增删改索引都是通过IndexWriter对象来完成
*/
public void addIndex(Article article) {
try {
IndexWriter indexWriter = LuceneUtils.getIndexWriter();
indexWriter.addDocument(ArticleUtils.articleToDocument(article));
indexWriter.close();
} catch (IOException e) {
e.printStackTrace();
}

}

/**
 * 删除索引,根据字段对应的值删除
 * @param fieldName
 * @param fieldValue
 * @throws IOException 
 */
public void deleteIndex(String fieldName, String fieldValue) throws IOException {
    IndexWriter indexWriter = LuceneUtils.getIndexWriter();
    //使用词条删除
    Term term = new Term(fieldName, fieldValue);
    indexWriter.deleteDocuments(term);
    indexWriter.close();
}

/**
 * 先删除符合条件的记录,再创建一个新的纪录
 * @param fieldName
 * @param fieldValue
 * @param article
 * @throws IOException
 */
public void updateIndex(String fieldName, String fieldValue, Article article) throws IOException {
    IndexWriter indexWriter = LuceneUtils.getIndexWriter();
    
    Term term = new Term(fieldName, fieldValue);
    Document doc = ArticleUtils.articleToDocument(article);
    /**
     * 1.设置更新的条件
     * 2.设置更新的内容和对象
     */
    indexWriter.updateDocument(term, doc);
    indexWriter.close();
}

/**
 * 查询是通过IndexSearch提供的(分页)
 */
public List<Article> findIndex(String keywords, int start, int count) {
    try {
        IndexSearcher indexSearcher = LuceneUtils.getIndexSearcher();
        
        //===========================================================
        //这里是第二种query方式,不是termQuery
        QueryParser queryParser = new MultiFieldQueryParser(
                LuceneUtils.getMatchVersion(), new String[] { "title",
                        "content" }, LuceneUtils.getAnalyzer());
        Query query = queryParser.parse(keywords);
        TopDocs topDocs = indexSearcher.search(query, 100);
        System.out.println("总记录数:" + topDocs.totalHits);
        //表示返回的结果集
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        List<Article> list = new ArrayList<Article>();
        
        int min = Math.min(scoreDocs.length, start + count);
        for (int i = start; i < min; i++) {
            System.out.println("相关度得分:"+scoreDocs[i].score);
            //获取查询结果的文档的惟一编号,只有获取惟一编号,才能获取该编号对应的数据
            int doc = scoreDocs[i].doc;
            //使用编号,获取真正的数据
            Document document = indexSearcher.doc(doc);
            Article article = ArticleUtils.documentToArticle(document);
            list.add(article);
        }
        return list;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

}
复制代码
对LuceneDao进行测试:

复制代码
import java.io.IOException;
import java.util.List;

import org.junit.Test;

import com.tarena.bean.Article;
import com.tarena.dao.LuceneDao;

public class TestLuceneDao {
private LuceneDao dao = new LuceneDao();

@Test
public void addIndex() {
    for (int i = 0; i <= 25; i++) {
        Article article = new Article();
        article.setId(i);
        article.setTitle("腾讯qq");
        article.setAuthor("马化腾");
        article.setContent("腾讯网(www.QQ.com)是中国浏览量最大的中文门户网站,是腾讯公司推出的集新闻信息、互动社区、娱乐产品和基础服务为一体的大型综合门户网站。腾讯网服务于全球华人...");
        article.setLink("http://www.qq.com/");
        dao.addIndex(article);
    }
}

@Test
public void findIndex() {
    String keywords = "第一";
    List<Article> list = dao.findIndex(keywords, 0, 30);
    for (Article article : list) {
        System.out.println(article.getId());
        System.out.println(article.getTitle());
        System.out.println(article.getContent());
        System.out.println(article.getAuthor());
        System.out.println(article.getLink());
    }
}

@Test
public void deleteIndex(){
    try {
        dao.deleteIndex("author", "陈驰");
    } catch (IOException e) {
        e.printStackTrace();
    }
}

@Test
public void updateIndex(){
    String fieldName = "title";
    String fieldValue = "qq";
    
    Article article = new Article();
    article.setId(1);
    article.setAuthor("陈驰");
    article.setLink("http://www.baidu.com");
    article.setTitle("天下第一");
    article.setContent("天下第一一一一一一");
    try {
        dao.updateIndex(fieldName, fieldValue, article);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

}
复制代码
由此,简单的CURD操作就完成了。

(5)关于分词器

相同一段文本,在不同的分词器下面分成的词是不尽相同的,前面我们的程序中分别用了StandardAnalyzer,以及IKAnalyzer。那么分词器究竟有什么作用呢?

在创建索引时会用到分词器,在使用字符串搜索时也会用到分词器,这两个地方要使用同一个分词器,否则可能会搜索不出结果。

  Analyzer(分词器)的作用是把一段文本中的词按规则取出所包含的所有词。对应的是Analyzer类,这是一个抽象类,切分词的具体规则是由子类实现的,所以对于不同的语言(规则),要用不同的分词器。如下图:

下面分别用过三种分词器,演示分词结果:

复制代码
import java.io.IOException;
import java.io.StringReader;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.cjk.CJKAnalyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.util.Version;
import org.wltea.analyzer.lucene.IKAnalyzer;

public class TestAnalyzer {

public static void main(String[] args) {
    //单字分词器
    //Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_44);
    //二分法分词器
    //Analyzer analyzer = new CJKAnalyzer(Version.LUCENE_44);
    
    //第三方的中文分词器,庖丁分词,中文分词,特点:扩展新的词,自定义停用词
    Analyzer analyzer = new IKAnalyzer();
    String text = "腾讯网(www.QQ.com)是中国浏览量最大的中文门户网站,是腾讯公司推出的集新闻信息、互动社区、娱乐产品和基础服务为一体的大型综合门户网站。腾讯网服务于全球华人...";
    try {
        testAnalyzer(analyzer, text);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

/**
 * 分词器的作用
 * 
 * @throws IOException
 */
public static void testAnalyzer(Analyzer analyzer, String text)
        throws IOException {
    System.out.println("当前使用的分词器:" + analyzer.getClass().getSimpleName());
    TokenStream tokenStream = analyzer.tokenStream("content",
            new StringReader(text));
    tokenStream.addAttribute(CharTermAttribute.class);
    //这里不写这一句,会报空指针异常
    tokenStream.reset();
    while (tokenStream.incrementToken()) {
        CharTermAttribute charTermAttribute = tokenStream
                .getAttribute(CharTermAttribute.class);
        System.out.println(new String(charTermAttribute.toString()));
    }
}

}

前使可以看到程序中分贝用了三种分词器(单字分词器,即标准分词器,二分法分词器,庖丁分词器),处理相同一段文本——腾讯网
显然最后一种分词器,对中文来说分词效果最好。

(6)关于相关度排序

1,相关度得分是在查询时根据查询条件实进计算出来的

2,如果索引库数据不变,查询条件不变,查出的文档得分也不变

在这里,有两种方式改变相关度排序:

1)通过改变文档Boost值来改变排序结果。Boost是指索引建立过程中,给整篇文档或者文档的某一特定属性设定的权值因子,在检索时,优先返回分数高的。通过Document对象的setBoost()方法和Field对象的setBoost()方法,可以分别为Document和Field指定Boost参数。不同在于前者对文档中每一个域都修改了参数,而后者只针对指定域进行修改。默认值为1F,一般不做修改。前面的ArticleUtils类中已经举例。

复制代码
//在添加的时候改变权重值,可以对每个document 的属性进行添加,
//注:如果添加的索引值没有进行分词,则不能改变权限值.
Document document=new Document();
document.add(new StringField(“id”,article.getId(),Store.YES));
StringField field=new StringField(“title”,article.getTitle(),Store.YES);
TextField textField=new TextField(“content”,article.getContent(),Store.YES);
textField.setBoost(5F);
document.add(field);
document.add(textField);
return document;
复制代码
2)使用Sort对象定制排序。Sort支持的排序功能以文档当中的域为单位,通过这种方法,可以实现一个或者多个不同域的多形式的值排序。

复制代码
IndexReader indexReader = DirectoryReader.open(directory);

IndexSearcher indexSearcher = new IndexSearcher(indexReader);

String fields[] = { “title” };

QueryParser queryParser = new MultiFieldQueryParser(
LuceneUtils.getMatchVersion(), fields,
LuceneUtils.getAnalyzer());
// 不同的规则构造不同的子类…
// title:keywords ,content:keywords
Query query = queryParser.parse(“抑郁症”);
Sort sort = new Sort();
// 升序
// SortField sortField=new SortField(“id”, Type.INT);
// 降序
SortField sortField = new SortField(“id”, Type.INT, true);

// 设置排序的字段…
sort.setSort(sortField);
TopDocs topDocs = indexSearcher.search(query, 100, sort);
//注意:String类型和Int类型在比较排序的时候不同
//例如:Int:123>23
// String:”123”<“23”
复制代码

(7)关于过滤

使用Filter可以对搜索结果进行过滤以获得更小范围的结果。使用Filter对性能的影响很大(有可能会使查询慢上百倍)。但也可使用相应的查询实现一样的效果。

复制代码
IndexReader indexReader = DirectoryReader.open(directory);

IndexSearcher indexSearcher = new IndexSearcher(indexReader);

String fields[] = { “title” };

QueryParser queryParser = new MultiFieldQueryParser(
LuceneUtils.getMatchVersion(), fields,
LuceneUtils.getAnalyzer());
// 不同的规则构造不同的子类…
// title:keywords ,content:keywords
Query query = queryParser.parse(“抑郁症”);

/**

  • 1:需要根据那个字段进行过滤
  • 2:字段对应的最小值
  • 3:字段对应的最大值
  • 4:是否包含最小值
  • 5:是否包含最大值…

*/
// filter 是一个抽象类,定义不同的filter 相当于是不同的过滤规则…
Filter filter = NumericRangeFilter
.newIntRange(“id”, 1, 10, false, true);

TopDocs topDocs = indexSearcher.search(query, filter, 100);
复制代码
(8)关于查询(这个非常非常重要,这是结合前面的分词器分出的词进行不同的查询的)

复制代码
import org.apache.lucene.document.Document;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.NumericRangeQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;

import com.tarena.utils.LuceneUtils;

/**
*

  • indexSearcher.searcher(Query )
  • Query 是一个查询,条件,不同的子类相当于不同的查询规则
  • 我们可以扩展…
  • @author Administrator

*/
public class TestQuery {
public static void main(String[] args) throws Exception {

    // testQuery();
    /**
     *  第一种查询,TermQuery
     *  这是关键字查询
     *  如果按照author查,因为author没有分词,所以查"马化腾"可以查询出来
     *  如果按照content查,因为content分词了,如果是单字分词器,只能通过某一个字查出来,比如"中"
     */
    //Query query=new TermQuery(new Term("content","中"));

    /**
     *  第二种查询:字符串搜索..
     *  使用查询字符串:QueryParser+ MultiFieldQueryParser的查询方式
     *  1、QueryParser:只在一个字段中查询
     *  2、MultiFieldQueryParser:可以在多个字段查询
     *  用来查询可以分词的字段,只要你输入的一段文本中包含分词,就会检索出来
     */

// String[] fields={“content”};
//
// QueryParser queryParser=new
// MultiFieldQueryParser(LuceneUtils.getMatchVersion(),fields,LuceneUtils.getAnalyzer());
// Query query=queryParser.parse(“中文”);

    /**
     *  第三种查询:查询所有..
     */
    // Query query=new MatchAllDocsQuery();

    /**
     *  第四种查询:范围查询,可以使用此查询来替代过滤器...
     */
    // 我们完成一种需求有两种方式,我们推荐用这种...性能比filter要高

    // Query query=NumericRangeQuery.newIntRange("id", 1, 10, true, true);

    /**
     *  第五种查询:通配符。。。
     */
    // ?代表单个任意字符,* 代表多个任意字符...
    // Query query=new WildcardQuery(new Term("title", "luce*"));

    /**
     *  第六种查询:模糊查询..。。。
     */
    // author String
    /*
     * 1:需要根据查询的条件
     * 
     * 2:最大可编辑数 取值范围0,1,2 允许我的查询条件的值,可以错误(或缺少)几个字符...
     * 
     */
    // Query query = new FuzzyQuery(new Term("author", "爱新觉罗杜小"), 1);
    /**
     * 
     * 第七种查询:短语查询
     * 
     */
    // PhraseQuery query=new PhraseQuery();
    // //(1)直接指定角标...
    // // query.add(new Term("title","solr"),0);
    // // query.add(new Term("title","全"),8);

    // (2)设置两个短语之间的最大间隔数...
    // //设置间隔数范围越大,它被匹配的结果就越多,性能也就越慢..
    // query.add(new Term("title","solr"));
    // query.add(new Term("title","全"));
    // query.setSlop(18);

    // 第八种查询:布尔查询
    BooleanQuery query = new BooleanQuery();
    // id 1~10
    Query query1 = NumericRangeQuery.newIntRange("id", 1, 10, true, true);

    Query query2 = NumericRangeQuery.newIntRange("id", 5, 15, true, true);

    // select * from table where title=? or content=?

    // 必须满足第一个条件...
    query.add(query1, Occur.MUST);

    // 可以满足第二个条件
    query.add(query2, Occur.SHOULD);
    testQuery(query);
}

public static void testQuery(Query query) throws Exception {
    IndexSearcher indexSearcher = LuceneUtils.getIndexSearcher();
    TopDocs topDocs = indexSearcher.search(query, 100);
    System.out.println("总记录数:" + topDocs.totalHits);
    for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
        Document document = indexSearcher.doc(scoreDoc.doc);
        System.out.println(document.get("id"));
        System.out.println(document.get("title"));
        System.out.println(document.get("content"));
        System.out.println(document.get("author"));
        System.out.println(document.get("link"));
    }

}

}
复制代码
其中, 布尔查询很重要,可以实现像京东、淘宝上面的多级检索的效果,示例代码如下:

复制代码
//封装查询条件(使用BooleanQuery对象,连接多个查询条件)
BooleanQuery query = new BooleanQuery();
//条件一(所属单位)
if(StringUtils.isNotBlank(projId)){
//词条查询
TermQuery query1 = new TermQuery(new Term(“projId”, projId));
//Occur.SHOULD相当于or
query.add(query1, Occur.MUST);//Occur.MUST相当与sql语句的and
}
//条件二(图纸类别)
if(StringUtils.isNotBlank(belongTo)){
//词条查询
TermQuery query2 = new TermQuery(new Term(“belongTo”, belongTo));
query.add(query2, Occur.MUST);//相当与sql语句的and
}

        //条件三(文件名称和文件描述)
        if(StringUtils.isNotBlank(queryString)){
            //多个字段进行检索的时候,查询使用QueryPaser
            //要是直接new QueryParser(),也可以,但是只能查询一个字段
            QueryParser queryParser = new MultiFieldQueryParser(Version.LUCENE_CURRENT,new String[]{"fileName","comment"},Configuration.getAnalyzer());
            Query query3 = queryParser.parse(queryString);
            query.add(query3, Occur.MUST);//相当与sql语句的and
        }

复制代码
(9)设置文字高亮

效果图:

1)高亮需要的jar包

highlighter\lucene-highlighter-4.4.0.jar

memory\lucene-memory-4.4.0.jar

2)高亮的特点

1、高亮将文本生成一段摘要,用于搜索,并把摘要中的关键词高亮显示

2、摘要的大小可以配置,默认100个字符。

3、文本实现高亮的效果,就是在需要高亮的文字前后加前缀与后缀(类似Html)

示例如下:

复制代码
import java.util.ArrayList;
import java.util.List;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.highlight.Formatter;
import org.apache.lucene.search.highlight.Fragmenter;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.Scorer;
import org.apache.lucene.search.highlight.SimpleFragmenter;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
import org.wltea.analyzer.lucene.IKAnalyzer;

import com.tarena.bean.Article;
import com.tarena.dao.LuceneDao;
import com.tarena.utils.ArticleUtils;
import com.tarena.utils.LuceneUtils;

public class TestHighLight {

public static void main(String[] args) {
    String keywords = "中华人民共和国";
    List<Article> list = findIndex(keywords, 0, 10);
    for (Article article : list) {
        System.out.println(article.getId());
        System.out.println(article.getTitle());
        System.out.println(article.getContent());
        System.out.println(article.getAuthor());
        System.out.println(article.getLink());
    }
}

/**
 * 查询是通过IndexSearch提供的(分页)
 */
public static List<Article> findIndex(String keywords, int start, int count) {
    Analyzer analyzer = new IKAnalyzer();
    try {
        IndexSearcher indexSearcher = LuceneUtils.getIndexSearcher();

        // ===========================================================
        // 这里是第二种query方式,不是termQuery
        QueryParser queryParser = new MultiFieldQueryParser(
                LuceneUtils.getMatchVersion(), new String[] { "title",
                        "content" }, analyzer);
        Query query = queryParser.parse(keywords);
        TopDocs topDocs = indexSearcher.search(query, 100);
        System.out.println("总记录数:" + topDocs.totalHits);

        /**
         * 添加设置文字高亮begin 使用lucene自带的高亮器进行高亮显示
         */
        // html页面高亮显示的格式化,默认是<b></b>
        Formatter formatter = new SimpleHTMLFormatter(
                "<font color='red'><b>", "</b></font>");
        // 执行查询条件,因为高亮的值就是查询条件
        Scorer scorer = new QueryScorer(query);
        Highlighter highlighter = new Highlighter(formatter, scorer);

        // 设置文字摘要,此时摘要大小
        int fragmentSize = 100;
        Fragmenter fragmenter = new SimpleFragmenter(fragmentSize);
        highlighter.setTextFragmenter(fragmenter);
        /** 添加设置文字高亮end */
        // 表示返回的结果集
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        List<Article> list = new ArrayList<Article>();

        int min = Math.min(scoreDocs.length, start + count);
        for (int i = start; i < min; i++) {
            //System.out.println("相关度得分:" + scoreDocs[i].score);
            // 获取查询结果的文档的惟一编号,只有获取惟一编号,才能获取该编号对应的数据
            int doc = scoreDocs[i].doc;
            // 使用编号,获取真正的数据
            Document document = indexSearcher.doc(doc);

            /** 获取文字高亮的信息 begin */
            // 获取文字的高亮,一次只能获取一个字段高亮的结果,如果获取不到,返回null值
            // 高亮之后的title
            // 注意:如果这个字段当中没有包含搜索关键字,你对这个字段的值进行高亮,返回的是null
            String title = highlighter.getBestFragment(
                    analyzer, "title",
                    document.get("title"));
            // 如果null表示没有高亮的结果,如果高亮的结果,应该将原值返回
            if (title == null) {
                title = document.get("title");
                if (title != null && title.length() > fragmentSize) {
                    // 截串,从0开始
                    title = title.substring(0, fragmentSize);
                }
            }
            System.out.println("-------title:" + title);
            // 高亮之后的content
            // 注意:如果这个字段当中没有包含搜索关键字,你对这个字段的值进行高亮,返回的是null
            String content = highlighter.getBestFragment(
                    analyzer, "content",
                    document.get("content"));
            // 如果null表示没有高亮的结果,如果高亮的结果,应该将原值返回
            if (content == null) {
                content = document.get("content");
                if (content != null && content.length() > fragmentSize) {
                    // 截串,从0开始
                    content = content.substring(0, fragmentSize);
                }
            }
            System.out.println("--------content:" + content);
            /** 获取文字高亮的信息 end */
            Article article = new Article();
            article.setId(Integer.parseInt(document.get("id")));
            article.setAuthor(document.get("author"));
            article.setLink(document.get("link"));
            article.setTitle(title);//高亮之后的
            article.setContent(content);//高亮之后的
            list.add(article);
        }
        return list;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

}
复制代码
回到顶部
六、Lucene优化
(1)MergePolicy 设置合并规则

复制代码
public void testOptimise1() throws IOException {
// 可以通过indexWriterConfig这个对象进行优化
// 在lucene4.0之后的版本会对索引进行自动的优化
// 通过改几个配置

    Directory d = FSDirectory.open(new File(Constant.FILEURL));

    IndexWriterConfig conf = new IndexWriterConfig(
            LuceneUtils.getMatchVersion(), LuceneUtils.getAnalyzer());
    // 在lucene里面是0配置的
    // 通过设置对象的参数来进行配置

    LogDocMergePolicy mergePolicy = new LogDocMergePolicy();

    /**
     * 
     * 1:mergeFactor
     * 
     * 当这个值越小,更少的内存被运用在创建索引的时候,搜索的时候越快,创建索引的时候越慢..
     * 
     * 当这个值越大,更多的内存被运用在创建索引的时候,搜索的时候越慢,创建的时候越快...
     * 
     * 
     * smaller value 2 < smaller value <10
     * 
     */
    // 设置索引的合并因子...
    mergePolicy.setMergeFactor(6);
    conf.setMergePolicy(mergePolicy);
    IndexWriter indexWriter = new IndexWriter(d, conf);
}

复制代码
(2)排除停用词,排除停用,被分词器过滤掉,词就不会建立索引,索引文件就会变小,这样搜索的时候就会变快。

(3)将索引数据分区存放

(4)将索引放在内存当中,而不是硬盘当中

Lucene的API接口设计的比较通用,输入输出结构都很像:

  数据库的表==>记录==>字段。

  所以很多传统的应用的文件、数据库等都可以比较方便的映射到Lucene的存储结构/接口中。总体上看:可以先把Lucene当成一个支持全文索引的数据库系统。

  Lucene的索引存储位置使用的是一个接口(抽象类),也就可以实现各种各样的实际存储方式(实现类、子类),比如存到文件系统中,存在内存中、存在数据库中等等。

• Lucene提供了两个子类:FSDirectory与RAMDirectory。
FSDirectory:在文件系统中,是真实的文件夹与文件。

  RAMDirectory:在内存中,是模拟的文件夹与文件。

• RAMDirectory与FSDirectory相比:
1、因为没有IO操作,所以速度快(优点)。

  2,因为在内存中,所以在程序退出后索引库数据就不存在了(缺点)。

复制代码
public void testOptimise4() throws IOException, ParseException {

    // 索引在硬盘里面...
    Directory directory1 = FSDirectory.open(new File(Constant.FILEURL));

    IOContext ioContext = new IOContext();

    // 索引放在内存当中...
    Directory directory = new RAMDirectory(directory1, ioContext);

    IndexReader indexReader = DirectoryReader.open(directory);

    IndexSearcher indexSearcher = new IndexSearcher(indexReader);

    String fields[] = { "title" };

    QueryParser queryParser = new MultiFieldQueryParser(
            LuceneUtils.getMatchVersion(), fields,
            LuceneUtils.getAnalyzer());
    // 不同的规则构造不同的子类..
    // title:keywords ,content:keywords
    Query query = queryParser.parse("抑郁症");

    TopDocs topDocs = indexSearcher.search(query, 100);

    System.out.println(topDocs.totalHits);

}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值