首页 > 技术文章 > lucene实现初级搜索引擎

Denise-hzf 原文

一、系统设计

搜索引擎项目代码主要分为三个部分,第一部分是构建索引,全文检索;第二部分是输入问题,对问题进行分词、提取关键词、关键词扩展;第三部分是将搜索结果输出到GUI图形用户界面。

二、搜索引擎

搜索引擎的工作流程可以简化归结为以下四个步骤:

(1)网上抓取网页

(2)建立索引数据库

(3)在索引数据库中搜索

(4)对搜索结果进行处理和排序

 

三、全文检索

1.什么是全文检索?

   全文检索是一种将文件中所有文本与检索项匹配的文字资料检索方法。

2.全文检索流程

全文检索大体分两个过程,索引创建 (Indexing) 和搜索索引 (Search) 。

①索引创建:从结构化和非结构化数据提取信息,创建索引的过程。

②搜索索引:得到用户的查询请求,搜索创建的索引,然后返回结果。

四、利用Lucene实现索引和搜索

1.简介

Lucene是一个高性能,易于扩展的IR(Information Retrieval)Java类库,可以利用其中的Java类轻松地在应用程序中增加索引和搜索功能。Lucene完全用Java实现,具有良好的跨平台性,是Apache Jakarta项目中一个子项目。

2.lucene架构图

Lucene的架构图如下图4.1所示:

 

图4.1Lucene的架构图

3. Lucene的核心类

Lucene的核心的索引类简介如下:

IndexWriter

IndexWriter是索引过程中的核心部件。这个类建立一个新的索引和添加文档(Documents)到一个存在的索引中,它对索引只有写权限,不具有读和搜索的权限。

Directory

Directory类代表了Lucene索引的位置,它是一个抽象类,有两个子类: FSDirectory类,它将索引存储在磁盘上,并在文件系统中维护一个真实文件的列表;另一个是RAMDirectory类,它将所有索引信息都放在内存中,适用于索引较小的情况。

Analyzer

在文本被索引之前,要经过一个Analyzer的处理。在IndexWriter的构造函数中指定的Analyzer负责从被索引的文本中抽取出标志(tokens),并排除掉标志以外的部分。假如被索引的内容不是纯文本的话,应该先将它转换为纯文本。Analyzer是一个抽象类,Lucene中包含了几种它的实现,用于完成不同的功能,例如用于去掉文本中的无用词(stop word)、将标志(tokens)全部转换为小写等等。为了达到特定的目标,可以设计不同的Analyzer的实现,这样就使得Lucene具有高度的灵活性及可扩充性。

Document

一个Document代表了一些字段(fields)的集合。

Field:

在索引中的每个Document都包含了一个或更多的命名了的字段(fields),这些字段用一个称为Field的类来表示。每个字段都对应于在搜索期间能从索引中查询并提取的一段数据。

IndexSearcher

IndexSearcher用于搜索IndexWriter建立的索引。IndexSearcher以只读方式打开索引,并提供了几种搜索方法,这些方法是它的抽象父类的实现。

Term

Term是最基本的搜索单元。与Field对象类似,它也包含了一对字符串元素:名字和值。Term对象和索引过程也是相关的。在搜索期间,先构建Term对象然后连同TermQuery一起使用。

TermQuery

TermQuery类是Lucene支持的最基本的查询类。它被用来匹配含有特定值的字段的文档。

Hits

Htis类是一个简单的指向分级搜索结果的指针容器。考虑到性能的原因,Hits的实例并不从索引中装载匹配查询的所有文档只是他们的一小部分。

五、构建索引的数据库

构建索引所用到的数据来自搜狗实验室( www.sogou.com/labs/resource)的搜狐新闻数据,对其中健康(health)和娱乐(yule)两类的数据构建了索引。

 

六、结合例子代码分析具体步骤

1.本实验代码用到的lucene版本和子 jar

Lucene-core-4.0.0.jar包括了常用的文档,索引,搜索,存储等相关核心代码。

Lucene-analyzers-common-4.0.0.jar包含词法分析器,用于提取关键字。

Lucene-highlighter-4.0.0.jar将搜索出的内容高亮显示。

Lucene-queryparser-4.0.0.jar用于各种搜索,如模糊搜索,范围搜索等。

2. 使用Lucene的两个主要步骤

创建索引,通过IndexWriter对不同的文件进行索引的创建,并将其保存在索引相关文件存储的位置中。

通过索引查询关键字相关文档。

3.代码具体分析

定义词法分析器

 

确定索引文件存储的位置

 

创建IndexWriter,进行索引文件的写入

 

内容提取,进行索引的存储

 

关闭查询器

 

 4.全部代码

package lucene;

public class Article {  
  
    private Integer id;  
    private String title;  
    private String content;  
      
    public Article() {  
        super();  
    }  
    public Article(Integer id, String title, String content) {  
        super();  
        this.id = id;  
        this.title = title;  
        this.content = content;  
    }  
    public synchronized Integer getId() {  
        return id;  
    }  
    public synchronized void setId(Integer id) {  
        this.id = id;  
    }  
    public synchronized String getTitle() {  
        return title;  
    }  
    public synchronized void setTitle(String title) {  
        this.title = title;  
    }  
    public synchronized String getContent() {  
        return content;  
    }  
    public synchronized void setContent(String content) {  
        this.content = content;  
    }  
    @Override  
    public String toString() {  
        return "Article [id=" + id + ", title=" + title + ", content=" + content + "]";  
    }     
}  
package lucene;
/*
 * 环境:lucene-3.6.0
 */
import java.io.File;  
import java.io.IOException;  
import java.util.ArrayList;  
import java.util.HashSet;
import java.util.List;  

import org.apache.lucene.analysis.Analyzer;  
import org.apache.lucene.document.Document;  
import org.apache.lucene.index.CorruptIndexException;  
import org.apache.lucene.queryParser.ParseException;  
import org.apache.lucene.queryParser.QueryParser;  
import org.apache.lucene.search.IndexSearcher;  
import org.apache.lucene.search.Query;  
import org.apache.lucene.search.ScoreDoc;  
import org.apache.lucene.search.Sort;  
import org.apache.lucene.search.TopDocs;  
import org.apache.lucene.store.Directory;  
import org.apache.lucene.store.SimpleFSDirectory;  
import org.apache.lucene.util.Version;  
import org.wltea.analyzer.lucene.IKAnalyzer;  

import lucene.Article;  
  
/** 
 * Lucene 检索各种索引的实现方式总结 
 * @author Administrator 
 * 
 */  
public class lucene_Search {  
      
    //public static final String INDEX_DIR_PATH = "indexDir1";  
    public static final String INDEX_DIR_PATH = "indexDir"; 
    /* 创建简单中文分析器 创建索引使用的分词器必须和查询时候使用的分词器一样,否则查询不到想要的结果 */  
    private Analyzer analyzer = null;  
    // 索引保存目录  
    private File indexFile = null;  
    //目录对象,因为操作索引文件都要用到它,所以定义为全局变量  
    private Directory directory = null;  
    //索引搜索对象  
    private IndexSearcher indexSearcher;  
      
    /** 
     * 初始化方法 
     * @throws IOException  
     */  
    public void init() throws IOException{  
        analyzer = new IKAnalyzer(true);  
        indexFile = new File(INDEX_DIR_PATH);  
        directory = new SimpleFSDirectory(indexFile);  
        indexSearcher = new IndexSearcher(directory);         
        System.out.println("*****************搜索索引程序初始化成功**********************");  
    }  
      
    /** 
     * 根据传递的结果集 封装成集合后显示出来 
     * @param scoreDocs 
     * @return 
     * @throws IOException  
     * @throws CorruptIndexException  
     */  
    public List<String> showResult(ScoreDoc[] scoreDocs) throws CorruptIndexException, IOException{  
        List<Article> articles = new ArrayList<Article>();  
        for (int i = 0; i < scoreDocs.length; i++) {  
            int doc = scoreDocs[i].doc;//索引id  
            Document document = indexSearcher.doc(doc);  
              
            Article article = new Article();  
            if (document.get("id") == null) {  
                System.out.println("id为空");  
            } else {  
                article.setId(Integer.parseInt(document.get("id")));  
                article.setTitle(document.get("title"));  
                article.setContent(document.get("content"));  
                articles.add(article);  
            }             
        }  
        List<String> resultList = new ArrayList<String>();
        
        if(articles.size()!=0){  
            for (Article article : articles) {  
                //System.out.println(article); 
                resultList.add(article.getContent());
            }  
        }else{  
            System.out.println("没有查到记录。");  
        }  
        return resultList;
    }  
  
    /** 
     * 通过QueryParser绑定单个字段来检索索引记录 
     * @param keyword 
     * @return 
     * @throws ParseException  
     * @throws IOException  
     * @throws CorruptIndexException  
     */  
    public List<String> searchByQueryParser(String keyword) throws ParseException, CorruptIndexException, IOException{  
        System.out.println("*****************通过QueryParser来检索索引记录**********************");  
        QueryParser queryParser = new QueryParser(Version.LUCENE_36, "content", analyzer);  
        Query query = queryParser.parse(keyword);  
        // public TopFieldDocs search(Query query, int n, Sort sort)  
        // 参数分别表示 Query查询对象,返回的查询数目,排序对象  
        TopDocs topDocs = indexSearcher.search(query, 50, new Sort());  
        List<String> resultList = showResult(topDocs.scoreDocs); 
        return resultList;
    }
  
    
    /** 
     * 销毁当前的操作类的实现,主要关闭资源的连接 
     */ 
    public void destory() 
                        throws IOException{  
        analyzer.close();  
        directory.close();  
        System.out.println("*****************成功关闭资源连接**********************");  
    } 
    

    public static List<String> lucene_searchIndex(String question)
            throws IOException, ParseException {  
        lucene_Search luceneInstance = new lucene_Search();
        luceneInstance.init();
        List<String> resultList = luceneInstance.searchByQueryParser(question);
        luceneInstance.destory();
        HashSet<String> resultset = new HashSet<String>();
        for(int i=0;i<resultList.size();i++){
            resultset.add(resultList.get(i));
        }
        resultList.clear();
        for(String data:resultset){
            resultList.add(data);            
        }
        return resultList;
    }
}
package lucene;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.LockObtainFailedException;
import org.apache.lucene.store.SimpleFSDirectory;
import org.apache.lucene.util.Version;
import org.wltea.analyzer.lucene.IKAnalyzer;

import Utils.readBigData;

public class lucene_BuildIndex {

   // public static final String INDEX_DIR_PATH = "indexDir1";  
    public static final String INDEX_DIR_PATH = "indexDir3";  
    /* 创建简单中文分析器 创建索引使用的分词器必须和查询时候使用的分词器一样,否则查询不到想要的结果 */  
    private Analyzer analyzer = null;  
    // 索引保存目录  
    private File indexFile = null;  
    //目录对象,因为操作索引文件都要用到它,所以定义为全局变量  
    private Directory directory = null;  
    //创建IndexWriter索引写入器  
    IndexWriterConfig indexWriterConfig = null;  
      
    SimpleDateFormat simpleDateFormat = null;  

    /** 
     * 获得指定格式的时间字符串 
     * @return 
     */  
    public String getDate(){  
        return simpleDateFormat.format(new Date());  
    }  
   
    /** 
     * 初始化方法 
     * @throws IOException  
     */  
    public void init() throws IOException{  
        analyzer = new IKAnalyzer(true);  
        indexFile = new File(INDEX_DIR_PATH);  
        directory = new SimpleFSDirectory(indexFile);  
          
        simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
        System.out.println("*****************倒排索引程序初始化成功**********************");  
    }  
      
    /** 
     * 将article对象的属性全部封装成document对象,重构之后方便创建索引 
     * @param article 
     * @return 
     */  
    public Document createDocument(Article article){  
        Document document = new Document();  
        document.add(new Field("id", article.getId().toString(),
                Field.Store.YES, Field.Index.NOT_ANALYZED));          
        document.add(new Field("content", article.getContent().toString(),
                Field.Store.YES, Field.Index.ANALYZED));  
        //document.add(new Field("title", article.getTitle().toString(),Field.Store.YES, Field.Index.ANALYZED));  
          
        return document;  
    }  
  
    /** 
     * 为了如实反映操作索引文件之后的效果,每次操作之后查询索引目录下所有的索引内容 
     * @throws IOException  
     * @throws CorruptIndexException  
     */  
    public void openIndexFile() 
                            throws CorruptIndexException, IOException{  
        System.out.println("*****************读取索引开始**********************");  
        IndexReader indexReader = IndexReader.open(directory);  
        int docLength = indexReader.maxDoc();  
        for (int i = 0; i < docLength; i++) {  
            Document doc = indexReader.document(i);  
            Article article = new Article();  
            if (doc.get("id") == null) {  
                System.out.println("id为空");  
            } else {  
                article.setId(Integer.parseInt(doc.get("id")));  
                article.setTitle(doc.get("title"));  
                article.setContent(doc.get("content"));  
            }  
            //System.out.println(article);  
        }  
        System.out.println("*****************读取索引结束**********************
");  
    }  
      
    /** 
     * 创建索引到索引文件中 
     * @param article 
     * @throws IOException  
     * @throws LockObtainFailedException  
     * @throws CorruptIndexException  
     */  
    public void createIndex(Article article) 
                            throws CorruptIndexException, LockObtainFailedException, IOException{  
        indexWriterConfig = new IndexWriterConfig(Version.LUCENE_36, analyzer);  
        IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig);  
        indexWriter.addDocument(createDocument(article));  
          
        indexWriter.close();  
        //System.out.println("[ " + getDate() + " ] Lucene写入索引到 [" + indexFile.getAbsolutePath() + "] 成功。");  
    }  
     
    
    /** 
     * 根据文件中的id删除对应的索引文件 
     * @param contentId 
     * @throws IOException  
     * @throws ParseException  
     */  
    public void deleteIndex(String contentId) 
                            throws IOException, ParseException{  
        //判断索引文件目录内容是否有索引,有返回true ,没有返回false  
        if(IndexReader.indexExists(directory)){   
            indexWriterConfig = new IndexWriterConfig(Version.LUCENE_36, analyzer);  
            IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig);  
            //封装term去检索字段名为id ,具体值为contentId的记录,如果存在就会删除,否则什么都不做  
            indexWriter.deleteDocuments(new Term("id",contentId));  
            /* 
            QueryParser queryParser = new QueryParser(Version.LUCENE_36, "id", analyzer); 
            Query query = queryParser.parse(contentId); 
            indexWriter.deleteDocuments(query); 
            */  
            indexWriter.close();  
            System.out.println("[ " + getDate() + " ] Lucene删除索引到 [" + indexFile.getAbsolutePath() + "] 成功。");  
        }else{  
            throw new IOException("[ " + getDate() + " ] Lucene删除索引失败,在 " + indexFile.getAbsolutePath() + "目录中没有找到索引文件。" );  
        }  
    }  
        
    /** 
     * 由于实际的内容修改,所以索引文件也要跟着修改 ,具体实现方式就是先删除索引,然后重新添加 
     * @param article 
     * @throws IOException  
     * @throws ParseException  
     */  
    public void updateIndex(Article article) 
                            throws IOException, ParseException{  
        deleteIndex(article.getId().toString());  
        createIndex(article);  
        System.out.println("updateIndex函数执行完毕");
    } 
     
    /** 
     * 销毁当前的操作类的实现,主要关闭资源的连接 
     *  
     * @throws IOException  
     */  
    public void destory() 
                        throws IOException{  
        analyzer.close();  
        directory.close();  
        System.out.println("*****************成功关闭资源连接**********************");  
    }  
     
    public static void main(String[] args) throws IOException, ParseException {  
        String path = "D:\#我的文件夹\自然语言处理\问答系统\爬取网页\1_2文档切分成句子.txt";
        List<String> information = readBigData.readTxtByStringBuffer(path);
        lucene_BuildIndex luceneInstance = new lucene_BuildIndex();      
           luceneInstance.init();//初始化            
        for(int i=0;i<information.size();i++){
            Article article = new Article(i,null,information.get(i));
            luceneInstance.createIndex(article);  
        }       
        luceneInstance.openIndexFile();           
        luceneInstance.destory();//销毁  
        
    }  
}

 

推荐阅读