《Lucene由潜入深教程系列》之Lucene入门实例

Posted 嗣金杂谈

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《Lucene由潜入深教程系列》之Lucene入门实例相关的知识,希望对你有一定的参考价值。

一、Lucene简介

        最初Lucene是 Apahce软件基金会Jakarta项目组的一个子项目,它是一个完全开放源码的全文检索工具包。Lucene 是一个基于 Java 的全文信息检索工具包,它不是一个完整的搜索应用程序,而是为你的应用程序提供索引和搜索功能。Lucene的其最初阶段是使用JAVA开发的,不过由于它的强大,逐渐被翻译成了多种语言,如C++、C#、Perl等。目前已经有很多应用程序的搜索功能是基于 Lucene 的,比如 Eclipse 的帮助系统的搜索功能,Nutch(一个WebCrawler工具)、Hadoop(一个基于Lucene的分布式计算平台)。


搜索应用程序和 Lucene 之间的关系图

二、入门实例

    1、实例说明

        首先,将引入一个文件,利用Lucene来查找其中的关键字,最后使用普通IO流的方式查找,并对两者的查找效率进行比较。

  2、开发思路

    a、对要进行查找的文档进行预处理

        在该例中,主要处理全角标点与半角标点的转换。

    b、将大文档切分成多个小文档

        这一步主要是为了更好的展示Lucene的一些功能,将文档切分为多个小文档,并给每一个文档一个唯一的ID号。

    c、建立关键字索引

    d、通过索引查找

3、编码实现

    a、引入Lucene的Maven依赖

        

<dependency>
  <groupId>org.apache.lucene</groupId>
  <artifactId>lucene-core</artifactId>
  <version>6.5.1</version>
</dependency>
<!-- 中文分词器 SmartChineseAnalyzer -->
<dependency>
  <groupId>org.apache.lucene</groupId>
  <artifactId>lucene-analyzers-smartcn</artifactId>
  <version>6.5.1</version>
</dependency>
  <dependency>
      <groupId>org.apache.lucene</groupId>
      <artifactId>lucene-queryparser</artifactId>
      <version>6.5.1</version>
  </dependency>

b、创建一个StringUtils工具类实现全角标点与半角标点的转换

/**
* String工具类
* Created by Administrator on 2017/5/7.
*/
public class StringUtils {
/**
    * 全角/半角转换
    * @param line
    * @return
    */
   public static String replace(String line){
HashMap map = new HashMap();
       map.put(",",",");
       map.put("。",".");
       map.put("(","(");
       map.put(")",")");
       map.put("|","|");
       map.put("《","<");
       map.put("》",">");
       map.put("【","[");
       map.put("】","]");
       map.put("?","?");
       map.put("“","\"");
       map.put("”","\"");
       map.put(":",":");
       map.put("、",",");
       map.put("——","-");
       map.put("—","-");
       map.put("~","~");
       map.put("!","!");

       int length = line.length();
       for (int i=0;i<length;i++){
//逐个取得长度为1的String,作为查询条件
           String charat = line.substring(i,i+1);
           //判断Hashmap的key里面是否在出现该String
           if (map.containsKey(charat)){
line = line.replace(charat,(String)map.get(charat));
           }
}
return line;
   }
}

c、创建一个文档工具类FileUtils,实现全角与半角的转换和文件的切割

public class FileUtils {
/**
    * 将文件中的全角符转换成半角符
    * @param file  原文件
    * @param destFile  目标文件
    * @return
    * @throws IOException
    */
   public static File charactorProcess(File file,String destFile) throws IOException {
//创建一个输出流,用于写新文件
       BufferedWriter writer = new BufferedWriter(new FileWriter(destFile));
       //创建一个输入流,用于读取文件
       BufferedReader reader = new BufferedReader(new FileReader(file));
       String line = reader.readLine();
       while (line!=null){
//替换所有的全角
           String newline = StringUtils.replace(line);
           writer.write(newline);
           //写入行分隔符
           writer.newLine();
           line = reader.readLine();
       }
//关闭输入输出流,将缓冲区数据写入文件
       reader.close();
       writer.close();

       return new File(destFile);
   }

/**
    * 文件切割
    * @param file 待分割的文件
    * @param outputPath 分隔文件路径
    * @param max_szie 每个文件最大字节大小(byte)
    *
    */
   public static void splitToSmallFiles(File file,String outputPath,int max_szie) throws IOException {
//文件计数器
       int filePointer = 0;

       BufferedWriter writer = null;
       BufferedReader reader = new BufferedReader(new FileReader(file));

       StringBuffer buffer = new StringBuffer();

       String line = reader.readLine();
       while (line!=null){
//如果读取字符串不为空,则将字符串加入缓冲区,并在每行字符串后面加上回车换行
           buffer.append(line).append("\r\n");
           //判断当前Buffer内容是否超过最大长度
           if (buffer.toString().getBytes().length>max_szie){
//如果达到最大长度,则将缓冲区内数据写入文件
               writer = new BufferedWriter(new FileWriter(outputPath+"/output"+filePointer+".txt"));
               writer.write(buffer.toString());
               writer.close();
               filePointer++;
               buffer = new StringBuffer();
           }
line = reader.readLine();
       }
//文件读取完毕,直接将缓冲区数据写入文件
       writer = new BufferedWriter(new FileWriter(outputPath+"/output"+filePointer+".txt"));
       writer.write(buffer.toString());
       writer.close();
   }
}

d、创建文件处理类FileProcessor

public class FileProcessor {
/**
    * 文件分割处理
    * @param file  需要做处理的源文件
    * @param outputDir 文件输出路径
    * @param max_size  每个文件最大大小
    */
   public static void process(File file,String outputDir,int max_size){
try {
FileUtils.splitToSmallFiles(file,outputDir,max_size);
       } catch (IOException e) {
e.printStackTrace();
       }
}
}

e、执行Main测试:

public static void main( String[] args )
{
String inputFile = "book.txt";
   String outputDir = "F:\\work\\LunceneDemo\\test\\";

   if(!new File(outputDir).exists()){
new File(outputDir).mkdir();
   }
FileProcessor fileProcessor = new FileProcessor();
   fileProcessor.process(new File(inputFile),outputDir,1024);
}

此时在F:\work\LunceneDemo\test看到被分割的文件。

f、创建索引类:IndexProcesser

public class IndexProcesser {
private static String INDEX_STORE_PATH = "F:\\work\\LunceneDemo\\index";

   /**
    * 创建索引
    * @param inputDir
    */
   public void createIndex(String inputDir){
try {
//创建IndexWriterConfig
           IndexWriterConfig indexWriterConfig = new IndexWriterConfig(new
       
SmartChineseAnalyzer());
           //创建索引目录
           Directory dir = FSDirectory.open(Paths.get(INDEX_STORE_PATH));
          // 创建IndexWriter
           IndexWriter writer = new IndexWriter(dir,indexWriterConfig);

           File fileDir = new File(inputDir);
           //取得所有需要建立索引的文件数组
           File[] files = fileDir.listFiles();
           for(int i=0;i<files.length;i++){
String fileName = files[i].getName();
               //判断文件是否为txt文件
               if(fileName.substring(fileName.lastIndexOf(".")).equals(".txt")){
//创建Document
                   Document document = new Document();
                   //为文件名创建一个Field
                   StringField stringField = new StringField("filename",files[i].getName(), Field.Store.YES);
                   document.add(stringField);
                   //对于内容只索引不存储
                   TextField contentField = new TextField("content",
                                 loadFileToString(files[i]),Field.Store.NO);
                   document.add(contentField);

                   //把Document加入IndexWriter
                   writer.addDocument(document);
               }
}
writer.close();
       } catch (IOException e) {
e.printStackTrace();
       }
}

/**
    * 从文件中读取内容
    * @param file
    * @return
    */
   public String loadFileToString(File file){
try {
BufferedReader reader = new BufferedReader(new FileReader(file));
           StringBuffer buffer = new StringBuffer();
           String line = reader.readLine();
           while(line!=null){
buffer.append(line);
               line = reader.readLine();
           }
reader.close();
           return  buffer.toString();
       } catch (IOException e) {
e.printStackTrace();
           return  null;
       }
}
}

g、写一个Main方法执行索引的创建

public static void main(String[] args) {
IndexProcesser processer = new IndexProcesser();
   processer.createIndex("F:\\work\\LunceneDemo\\test");
}

此时在F:\work\LunceneDemo\test目录下,生成索引文件

h、创建一个检索类Search,检索要查找的关键字,包含两种检索方式,一种是通过Lucene索引来检索,另一种方式,直接通过IO流读取文件来检索。

public class Search {
private static String INDEX_STORE_PATH = "F:\\work\\LunceneDemo\\index";

   /**
    * 搜索
    * @param searchType 代表要搜索的Filed
    * @param searchKey 关键字
    */
   public void IndexSearch(String searchType,String searchKey) throws IOException {
System.out.println("==========使用索引方式搜索===========");
       IndexReader reader=null;
       try {
Directory directory = FSDirectory.open(Paths.get(INDEX_STORE_PATH));
           if (directory==null) {
System.out.println("The Lucene index is not exist");
               return;
           }
reader = DirectoryReader.open(directory);
           System.out.println("max num:" + reader.maxDoc());
           System.out.println("index num:" + reader.numDocs());
           //创建搜索器
           IndexSearcher searcher = new IndexSearcher(reader);
           /*//创建搜索单元
           Term term = new Term(searchType,searchKey);
           //由Term生成一个Query
           Query query = new TermQuery(term);*/

           //搜索开始时间
           Date beginTime = new Date();
          /* //使用query进行查询,1000为限制返回的结果
           TopDocs topDocs = searcher.search(query,1000);
           //当有检索到结果时进入循环,打印出文档路径
           for(int i=0;i<topDocs.totalHits;i++){
               Document docHit = searcher.doc(doc.scoreDocs[i].doc);
               System.out.println(docHit.get("fileName"));
           }*/
           //TopDocs topDocs = searcher.search(query,1000);
           QueryParser parse = new QueryParser(searchType, new SmartChineseAnalyzer());
           Query query = parse.parse(searchKey);
           TopDocs topDocs = searcher.search(query, 1000);
           ScoreDoc[] docs = topDocs.scoreDocs;
           // 碰撞结果
           if(null == docs || docs.length == 0) {
System.out.println("No results for this query.");
               return;
           }
System.out.println("find "+topDocs.totalHits+" file matches." );
           for (ScoreDoc scoreDoc : docs) {
int docID = scoreDoc.doc;
               float score = scoreDoc.score;
               Document document = searcher.doc(docID);
               System.out.println("score: "+score);
               System.out.println("find matches in" +document.get("filename"));
           }
Date endTime = new Date();
           long timeOfSearch = endTime.getTime()-beginTime.getTime();
           System.out.println("使用索引方式搜索总耗时 "+timeOfSearch+" ms");
       }catch (Exception e){
e.printStackTrace();
       }finally {
if(reader!=null) reader.close();
       }
}

public void stringSearch(String keyword,String searchDir) throws IOException {
System.out.println("==========使用字符串匹配方式搜索===========");

       File filesDir = new File(searchDir);
       File[] files = filesDir.listFiles();

       //保存文件名和匹配的次数
       Map map = new LinkedHashMap();

       Date beginTime = new Date();
       for(int i=0;i<files.length;i++){
//初始化匹配次数
           int hits = 0;
           try {
BufferedReader reader = new BufferedReader(new FileReader(files[i]));
               StringBuffer buffer = new StringBuffer();
               String line = reader.readLine();
               while (line!=null){
buffer.append(line);
                   line = reader.readLine();
               }
reader.close();

               String stringToSearch = buffer.toString();

               int fromIndex = keyword.length();
               while((fromIndex=stringToSearch.indexOf(keyword,fromIndex+keyword.length()))!=-1){
hits++;
               }
map.put(files[i].getName(),new Integer(hits));
           } catch (FileNotFoundException e) {
e.printStackTrace();
           }
}
Iterator it = map.keySet().iterator();
       while(it.hasNext()){
String fileName = (String) it.next();
           Integer hits = (Integer) map.get(fileName);
           System.out.println("find "+hits.intValue()+" matches in "+fileName);
       }

Date endTimes = new Date();
       //得到搜索耗费时间
       long timeOfSearch = endTimes.getTime()-beginTime.getTime();
       System.out.println("使用字符串匹配方式搜索总耗时 "+timeOfSearch+" ms");
   }
}

i、执行测试,并对两种实现所花费的时间做对比。

public static void main(String[] args) throws IOException {
Search search = new Search();
   search.IndexSearch("content","丁义珍");
   System.out.println();
   search.stringSearch("丁义珍","F:\\work\\LunceneDemo\\test");
}

  三、小结

        以上主要通过对一个文档的处理,演示了如何使用Lucene来从文档中检索关键字,关于具体Lucene的API使用细节,后续章节再给大家讲解。在使用Lucene从文档中检索关键字时,具体步骤如下:

(1)     文档预处理

(2)    对要处理的文件内容建立索引

(3)    构建查询对象

(4)    在索引中查找


以上是关于《Lucene由潜入深教程系列》之Lucene入门实例的主要内容,如果未能解决你的问题,请参考以下文章

LuceneApache Lucene全文检索引擎架构之入门实战1

全文检索工具Lucene入门教程

Lucene4.8教程之二索引

Lucene系列:LuceneUtils之CRUD

Elasticsearch 全教程--入门

Lucene系列:LuceneUtils之同步分页