全文检索技术Lucene

1. 全文检索与Lucene的介绍

全文检索

就是将非结构化数据中的一部分信息提取出来,重新组织,使其变得有一定的结构,然后对此有一定结构的数据进行搜索,从而达到搜索相对较快的目的。这部分从非结构化数据中提取出的然后重新组织的信息,我们称之为索引。这种先建立索引,在对索引进行搜索的过程就叫全文检索(Full-text Search)。

Lucene
Lucene是apache下的一个开放源代码的全文检索引擎工具包。提供了完整的查询引擎和索引引 擎,部分文本分析引擎。Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能。

2. 索引过程和搜索过程的介绍

索引过程包括

确定原始内容即要搜索的内容,采集文档,创建文档,分析文档,索引文档

搜索过程包括
用户通过搜索界面,创建查询,执行搜索,从索引库搜索,渲染搜索结果

  • 原始文档:指要索引和搜索的内容。原始内容包括互联网上的网页、数据库中的数据、磁盘上的文件等。
  • 创建文档对象:将原始内容创建成文档(Document),文档中包括一个一个的域(Field),域中存储内容。
  • 分析文档:对文档对象中的文档进行分词处理
  • 创建索引: 创建索引是对语汇单元索引,通过词语找文档,这种索引的结构叫倒排索引结构。
  • 创建查询:用户在创建查询的时候应该先构建一个查询对象。
  • 执行查询:根据查询语法在倒排索引词典表中分别找出对应搜索词的索引,从而找到索引所链接的文档。
  • 渲染结果:把查询结果放到页面上。

3. 配置开发环境

    <!--核心包-->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-core</artifactId>
            <version>5.3.1</version>
        </dependency>
        <!--一般分词器,适用于英文分词-->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-analyzers-common</artifactId>
            <version>5.3.1</version>
        </dependency>
        <!--中文分词器-->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-analyzers-smartcn</artifactId>
            <version>5.3.1</version>
        </dependency>

        <!--对分词索引查询解析-->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-queryparser</artifactId>
            <version>5.3.1</version>
        </dependency>
        <!--检索关键字高亮显示-->
        <dependency>
        <groupId>org.apache.lucene</groupId>
        <artifactId>lucene-highlighter</artifactId>
        <version>5.3.1</version>

        </dependency>

4. 入门程序:创建索引

  • 用maven导入Lucene的关键jar包。
  • 创建一个 IndexWriter对象,指定索引库存放位置Directory对象,在指定IndexWriterConfig对象。
  • 创建document对象。
  • 创建field对象,将field对象添加到document对象。
  • 使用indexwriter对象将document对象写入索引库,此过程进行索引创建。并将索引和document对象写入索引库。
  • 关闭IndexWriter对象。

        //创建一个标准的分析器对象
        Analyzer analyzer = new StandardAnalyzer();
        

        //创建一个directory对象保存在磁盘中
        Directory directory1 = FSDirectory.open(new File("/Users/wenshijin/Downloads/ddddd/Lucene/index").toPath());

        //基于Directory创建一个IndexWriter对象
        IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
        IndexWriter indexWriter = new IndexWriter(directory1, indexWriterConfig);

        //读取磁盘文件,为每个文件创建一个文档对象
        File dir = new File("/Users/wenshijin/Downloads/ddddd/Lucene/searchsource");
        File[] files = dir.listFiles();

        for (File f :
                files) {
            //文件名
            String fileName = f.getName();
            //文件路径
            String filePath = f.getPath();
            //文件的内容
            String fileContent = FileUtils.readFileToString(f, "utf-8");
            //文件大小
            Long fileSize = FileUtils.sizeOf(f);
            
            //创建Field,域名,域的内容,是否存储
            Field fieldName = new TextField("name",fileName,Field.Store.YES);
            Field fieldPath = new TextField("path",filePath,Field.Store.YES);
            Field fieldContent = new TextField("content",fileContent,Field.Store.YES);
            Field fieldSize = new TextField("size",fileSize+"",Field.Store.YES);

            //创建文档对象,并把field域对象存储进document对象中
            Document document = new Document();
            document.add(fieldName);
            document.add(fieldPath);
            document.add(fieldContent);
            document.add(fieldSize);

            //把文档对象写入索引库;
            indexWriter.addDocument(document);

         }
         //关闭indexWriter对象
        indexWriter.close();

5. 入门程序:查询索引

  • 创建一个Directory对象,路径是存放索引库的位置。
  • 创建一个IndexReader对象,参数是Directory对象。
  • 创建一个IndexSearch对象,参数是IndexReader对象。
  • 在创建一个TermQuery对象,指定查询的域和关键词。
  • 执行查询。
  • 返回查询结果,遍历查询结果并输出。
  • 关闭IndexReader对象。
 //创建一个Director对象,指定索引库的位置
        Directory directory = FSDirectory.open(new File("/Users/wenshijin/Downloads/ddddd/Lucene/index").toPath());
        //创建一个IndexReader对象
        IndexReader indexReader = DirectoryReader.open(directory);
        //创建一个IndexSearch对象。
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);
        //创建一个Query对象,TermQuery
        Query query = new TermQuery(new Term("content","spring"));
        //执行查询,得到一个TopDocs对象,参数:查询对象,返回最大记录数
        TopDocs topDocs = indexSearcher.search(query,10);

        System.out.println("查询总记录数:" + topDocs.totalHits);
        //遍历查询结果
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        for (ScoreDoc doc :
                scoreDocs) {
            int docId = doc.doc;
            Document document = indexSearcher.doc(docId);
            System.out.println(document.get("name"));
            System.out.println(document.get("path"));
            System.out.println(document.get("size"));
            System.out.println(document.get("content"));
            System.out.println("-------------------");
        }

        indexReader.close();

6. 用分析器查看分词效果

  • 创建一个标准分析器。
  • 创建一个TokenStream对象,存放field和分析文本。
  • 添加引用,然后把指针指向引用列表的头部。
  • 循环遍历输出数据
  • 关闭TokenStream对象
//创建一个标准的分词器
     Analyzer analyzer = new StandardAnalyzer();
     //创建一个TokenStream对象,存放field和分析文本。
     TokenStream tokenStream = analyzer.tokenStream("","The Spring Framework provides a comprehensive programming and configuration model.");
     //添加引用
     CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);
    //把指针放置在头部
     tokenStream.reset();
     //循环遍历
     while(tokenStream.incrementToken()) {
         System.out.println(charTermAttribute.toString());
     }

     //关闭对象
     tokenStream.close();

7. 中文分词器IKAnalyzer

在上文中我们就使用过Lucene自带的分词器StandardAnalyzer。会将语汇单元转成小写形式,并去除停用词及标点符号,很明显也是不适合于中文环境。所以在这里我们介绍一款中文分词器IKAnalyzer。
IKAnalyzer是一款由国人开发的分词器,暂时只能去github上下载jar包,来导入,或者把项目安装到自己的maven的本地仓库才能导入。
IKAnalyzer有自己的配置文件,在使用IKAnalyzer分词器的时候需要配置配置文件,并把扩展词典和停用词典添加到classpath下。必须是UTF-8格式

  //索引库存放路径
    Directory directory = FSDirectory.open(new File("/Users/wenshijin/Downloads/ddddd/Lucene/index").toPath());
    IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer());
    //创建一个indexwriter对象
    IndexWriter indexWriter = new IndexWriter(directory, config);


8. 索引库的维护

索引库的添加

 //索引库存放路径
    Directory directory = FSDirectory.open(new File("/Users/wenshijin/Downloads/ddddd/Lucene/index").toPath());
    IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer());
    //创建一个indexwriter对象
    IndexWriter indexWriter = new IndexWriter(directory, config);
    //创建一个Document对象
    Document document = new Document();
    //向document对象中添加域。
    //不同的document可以有不同的域,同一个document可以有相同的域。
    document.add(new TextField("filename", "新添加的文档", Field.Store.YES));
    document.add(new TextField("content", "新添加的文档的内容", Field.Store.NO));
    //LongPoint创建索引
    document.add(new LongPoint("size", 1000l));
    //StoreField存储数据
    document.add(new StoredField("size", 1000l));
    //不需要创建索引的就使用StoreField存储
    document.add(new StoredField("path", "d:/temp/1.txt"));
    //添加文档到索引库
    indexWriter.addDocument(document);
    //关闭indexwriter
    indexWriter.close();

索引库的删除

删除全部

   IndexWriter indexWriter = getIndexWriter();
    //删除全部索引
    indexWriter.deleteAll();
    //关闭indexwriter
    indexWriter.close();

指定删除

      IndexWriter indexWriter = getIndexWriter();
        //创建一个查询条件
        Query query = new TermQuery(new Term("filename", "apache"));
        //根据查询条件删除
        indexWriter.deleteDocuments(query);
        //关闭indexwriter
        indexWriter.close();

索引库的修改

 IndexWriter indexWriter = getIndexWriter();
    //创建一个Document对象
    Document document = new Document();
    //向document对象中添加域。
    //不同的document可以有不同的域,同一个document可以有相同的域。
    document.add(new TextField("filename", "要更新的文档", Field.Store.YES));
    document.add(new TextField("content", " Lucene 简介 Lucene 是一个基于 Java 的全文信息检索工具包," +
                                                       "它不是一个完整的搜索应用程序,而是为你的应用程序提供索引和搜索功能。",
                Field.Store.YES));
    indexWriter.updateDocument(new Term("content", "java"), document);
    //关闭indexWriter
    indexWriter.close();

9. 索引库的查询

对要搜索的信息创建Query查询对象,Lucene会根据Query查询对象生成最终的查询语法。可以使用Lucene提供Query子类或者使用QueryParse解析查询表达式来创建查询对象。

TermQuery(Query的子类):通过项查询,建议使用不分词的field域来进行查询。

Directory directory = FSDirectory.open(new File("/Users/wenshijin/Downloads/ddddd/Lucene/index").toPath());
    IndexReader indexReader = DirectoryReader.open(directory);
    IndexSearcher indexSearcher = new IndexSearcher(indexReader);
     //创建查询对象
    Query query = new TermQuery(new Term("content", "lucene"));
    //执行查询
    TopDocs topDocs = indexSearcher.search(query, 10);
    //共查询到的document个数
    System.out.println("查询结果总数量:" + topDocs.totalHits);
    //遍历查询结果
    for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
        Document document = indexSearcher.doc(scoreDoc.doc);
        System.out.println(document.get("filename"));
        //System.out.println(document.get("content"));
        System.out.println(document.get("path"));
        System.out.println(document.get("size"));
    }
    //关闭indexreader
    indexSearcher.getIndexReader().close();

使用queryparser查询:通过QueryParser也可以创建Query,QueryParser提供一个Parse方法,此方法可以直接根据查询语法来查询。需要导入专属依赖。

 IndexSearcher indexSearcher = getIndexSearcher();
    //创建queryparser对象
    //第一个参数默认搜索的域
    //第二个参数就是分析器对象
    QueryParser queryParser = new QueryParser("content", new IKAnalyzer());
    Query query = queryParser.parse("Lucene是java开发的");
    //执行查询
    printResult(query, indexSearcher);
   private void printResult(Query query, IndexSearcher     indexSearcher) throws Exception {
    //执行查询
    TopDocs topDocs = indexSearcher.search(query, 10);
    //共查询到的document个数
    System.out.println("查询结果总数量:" + topDocs.totalHits);
    //遍历查询结果
    for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
        Document document = indexSearcher.doc(scoreDoc.doc);
        System.out.println(document.get("filename"));
        //System.out.println(document.get("content"));
        System.out.println(document.get("path"));
        System.out.println(document.get("size"));
    }
    //关闭indexreader
    indexSearcher.getIndexReader().close();
    }

10. 总结

由于作者初步尝试用markdown来写笔记,导致排版可能不是那么好。作者会在之后的学习中改进自己的不足。
全篇文章讲述的是全文检索技术Lucene的一些入门使用,和中文分词器IKAnalyzer的使用。还有对索引库的使用。在这里并没有介绍查看索引的工具Luke。有兴趣的可以自行了解。