Lucene和搜索引擎不是一回事
Lucene是一个工具包,它不能独立运行,不能单独对外提供服务。
搜索引擎可以独立运行对外提供搜索服务。
1.0 全文检索的定义
全文检索首先对要搜索的文档进行分词,然后形成索引,通过查询索引来查询文档。
全文检索就是先创建索引,然后根据索引来进行搜索的过程,就叫全文检索。
比如:字典,
字典的偏旁部首页,就类似于luence的索引
字典的具体内容,就类似于luence的文档内容
1.1 环境准备
- Jdk:1.7及以上
- Lucene:4.10(从4.8版本以后,必须使用jdk1.7及以上)
- Ide:indigo
- 数据库:mysql 5
1.2 Lucene下载
Lucene是开发全文检索功能的工具包,使用时从官方网站下载,并解压。
官方网站:http://lucene.apache.org/
目前最新版本:5.4.0
下载地址:http://archive.apache.org/dist/lucene/java/
下载版本:4.10.3
JDK要求:1.7以上(从版本4.8开始,不支持1.7以下)
1.3 工程搭建
- Mysql驱动包
- Analysis的包
- Core包
- QueryParser包
- Junit包(非必须)
1.4 索引流程
为什么采集数据
全文检索搜索的内容的格式是多种多样的,比如:视频、mp3、图片、文档等等。
对于这种格式不同的数据,需要先将他们采集到本地,然后统一封装到lucene的文档对象中,
也就是说需要将存储的内容进行统一才能对它进行查询。
采集数据的方式
- 对于互联网中的数据,使用爬虫工具(http工具)将网页爬取到本地
- 对于数据库中的数据,使用jdbc程序进行数据采集
- 对于文件系统的数据,使用io流采集
索引
1.5 采集数据
>
public class BookDaoImpl implements BookDao {
@Override
public List<Book> queryBooks() {
// 数据库链接
Connection connection = null;
// 预编译statement
PreparedStatement preparedStatement = null;
// 结果集
ResultSet resultSet = null;
// 图书列表
List<Book> list = new ArrayList<Book>();
try {
// 加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
// 连接数据库
connection = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/solr", "root", "root");
// SQL语句
String sql = "SELECT * FROM book";
// 创建preparedStatement
preparedStatement = connection.prepareStatement(sql);
// 获取结果集
resultSet = preparedStatement.executeQuery();
// 结果集解析
while (resultSet.next()) {
Book book = new Book();
book.setId(resultSet.getInt("id"));
book.setName(resultSet.getString("name"));
book.setPrice(resultSet.getFloat("price"));
book.setPic(resultSet.getString("pic"));
book.setDescription(resultSet.getString("description"));
list.add(book);
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
}
>
1.6 创建索引
IndexWriter是索引过程的核心组件,通过IndexWriter可以创建新索引、更新索引、删除索引操作。
IndexWriter需要通过Directory对索引进行存储操作。
Directory描述了索引的存储位置,底层封装了I/O操作,负责对索引进行存储。
它是一个抽象类,它的子类常用的包括FSDirectory(在文件系统存储索引)、RAMDirectory(在内存存储索引)。
@Test
public void createIndex() throws Exception {
// 采集数据
BookDao dao = new BookDaoImpl();
List<Book> list = dao.queryBooks();
// 将采集到的数据封装到Document对象中
List<Document> docList = new ArrayList<>();
Document document;
for (Book book : list) {
document = new Document();
// store:如果是yes,则说明存储到文档域中
// 图书ID
Field id = new TextField("id", book.getId().toString(), Store.YES);
// 图书名称
Field name = new TextField("name", book.getName(), Store.YES);
// 图书价格
Field price = new TextField("price", book.getPrice().toString(),
Store.YES);
// 图书图片地址
Field pic = new TextField("pic", book.getPic(), Store.YES);
// 图书描述
Field description = new TextField("description",
book.getDescription(), Store.YES);
// 将field域设置到Document对象中
document.add(id);
document.add(name);
document.add(price);
document.add(pic);
document.add(description);
docList.add(document);
}
// 创建分词器,标准分词器
Analyzer analyzer = new StandardAnalyzer();
// 创建IndexWriter
IndexWriterConfig cfg = new IndexWriterConfig(Version.LUCENE_4_10_3,
analyzer);
// 指定索引库的地址
File indexFile = new File("E:\\11-index\\hm19\\");
Directory directory = FSDirectory.open(indexFile);
IndexWriter writer = new IndexWriter(directory, cfg);
// 通过IndexWriter对象将Document写入到索引库中
for (Document doc : docList) {
writer.addDocument(doc);
}
// 关闭writer
writer.close();
}
1.6 分词
Lucene中分词主要分为两个步骤:分词、过滤
- 分词:将field域中的内容一个个的分词。
过滤:将分好的词进行过滤,比如去掉标点符号、大写转小写、词的型还原(复数转单数、过去式转成现在式)、停用词过滤
停用词:单独应用没有特殊意义的词。比如的、啊、等,英文中的this is a the等等。
要分词的内容
Lucene is a Java full-text search engine.
- 分词
Lucene
is
a
Java
Full
-
text
search
engine
.过滤
去掉标点符号
Lucene
is
a
Java
Full
text
search
engine
去掉停用词
-
LuceneJava
Full
text
search
engine
大写转小写
-
lucenejava
full
text
search
engine
如下是org.apache.lucene.analysis.standard.standardAnalyzer的部分源码:
@Override protected TokenStreamComponents createComponents(final String fieldName, final Reader reader) { final StandardTokenizer src = new StandardTokenizer(getVersion(), reader); src.setMaxTokenLength(maxTokenLength); TokenStream tok = new StandardFilter(getVersion(), src); tok = new LowerCaseFilter(getVersion(), tok); tok = new StopFilter(getVersion(), tok, stopwords); return new TokenStreamComponents(src, tok) { @Override protected void setReader(final Reader reader) throws IOException { src.setMaxTokenLength(StandardAnalyzer.this.maxTokenLength); super.setReader(reader); } }; }
从一个Reader字符流开始,创建一个基于Reader的Tokenizer分词器,经过三个TokenFilter生成语汇单元Token。
同一个域中相同的语汇单元(Token)对应同一个Term(词),它记录了语汇单元的内容及所在域的域名等,还包括来该token出现的频率及位置。- 不同的域中拆分出来的相同的单词对应不同的term。
- 相同的域中拆分出来的相同的单词对应相同的term。
例如:图书信息里面,图书名称中的java和图书描述中的java对应不同的term
1.7 搜索流程
同数据库的sql一样,lucene全文检索也有固定的语法:
最基本的有比如:AND, OR, NOT 等举个例子,用户想找一个description中包括java关键字和lucene关键字的文档。
它对应的查询语句:description:java AND lucene
代码
@Test public void indexSearch() throws Exception { // 创建query对象 // 使用QueryParser搜索时,需要指定分词器,搜索时的分词器要和索引时的分词器一致 // 第一个参数:默认搜索的域的名称 QueryParser parser = new QueryParser("description", new StandardAnalyzer()); // 通过queryparser来创建query对象 // 参数:输入的lucene的查询语句(关键字一定要大写) Query query = parser.parse("description:java AND lucene"); // 创建IndexSearcher // 指定索引库的地址 File indexFile = new File("E:\\11-index\\hm19\\"); Directory directory = FSDirectory.open(indexFile); IndexReader reader = DirectoryReader.open(directory); IndexSearcher searcher = new IndexSearcher(reader); // 通过searcher来搜索索引库 // 第二个参数:指定需要显示的顶部记录的N条 TopDocs topDocs = searcher.search(query, 10); // 根据查询条件匹配出的记录总数 int count = topDocs.totalHits; System.out.println("匹配出的记录总数:" + count); // 根据查询条件匹配出的记录 ScoreDoc[] scoreDocs = topDocs.scoreDocs; for (ScoreDoc scoreDoc : scoreDocs) { // 获取文档的ID int docId = scoreDoc.doc; // 通过ID获取文档 Document doc = searcher.doc(docId); System.out.println("商品ID:" + doc.get("id")); System.out.println("商品名称:" + doc.get("name")); System.out.println("商品价格:" + doc.get("price")); System.out.println("商品图片地址:" + doc.get("pic")); System.out.println("=========================="); // System.out.println("商品描述:" + doc.get("description")); } // 关闭资源 reader.close(); }