一、全文检索概述
1.常见的全文检索
(1)window系统中的指定盘符中的某一个位置来搜索.(2)百度或google中,可以搜索互联网中的信息,有网页,pdf,word,音频,视频等内容.
(3)在bbs系统中,有搜索文章的功能.
上边的查询功能都相似,都是查询的文本内容,查询方法也相似即找出含有指定字符串的资源,只不过是查询的范围不一样(硬盘,帮助文档,互联网).
2.全文检索的概念
(1)从大量的信息中快速、准确地找出需要的信息.(2)搜索的内容是文本信息(不是多媒体)
(3)搜索的方式:不是根据语句的意思进行处理.
(4)全面、快速、准确是衡量全文检索系统的关键指标.
(5)概括:
* 只处理文本.
* 不处理语义.
* 搜索英文时不区分大小写.
* 结果列表有相关度排序.
3.全文检索的应用场景
常用在有大量数据出现的系统中.* 站内搜索
(1)bbs的关键字搜索
百度贴吧等
(2)商品网站的搜索
中关村在线
(3)文件管理系统
Windows的文件搜索.
* 垂直搜索
(1)针对某个行业的搜索引擎
(2)搜索引擎的细分和延伸
(3)是针对网页库中的专门信息的整合
(4)其特点是专、深、精,并具有行业色彩
(5)可以应用于购物搜索、房产搜索、人才搜索
4.全文检索与数据库搜索的区别
(1)数据库的搜索类似:select * from 表名 where 字段名 like '%关键字%'
缺点:
* 搜索效果比较差
* 在搜索的结果中,有大量数据被搜索出来,有很多数据是没有用的.
* 查询速度在大量数据的情况下是很难做到快速的.
(2)全文检索
* 搜索结果按相关度排序:意味着只有前几个页面对于用户来说是比较有用的,其他的结果与用户想要的答案很可能相差甚远.
数据库搜索是做不到相关度排序的.
* 因为全文检索是采用索引的方式,所以在速度上肯定比数据库方式like要快.
* 因此数据库搜索不能代替全文检索.
二、Lucene概述
1.lucene简介
* 全文检索是一个概念,而具体的实现有很多框架,lucene是其中一种.* lucene是一个开放源代码的全文检索引擎工具包,由Apache软件基金会支持和提供.
* lucene是一个高效的,可扩展的,全文检索库.
* 全部用java实现,无需配置.
* 仅支持纯文本文件的索引(Indexing)和搜索(Search)
* 不负责由其他格式的文件抽取纯文本文件,或从网络中抓取文件的过程.
2.互联网搜索流程
* 当用户打开百度网页搜索某些数据时,不是直接找的网页,而是找百度的索引库.索引库里包含的内容有索引号和摘要,查询出来的是摘要的内容
* 百度的索引库的索引和互联网的某一个网站相对应.
* 用户点击每一个搜索出来的内容惊醒网页查找时,这个时候找的才是互联网中的网页.
3.lucene相关细节
* 在数据库中,数据库中的数据文件存储在磁盘上,索引库也是同样,索引库中的索引数据也在磁盘上存在,我们用Directory这个类来描述.
* 我们可以通过API来实现对索引库的增删改查的操作.
* 在数据库中,各种数据形式都可以概括为一种:表.而在索引库中,各种数据形式也可以抽象出一种数据格式:Document.
* Document的结构为:Document(List<Field>)
* Field里存放一个键值对,键值对都为字符串的形式.
* 对索引库中索引的操作实际上也就是对Document的操作.
4.lucene的索引结构
(1) 索引(Index):* 在lucene中一个索引是放在一个文件夹中的.
* 同一个文件夹中的所有文件构成一个lucene索引.
(2)段(Segment):
* 一个索引可以包含多个段,段与段之间是独立的 ,添加新文档可以生成新的段,不同的段可以合并.
* 具有相同前缀文件的属同一个段.
* segments.gen和segments_5是段的元数据文件,也即它们保存了段的属性信息.
(3)文档(Document)
* 文档是我们建立索引的基本单位,不同的文档是保存在不同的段中的,一个段可以包含多篇文档.
* 新添加的文档是单独保存在一个新生成的段中,随着段的合并,不同的文档合并到同一个段中.
(4)域(Field):
* 一篇文档包含不同类型的信息,可以分开索引,比如标题,时间,正文,作者等,都可以保存在不同的域里.
* 不同域的索引方式可以不同.
(5)词(Term):
* 词是索引的最小单位,是经过词法分析和语言处理后的字符串.
说明:lucene的索引结构中,及保存了正向信息,也保存了反向信息.
正向信息: 按层次保存了从索引,一直到词的包含关系:
索引(Index)-->段(segment)-->文档(Document)-->域(Field)-->词(Term)
反向信息:保存了词典到倒排表的映射
词(Term)-->文档(Document)
5.简单代码示例
(1)准备开发环境需要的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 (高亮器)
(2)创建索引
public void testCreateIndex() throws Exception{
//Article类,有id,title,content字段,并有get,set方法.
Article article = new Article();
article.setId(1L);
article.setTitle("lucene");
article.setContent("baidu,google all good");
Directory directory = FSDirectory.open(new File("./indexDir"));
Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_30);
//第一步:创建indexWriter
IndexWriter indexWriter = new IndexWriter(directory,analyzer,MaxFieldLength.LIMITED);
Document document = new Document();
Field idField = new Field("id",article.getId().toString(),Store.YES,Index.NOT_ANALYZED);
Field titleField = new Field("title",article.getTitle(),Store.YES,Index.ANALYZED);
Field contentField = new Field("content",article.getContent(),Store.YES,Index.ANALYZED);
//其中Stroe表示是否存储,Index..表示是否分词,例如id需要存储但不用分词.
document.add(idField);
document.add(titleField);
document.add(contentField);
//第二部:添加document
indexWriter.addDocument(document);
//第三部:关闭indexWriter
indexWriter.close();
}
(3)搜索
public void testSearchIndex() throws Exception{
Directory directory = FSDirectory.open(new File("./indexDir"));
//第一步:创建IndexSearcher
IndexSearcher indexSearch = new IndexSearcher(directory);
Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_30);
//下面的content表明要搜索的Field,即要搜索的范围
QueryParser queryParser = new QueryParser(Version.LUCENE_30,"content",analyzer);
Query query = queryParser.parse("baidu");//要搜索的关键词,查询方式很多种,query生成种类不同
//第二步:进行搜索,search(query,5),5表示要显示前5条记录
TopDocs topDocs = indexSearch.search(query, 5);
int count = topDocs.totalHits;//获得总记录数
ScoreDoc[] scoreDocs = topDocs.scoreDocs;//获得前5行目录的id列表
List<Article> articleList = new ArrayList<Article>();
for(ScoreDoc scoreDoc : scoreDocs){
float score = scoreDoc.score;//获取相关度得分,可以根据此值进行相关度排名.
int index = scoreDoc.doc;//获取目录列表id
Document document = indexSearch.doc(index);//根据id获得Document
//把document转化为Article
Article article = new Article();
article.setId(Long.parseLong(document.get("id")));
article.setTitle(document.get("title"));
article.setContent(document.get("content"));
articleList.add(article);
}
//循环输出要检索的内容.
for(Article article : articleList){
System.out.println(article.getId());
System.out.println(article.getTitle());
System.out.println(article.getContent());
}
}
(4)简单优化
* Document到Article,与Article到Document的转化可以封装成一个工具类.
* Directory与Analyzer,也可以封装成一个类中.
* 原生的分词器对中文分词并不好,要引入另外一个分词器:IKAnalyzer,除了jar包还有三个文件
①ext_stopword.dic为停止词的词库,词库里的词都被当作为停止词使用.
②mydict.dic中添加自己所需要的词,扩充词库.
③IKAnalyzer.cfg.xml为IKAnalyzer的配置文件.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!-- 配置扩展词典 -->
<entry key="ext_dict">/mydict.dic</entry>
<!-- 配置扩展停用词词典 -->
<entry key="ext_stopwords">/ext_stopword.dic</entry>
</properties>
* 在indexWriter.close()之前加入indexWriter.optimize(),当添加同一document创建索引时,覆盖原有的索引文件(内部其实是先进行删除标识).
防止相同索引文件占用空间.
6.查询方式
(1)关键词查询(区分大小写)Term term = new Term("title","lucene");
Query query = new TermQuery(term);
indexSearch.search(query, 5);
(2)查询所有的文档(常用)
Query query = new MatchAllDocsQuery();
indexSearch.search(query, 5);
(3) 通配符查询(常用)
* 代表任意多个任意字符
? 任意一个任意字符
Term term = new Term("title","*.java");
Query query = new WildcardQuery(term);
indexSearch.search(query, 5);
(4) 短语查询
所有的关键词对象必须针对同一个属性
Term term = new Term("title","lucene");
Term term2 = new Term("title","搜索");
PhraseQuery query = new PhraseQuery();
query.add(term,0);//针对第几条索引进行查询
query.add(term2,4);
indexSearch.search(query, 5);
(5)boolean查询
各种关键词的组合
Term term = new Term("title","北京");
TermQuery termQuery = new TermQuery(term);
Term term2 = new Term("title","美女");
TermQuery termQuery3 = new TermQuery(term2);
BooleanQuery query = new BooleanQuery();
query.add(termQuery,Occur.SHOULD);
query.add(termQuery2,Occur.MUST); //表明搜索条件为,必须有美女关键字,而北京关键字可有可无,should表示或
indexSearch.search(query, 5);
(6)范围查询
//搜索id为5到10的索引,含头也含尾
Query query = NumericRangeQuery.newLongRange("id", 5L, 10L, true, true);
indexSearch.search(query, 5);
7.高亮
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, 25);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
/***********************************************************************/
//给关键字加上前缀和后缀
Formatter formatter = new SimpleHTMLFormatter("<font color='red'>","</font>");
//scorer封装了关键字
Scorer scorer = new QueryScorer(query);
Highlighter highlighter = new Highlighter(formatter,scorer);
//创建一个摘要
Fragmenter fragmenter = new SimpleFragmenter(10);//指定摘要大小,如果使用无参构造器,默认为100.
highlighter.setTextFragmenter(fragmenter);
/***********************************************************************/
List<Article> articleList = new ArrayList<Article>();
for(ScoreDoc scoreDoc:scoreDocs){
float score = scoreDoc.score;
System.out.println(score);//相关的得分
Document document = indexSearcher.doc(scoreDoc.doc);
Article article = DocumentUtils.document2Article(document);
//使用高亮器
//1、分词器:查找关键词.2、字段:在哪个字段上进行高亮.3、字段的内容:把字段的内容提取出来.
String titleText = highlighter.getBestFragment(LuceneUtils.analyzer, "title", document.get("title"));
String contentText = highlighter.getBestFragment(LuceneUtils.analyzer, "content", document.get("content"));
if(titleText!=null){
article.setTitle(titleText);
}
if(contentText!=null){
article.setContent(contentText);
}
articleList.add(article);
}
//循环输出检索内容
for(Article article:articleList){
System.out.println(article.getId());
System.out.println(article.getTitle());
System.out.println(article.getContent());
}
}
8.核心API
(1)IndexWriter* 此类可以对索引进行增、删、改操作.
* 利用构造方法可以构造一个IndexWriter的对象.
* addDocument: 向索引库中添加一个Document
* updateDocument: 更新一个Document
* deleteDocuments:删除一个Document
(2)Directory(指向索引库的位置)
* FSDirectory
①通过 FSDirectory.open(new File("./indexDir"))建立一个indexDir的文件夹,而这个文件夹就是索引库存放的位置.
②通过这种方法建立索引库时如果indexDire文件夹不存在,程序将自动创建一个,如果存在就用原来的这个.
③通过这个类可以知道所建立的索引库在磁盘上,能永久性的保存数据。这是优点.
④缺点为因为程序要访问磁盘上的数据,这个操作可能引发大量的IO操作,会降低性能.
* RAMDirectory
①通过构造函数的形式Directory ramdirectory = new RAMDirectory(fsdirectory)可以建立RAMDirectory
②这种方法建立的索引库会在内存中开辟一定的空间,通过构造函数的形式把fsdirectory移动到内存中
③这种方法索引库中的数据是暂时的,只要内存的数据消失,这个索引库就跟着消失了
④因为程序是在内存中跟索引库交互,所以利用这种方法创建的索引的好处就在效率比较高,访问速度比较快
(3)Document
* 一个Directory是由很多Document组成的。用户从客户端输入的要搜索的关键内容被服务器端包装成JavaBean,然后再转化为Document
(4)Field
* Field相当于javaBean的属性.
* new Field("title",article.getTitle(),Store.YES,Index.ANALYZED);
第一参数为属性,第二个为属性值,第三个参数为是否向索引库里存储.
第四个参数为是否更新索引库:
NO:不进行引索.
ANALYZED:进行分词引索.
NOT_ANALYZED:进行引索,把整个输入作为一个词对待.
(5)MaxFieldLength
* 能存储的最大长度.
* 在IndexWriter的构造方法里使用.
* 值:
LIMITED:限制的最大长度,值为10000
UNLIMITED:没有限制的最大长度(一般不使用)