Lucene的普及和成功的背后是因为它的简单。
因此,你不需要深入理解Lucene的信息索引和检索工作方面的知识就可以开始使用。
Lucene提供了简单但是强大的核心API去实现全文索引和检索,你只需要掌握少数的类就能将Lucene整合到应用中。
刚接触Lucene的人可能会误认为Lucene是一个文件搜索工具、网络爬虫、或者网页搜索引擎。实际上Lucene是一个软件库,而不是一个全功能的搜索应用程序。它涉及全文索引和搜索,而且做得非常好。Lucene可以让你的应用程序隐藏起复杂的索引和搜索背后的操作,而使用简单的API处理特定的问题领域和业务规则。你可以想象Lucene就是像一个层,你的应用就在层的上面。
Lucene允许你添加索引和搜索功能到应用程序中。Lucene不关心数据的来源,Lucene可以索引和搜索任何可以转换成文本格式的数据。这意味着你可以用Lucene索引和搜索数据:远程web服务器上的网页、存储在本地文件系统的文档、简单的文本文件、Microsoft Word文档、HTML或PDF文件,或者其他任何可以从中提取文本信息的格式文件。
所有搜索引擎的核心就是索引的概念:把原始数据处理成一个高效的交叉引用查找,以便快速检索。让我们看看快速高效的索引和搜索过程。
1.索引是什么,为什么它这么重要?
假如你需要搜索大量的文件,你希望找到那些包含某个单词或词组的文件。你将如何去写一个程序实现这个功能?一个做法就是按顺序扫描每一个文件,搜索是否包含给定的单词或词组。但是这样的做法有很多缺陷的,其中最明显的就是在大量的文件存在的情况下,速度是令人无法接受的。这种情况下,索引产生了。为了搜索大量的文本,你首先要对这些文本以特定的结构存储,这种存储结构可以让你迅速的搜索,消除慢的顺序扫描的过程。这种存储结构就叫索引,将文本转换成特定结构存储的过程,就叫建立索引。
索引作为一种数据结构,允许你快速随机的访问存储在里面的词。类似于字典的目录,某个词对应到某一页,查找的时候直接定位到那一页,速度就非常快,不用一页一页的翻去查找。Lucene的索引是一种专门设计的数据结构,通常作为一组索引文件存储在文件系统上。
2.什么是搜索?
在索引中搜索关键词,找到包含关键词的文档的过程就是搜索。搜索质量通常使用准确度和召回率来描述。所谓召回率是指一次搜索结果集合中符合用户要求的数目与和用户查询相关的总数之比,而准确率是指一次搜索结果集合中符合用户要求的数目与该次搜索结果总数之比。我们也需要考虑其他有关搜索的因素,比如速度和快速搜索大量文本的能力,单个和多项查询、 短语查询、 通配符、 结果的排名和排序的支持也很重要。
3.Lucene in Action
假如我们需要索引和搜索存储在一个目录下的文件。
在我们使用Lucene进行搜索之前,我们需要先建立索引。使用的Lucene的版本是3.6。
3.1建立索引
1)创建存放索引的目录Directory
2)创建索引器配置管理类IndexWriterConfig
3)使用索引目录和配置管理类创建索引器
4)使用索引器将Document写到索引文件中
索引器类:
/** * 索引器 * @author Luxh */ public class Indexer { /** * 建立索引 * @param filePath 需要建立索引的文件的存放路径 * @throws IOException */ public static void createIndex(String filePath) throws IOException { //在当前路径下创建一个叫indexDir的目录 File indexDir = new File("./indexDir"); //创建索引目录 Directory directory = FSDirectory.open(indexDir); //创建一个分词器 Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_36); //创建索引配置器 IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LUCENE_36,analyzer); LogMergePolicy mergePolicy = new LogByteSizeMergePolicy(); //设置segment添加文档(Document)时的合并频率 //值较小,建立索引的速度就较慢 //值较大,建立索引的速度就较快,>10适合批量建立索引 mergePolicy.setMergeFactor(50); //设置segment最大合并文档(Document)数 //值较小有利于追加索引的速度 //值较大,适合批量建立索引和更快的搜索 mergePolicy.setMaxMergeDocs(5000); //启用复合式索引文件格式,合并多个segment mergePolicy.setUseCompoundFile(true); indexWriterConfig.setMergePolicy(mergePolicy); //设置索引的打开模式 indexWriterConfig.setOpenMode(OpenMode.CREATE_OR_APPEND); //创建索引器 IndexWriter indexWriter = new IndexWriter(directory,indexWriterConfig); File fileDir = new File(filePath); for(File file : fileDir.listFiles()) { //Document是Lucene的文档结构,需要索引的对象都要转换为Document Document document = new Document(); //文件名,可查询,分词,存储到索引库记录中 document.add(new Field("name",getFileName(file),Store.YES,Index.ANALYZED)); //文件路径,可查询,不分词,存储到索引库记录中 document.add(new Field("path",file.getAbsolutePath(),Store.YES,Index.NOT_ANALYZED)); //大文本内容,可查询,不存储,实际上可根据文件路径去找到真正的文本内容 //document.add(new Field("content",new FileReader(file))); //小文本内容,可以存储到索引记录库 document.add(new Field("content",getFileContent(file),Store.YES,Index.ANALYZED)); //把文档添加到索引库 indexWriter.addDocument(document); } //提交索引到磁盘上的索引库,关闭索引器 indexWriter.close(); } /** * 获取文件名 */ public static String getFileName(File file) { String fileName = ""; if(file != null) { fileName = file.getName().substring(0, file.getName().lastIndexOf(".")); } return fileName; } /** * 获取文本 * @param file */ public static String getFileContent(File file) { FileReader fr = null; BufferedReader br = null; String content = ""; try { fr = new FileReader(file); br = new BufferedReader(fr); StringBuffer sb = new StringBuffer(); String line = br.readLine(); while(null != line){ sb.append(line); line = br.readLine(); } content = sb.toString(); }catch(Exception e) { e.printStackTrace(); }finally { try { if(fr != null) fr.close(); if(br != null) br.close(); } catch (IOException e) { e.printStackTrace(); } } return content; } }
IndexWriter:索引器,负责创建和维护一条索引。
在Lucene3.6版本,只推荐使用一个构造方法IndexWriter(Directory d,IndexWriterConfig conf),其他的构造方法都已经过时。所有关于IndexWriter的配置都是通过IndexWriterConfig来进行管理。
IndexWriterConfig:索引器配置类,管理所有有关索引器的配置。只有一个构造方法IndexWriterConfig(Version matchVersion,Analyzer analyzer),构造方法中的参数matchVersion是Lucene的版本,analyzer是分词器。
接下来我们运行索引器创建索引。
public class TestIndexer { /** * 创建索引 * @throws IOException */ @Test public void testCreateIndex() throws IOException{ //存放需要建立索引的文件的目录路径 String filePath = "./fileDir"; //调用索引器的创建索引方法 Indexer.createIndex(filePath); } }
这样我们就对当前路径下fileDir中的文件创建了索引。
3.2执行搜索
在Lucene中搜索像建立索引一样简单、快速。现在,我们建立一个搜索器,搜索包含特定文本的文件。
1)使用QueryParser将查询的关键词解析成Lucene的查询对象Query。创建QueryParser的时候我们需要用到分词器,这个分词器要和前面创建索引的时候使用的分词器一致。
2)使用FSDirectory打开索引所在的目录。
3)使用IndexReader读取索引目录和使用IndexSearcher进行搜索。
4)返回搜索结果对象TopDocs。TopDocs包含搜索到结果总数和结果的集合ScoreDocs数组
5)遍历结果的集合ScoreDocs数组,根据每一个ScoreDoc的文档编号获取Document
看看搜索器的代码:
/** * 搜索器 * @author Luxh */ public class Searcher { /** * 搜索 * @param keyWord 搜索的关键词 * @param indexDir 索引目录所在路径 * @throws ParseException * @throws IOException * @return List<Document> */ public static List<Document> search(String keyWord,String indexDirPath) throws ParseException, IOException { String[] fields = {"name","content"}; //创建一个分词器,和创建索引时用的分词器要一致 Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_36); //创建查询解析器 QueryParser queryParser = new MultiFieldQueryParser(Version.LUCENE_36,fields,analyzer); //将查询关键词解析成Lucene的Query对象 Query query = queryParser.parse(keyWord); //打开索引目录 File indexDir = new File(indexDirPath); Directory directory = FSDirectory.open(indexDir); //获取访问索引的接口,进行搜索 IndexReader indexReader = IndexReader.open(directory); IndexSearcher indexSearcher = new IndexSearcher(indexReader); //TopDocs 搜索返回的结果 TopDocs topDocs = indexSearcher.search(query, 100);//只返回前100条记录 int totalCount = topDocs.totalHits; // 搜索结果总数量 System.out.println("搜索到的结果总数量为:" + totalCount); ScoreDoc[] scoreDocs = topDocs.scoreDocs; // 搜索的结果集合 List<Document> docs = new ArrayList<Document>(); for(ScoreDoc scoreDoc : scoreDocs) { //文档编号 int docID = scoreDoc.doc; //根据文档编号获取文档 Document doc = indexSearcher.doc(docID); docs.add(doc); } indexReader.close(); indexSearcher.close(); return docs; } }
接下来我们运行搜索器:
public class TestSearcher { /** * 搜索 */ @Test public void testSearch() throws IOException, ParseException{ //搜索关键词 String keyWord = "Java"; //索引目录路径 String indexDirPath = "./indexDir"; //调用搜索器进行搜索 List<Document> docs = Searcher.search(keyWord, indexDirPath); for(Document doc : docs) { System.out.println("文件名 : "+doc.get("name")); System.out.println("路径 : "+doc.get("path")); System.out.println("内容 : "+doc.get("content")); } } }
如果有包含关键词的文件,就会被搜索出来了。