Lucene 基础教程
简介
Lucene是什么?
Lucene是一个全文搜索框架,提供了一种可以实现类似于Google、Baidu搜索引擎功能的工具,但并非应用产品。
Lucene原理
lucene提供的服务实际包含两部分:
写入:将你提供的源(本质是字符串)写入索引或者将其从索引中删除;
读出:即向用户提供全文搜索服务,让用户可以通过关键词定位源。
写入详解
源字符串首先经过analyzer(分词器)处理,分成一个个词语。
将源中需要的信息加入Document的各个Field中,并把需要索引的Field索引起来,把需要存储的Field存储起来。
将索引写入存储器存储器可以是内存或磁盘。
读出详解
用户提供搜索关键词,经过analyzer分词处理。
对处理后的关键词搜索索引找出对应的Document域。
用户根据需要从找到的Document域中提取需要的文段。
构建索引简单的代码片段
IndexWriter writer = new IndexWriter(“/data/index/”, new StandardAnalyzer(), true)
Document doc = new Document()
doc.add (new Field("title" , "lucene introduction" , Field.Store .YES , Field.Index .TOKENIZED ))
doc.add (new Field("content" , "lucene works well" , Field.Store .YES , Field.Index .TOKENIZED ))
writer.addDocument (doc)
writer.optimize ()
writer.close ()
代码分析
1.创建一个writer,指定存放索引目录为“/data/index”
2.使用的分析器为StandardAnalyzer,
3.我们新建一个document,覆盖索引目录已存在的索引文件(第三个参数)
4.向document添加名为“title"的field,内容是“lucene introduction”,存储并索引。
5.添加名为“content”的field,内容是“lucene works well”,存储并索引。
6.将此文档添加到索引中,如有多个文档,重复以上操作,创建document并添加。
7.添加完所有document,对索引进行优化,将多个segment合并,提高索引速度。
8.r关闭writer(重要)。
如果你想把纯文本文件索引起来,而不想自己将它们读入字符串创建field,你可以用下面的代码创建field:
Field field = new Field("content" , new FileReader(file ));
这里的file就是该文本文件。该构造函数实际上是读去文件内容,并对其进行索引,但不存储。
实现简单的索引
在根目录下建一个名为data的文件夹,文件夹里面建两个个txt文件:"1.txt","2.txt"
其中1.txt的内容如下:
Lucene是开源项目。
它可以用于任何应用程序来搜索功能。
Lucene是简单而功能强大的基于Java的搜索库。
"2.txt"内容可以随便写。
这里我们给2.txt的内容为:
索引和搜索。
它是可扩展的,高性能的库用于索引和搜索几乎任何类型的文本。
Lucene库提供了所需的任何搜索应用程序的核心业务。
索引和搜索。
建立索引
package com.goson.lucene;
import org.apache.commons.io.FileUtils;
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.TextField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
/**
* Created by dllo on 17/12/26.
*/
public class Index {
public void index () {
IndexWriter indexWriter = null ;
try {
Directory directory = FSDirectory.open(FileSystems.getDefault().getPath(".../Lucene/index" ));
Analyzer analyzer = new StandardAnalyzer();
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
indexWriter = new IndexWriter(directory, indexWriterConfig);
indexWriter.deleteAll();
File dataFile = new File(".../Lucene/data" );
File[] fileList = dataFile.listFiles();
for (File file : fileList) {
Document document = new Document();
/**
* field
一个Document可以包含多个信息域,例如一篇文章可以包含“标题”、“正文”、“最后修改时间”等信息域,这些信息域就是通过Field在Document中存储的。
Field有两个属性可选:存储和索引。通过存储属性你可以控制是否对这个Field进行存储;通过索引属性你可以控制是否对该Field进行索引。这看起来似乎有些废话,事实上对这两个属性的正确组合很重要,下面举例说明:还是以刚才的文章为例子,我们需要对标题和正文进行全文搜索,所以我们要把索引属性设置为真,同时我们希望能直接从搜索结果中提取文章标题,所以我们把标题域的存储属性设置为真,但是由于正文域太大了,我们为了缩小索引文件大小,将正文域的存储属性设置为假,当需要时再直接读取文件;我们只是希望能从搜索解果中提取最后修改时间,不需要对它进行搜索,所以我们把最后修改时间域的存储属性设置为真,索引属性设置为假。上面的三个域涵盖了两个属性的三种组合,还有一种全为假的没有用到,事实上Field不允许你那么设置,因为既不存储又不索引的域是没有意义的。
*/
document.add(new Field("content" , FileUtils.readFileToString(file, "utf-8" ), TextField.TYPE_STORED));
document.add(new Field("fileName" , file.getName(), TextField.TYPE_STORED));
indexWriter.addDocument(document);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (indexWriter != null ) {
try {
indexWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
查询
package com .goson .lucene
import org.apache .lucene .analysis .Analyzer
import org.apache .lucene .analysis .TokenStream
import org.apache .lucene .analysis .standard .StandardAnalyzer
import org.apache .lucene .document .Document
import org.apache .lucene .index .DirectoryReader
import org.apache .lucene .queryparser .classic .ParseException
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 .*
import org.apache .lucene .store .Directory
import org.apache .lucene .store .FSDirectory
import java.io .IOException
import java.io .StringReader
import java.nio .file .FileSystems
public class Search {
public void search(String keyword) {
try {
//获取索引文件
Directory directory = FSDirectory.open (FileSystems.getDefault ().getPath (".../Lucene/index" ))
//创建索引文件的读取器
DirectoryReader reader = DirectoryReader.open (directory)
//创建检索对象
IndexSearcher indexSearcher = new IndexSearcher(reader)
//下面开始对用户输入的关键词做处理
//创建分词器
Analyzer analyzer = new StandardAnalyzer()
//创建查询解析器
QueryParser queryParser = new QueryParser("content" , analyzer)
//对keyword进行解析
Query query = queryParser.parse (keyword)
//检索 并生成结果集 参数一: 检索的关键词 参数2 : 显示的条数
TopDocs search = indexSearcher.search (query, 2 )
//高亮的评分
QueryScorer scorer = new QueryScorer(query)
//将原始的字符串拆分成独立的片段
Fragmenter fragmenter = new SimpleSpanFragmenter(scorer)
//创建html高亮标签
SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<font color='red'" ,"</font>" )
//高亮分析器
Highlighter highlighter = new Highlighter(simpleHTMLFormatter,scorer)
highlighter.setTextFragmenter (fragmenter)
for(ScoreDoc scoreDoc : search.scoreDocs ){
Document document = indexSearcher.doc (scoreDoc.doc )
String content = document.get ("content" )
if(content !=null){
// tokenStream是分词器做好处理之后得到的一个流
// 这个流里面储存了粉刺的各种信息
// 可以通过tokenStream有效的获取分词单元
TokenStream tokenStream = analyzer.tokenStream ("content" , new StringReader(content))
String hContent = highlighter.getBestFragment (tokenStream, content)
System.out .println (hContent)
}
}
} catch (IOException e) {
e.printStackTrace ()
} catch (ParseException e) {
e.printStackTrace ()
} catch (InvalidTokenOffsetsException e) {
e.printStackTrace ()
}
}
}
测试并在控制台显示
package com.goson.lucene;
import org.junit.Test;
/**
* Created by dllo on 17/12/26.
*/
public class MainTest {
@Test
public void test1 () {
Index index = new Index();
index.index();
Search search = new Search();
search.search("扩展搜索" );
}
}