综述:搜索引擎我们都不陌生,但是为什么要使用框架呢?而什么是Lucene呢?相比直接使用SQL搜索有哪些优点呢?下面逐一说明。
一、什么是Lucene
Lucene不是一个完整的全文检索引擎,而是一个全文检索引擎的 架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎。Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。说白了就是一个做索引的开源框架。Lucene只做索引和搜索工作。
二、了解数据的分类和全文检索
数据分为结构化数据和非结构化数据,我们知道SQL查询数据库的操作其实是很快的,因为数据库分有表有行和列,但是对于像word、pdf、excel等形式的没有具体书写规范的非结构化数据,在其中搜寻是很困难的。
非结构化数据的查询方法有两种:
1、顺序扫描法:比如要找内容包含某一个字符串的文件,就是一个文档一个文档的看,对于每一个文档,从头看到尾,如果此文档包含此字符串,则此文档为我们要找的文件,接着看下一个文件,直到扫描完所有的文件。如利用windows的搜索也可以搜索文件内容,只是相当的慢。
2、全文检索:将非结构化数据中的一部分信息提取出来,重新组织,使其变得有一定结构,然后对此有一定结构的数据进行搜索,从而达到搜索相对较快的目的。这部分从非结构化数据中提取出的然后重新组织的信息,我们称之索引。
例如:字典。字典的拼音表和部首检字表就相当于字典的索引,对每一个字的解释是非结构化的,如果字典没有音节表和部首检字表,在茫茫辞海中找一个字只能顺序扫描。然而字的某些信息可以提取出来进行结构化处理,比如读音,就比较结构化,分声母和韵母,分别只有几种可以一一列举,于是将读音拿出来按一定的顺序排列,每一项读音都指向此字的详细解释的页数。我们搜索时按结构化的拼音搜到读音,然后按其指向的页数,便可找到我们的非结构化数据——也即对字的解释。
这种先建立索引,再对索引进行搜索的过程就叫全文检索(Full-text Search)。
虽然创建索引的过程也是非常耗时的,但是索引一旦创建就可以多次使用,全文检索主要处理的是查询,所以耗时间创建索引是值得的。
对于数据量大、数据结构不固定的数据可采用全文检索方式搜索,比如百度、Google等搜索引擎、论坛站内搜索、电商网站站内搜索等。
关于全文检索,我们要知道:
- 只处理文本。
- 不处理语义。
- 搜索时英文不区分大小写。 结果列表有相关度排序。(查出的结果如果没有相关度排序,那么系统不知道我想要的结果在哪一页。我们在使用百度搜索时,一般不需要翻页,为什么?因为百度做了相关度排序:为每一条结果打一个分数,这条结果越符合搜索条件,得分就越高,叫做相关度得分,结果列表会按照这个分数由高到低排列,所以第1页的结果就是我们最想要的结果。)
在信息检索工具中,全文检索是最具通用性和实用性的。
三、全文检索的流程
1、采集数据:第一步肯定是获取数据,采集数据(从网站爬取或连接数据库)就是为了创建索引,创建索引需要先将采集的原始数据加工为文档,再由文档分词产生索引。文档(Document) 中包含若干个Field域。
2、创建检索:使用Lucene的相关jar包中的相关函数进行创建。
3、用户搜索:提供用户搜索功能。
4、搜索索引:把用户搜索的内容作为索引去索引库里面进行比对。
5、反馈用户:把根据用户提供的相关搜索条件的内容返回给用户。四、Demo
使用两个程序实现一个简单的demo
1、创建索引
package com.stitp.lucence; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.nio.file.Paths; 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; public class Indexer { // 写索引实例 private IndexWriter writer; /** * 构造方法 实例化IndexWriter * * @param indexDir * @throws IOException */ public Indexer(String indexDir) throws IOException { //得到索引所在目录的路径 Directory directory = FSDirectory.open(Paths.get(indexDir)); // 标准分词器 Analyzer analyzer = new StandardAnalyzer(); //保存用于创建IndexWriter的所有配置。 IndexWriterConfig iwConfig = new IndexWriterConfig(analyzer); //实例化IndexWriter writer = new IndexWriter(directory, iwConfig); } /** * 关闭写索引 * * @throws Exception * @return 索引了多少个文件 */ public void close() throws IOException { writer.close(); } public int index(String dataDir) throws Exception { File[] files = new File(dataDir).listFiles(); for (File file : files) { //索引指定文件 indexFile(file); } //返回索引了多少个文件 return writer.numDocs(); } /** * 索引指定文件 * * @param f */ private void indexFile(File f) throws Exception { //输出索引文件的路径 System.out.println("索引文件:" + f.getCanonicalPath()); //获取文档,文档里再设置每个字段 Document doc = getDocument(f); //开始写入,就是把文档写进了索引文件里去了; writer.addDocument(doc); } /** * 获取文档,文档里再设置每个字段 * * @param f * @return document */ private Document getDocument(File f) throws Exception { Document doc = new Document(); //把设置好的索引加到Document里,以便在确定被索引文档 doc.add(new TextField("contents", new FileReader(f))); //Field.Store.YES:把文件名存索引文件里,为NO就说明不需要加到索引文件里去 doc.add(new TextField("fileName", f.getName(), Field.Store.YES)); //把完整路径存在索引文件里 doc.add(new TextField("fullPath", f.getCanonicalPath(), Field.Store.YES)); return doc; } public static void main(String[] args) { //索引指定的文档路径 String indexDir = "F:\\lucene"; 被索引数据的路径 String dataDir = "F:\\lucene\\data"; Indexer indexer = null; int numIndexed = 0; //索引开始时间 long start = System.currentTimeMillis(); try { indexer = new Indexer(indexDir); numIndexed = indexer.index(dataDir); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { try { indexer.close(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } //索引结束时间 long end = System.currentTimeMillis(); System.out.println("索引:" + numIndexed + " 个文件 花费了" + (end - start) + " 毫秒"); } }
运行主函数之后:
2、全文检索测试package com.stitp.lucence; import java.nio.file.Paths; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexReader; 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.store.Directory; import org.apache.lucene.store.FSDirectory; /** * 根据索引搜索 */ public class Searcher { public static void search(String indexDir, String q) throws Exception { // 得到读取索引文件的路径 Directory dir = FSDirectory.open(Paths.get(indexDir)); // 通过dir得到的路径下的所有的文件 IndexReader reader = DirectoryReader.open(dir); // 建立索引查询器 IndexSearcher is = new IndexSearcher(reader); // 实例化分析器 Analyzer analyzer = new StandardAnalyzer(); // 建立查询解析器 /** * 第一个参数是要查询的字段; 第二个参数是分析器Analyzer */ QueryParser parser = new QueryParser("contents", analyzer); // 根据传进来的p查找 Query query = parser.parse(q); // 计算索引开始时间 long start = System.currentTimeMillis(); // 开始查询 /** * 第一个参数是通过传过来的参数来查找得到的query; 第二个参数是要出查询的行数 */ TopDocs hits = is.search(query, 10); // 计算索引结束时间 long end = System.currentTimeMillis(); System.out.println("匹配 " + q + " ,总共花费" + (end - start) + "毫秒" + "查询到" + hits.totalHits + "个记录"); // 遍历hits.scoreDocs,得到scoreDoc /** * ScoreDoc:得分文档,即得到文档 scoreDocs:代表的是topDocs这个文档数组 * * @throws Exception */ for (ScoreDoc scoreDoc : hits.scoreDocs) { Document doc = is.doc(scoreDoc.doc); System.out.println(doc.get("fullPath")); } // 关闭reader reader.close(); } public static void main(String[] args) { String indexDir = "F:\\lucene"; //我们要搜索的内容 String q = "java"; try { search(indexDir, q); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
运行结果
转载自:Lucene7.2.1系列(一)快速入门