首页 > 代码库 > 全文检索技术---Lucene

全文检索技术---Lucene

1       Lucene介绍

1.1   什么是Lucene

Lucene是apache下的一个开源的全文检索引擎工具包。它为软件开发人员提供一个简单易用的工具包(类库),以方便的在目标系统中实现全文检索的功能。

1.2   全文检索的应用场景

1.2.1  搜索引擎

技术分享

 

©注意:

Lucene和搜索引擎是不同的,Lucene是一套用java或其它语言写的全文检索的工具包。它为应用程序提供了很多个api接口去调用,可以简单理解为是一套实现全文检索的类库。搜索引擎是一个全文检索系统,它是一个单独运行的软件系统。

 

1.2.2  站内搜索(关注)

技术分享

 

1.3  全文检索定义

  全文检索首先将要查询的目标文档中的词提取出来,组成索引,通过查询索引达到搜索目标文档的目的。这种先建立索引,再对索引进行搜索的过程就叫全文检索(Full-text Search

2       Lucene实现全文检索的流程

技术分享

全文检索的流程分为两大部分:索引流程、搜索流程。

  • 索引流程:即采集数据à构建文档对象à分析文档(分词)à创建索引。
  • 搜索流程:即用户通过搜索界面à创建查询à执行搜索,搜索器从索引库搜索à渲染搜索结果。

3       入门程序

  3.1.1  第一步:添加jar包

入门程序只需要添加以下jar包:

  1.  mysql5.1驱动包:mysql-connector-java-5.1.7-bin.jar
  2.  核心包:lucene-core-4.10.3.jar
  3.  分析器通用包:lucene-analyzers-common-4.10.3.jar
  4.  查询解析器包:lucene-queryparser-4.10.3.jar

   3.1.2  PO,DAO以及测试代码

      技术分享

 

技术分享
 1 package cn.xjy.po ; 2  3 public class Book { 4      5     // 图书ID 6     private Integer    id ; 7     // 图书名称 8     private String    name ; 9     // 图书价格10     private Float    price ;11     // 图书图片12     private String    pic ;13     // 图书描述14     private String    description ;15     16     public Book() {}17     18     public Book(Integer id, String name, Float price, String pic, String description) {19         super() ;20         this.id = id ;21         this.name = name ;22         this.price = price ;23         this.pic = pic ;24         this.description = description ;25     }26     27     public Integer getId() {28         return id ;29     }30     31     public void setId(Integer id) {32         this.id = id ;33     }34     35     public String getName() {36         return name ;37     }38     39     public void setName(String name) {40         this.name = name ;41     }42     43     public Float getPrice() {44         return price ;45     }46     47     public void setPrice(Float price) {48         this.price = price ;49     }50     51     public String getPic() {52         return pic ;53     }54     55     public void setPic(String pic) {56         this.pic = pic ;57     }58     59     public String getDescription() {60         return description ;61     }62     63     public void setDescription(String description) {64         this.description = description ;65     }66     67     @Override68     public String toString() {69         return "Book [id=" + id + ", name=" + name + ", price=" + price + ", pic=" + pic70                 + ", description=" + description + "]" ;71     }72     73 }
View Code

     

技术分享
 1 package cn.xjy.dao ; 2  3 import java.sql.Connection ; 4 import java.sql.DriverManager ; 5 import java.sql.PreparedStatement ; 6 import java.sql.ResultSet ; 7 import java.util.ArrayList ; 8 import java.util.List ; 9 import cn.xjy.po.Book ;10 11 public class BookDaoImpl implements BookDao {12     13     @Override14     public List<Book> getBooks() {15         List<Book> books = new ArrayList<Book>() ;16         17         try {18             Class.forName("com.mysql.jdbc.Driver") ;19             Connection con = DriverManager.getConnection("jdbc:mysql:///luncene", "root", "root") ;20             PreparedStatement statement = con.prepareStatement("select * from book") ;21             ResultSet resultSet = statement.executeQuery() ;22             while (resultSet.next()) {23                 Book book = new Book(resultSet.getInt("id"), resultSet.getString("name"),24                         resultSet.getFloat("price"), resultSet.getString("pic"),25                         resultSet.getString("description")) ;26                 books.add(book) ;27             }28         } catch (Exception e) {29             e.printStackTrace() ;30         }31         32         return books ;33     }34     35 }
View Code

 

 

技术分享
  1 package cn.xjy.lucene ;  2   3 import java.io.File ;  4 import java.util.ArrayList ;  5 import java.util.List ;  6 import org.apache.lucene.analysis.Analyzer ;  7 import org.apache.lucene.analysis.standard.StandardAnalyzer ;  8 import org.apache.lucene.document.Document ;  9 import org.apache.lucene.document.Field ; 10 import org.apache.lucene.document.Field.Store ; 11 import org.apache.lucene.document.FloatField ; 12 import org.apache.lucene.document.IntField ; 13 import org.apache.lucene.document.TextField ; 14 import org.apache.lucene.index.DirectoryReader ; 15 import org.apache.lucene.index.IndexReader ; 16 import org.apache.lucene.index.IndexWriter ; 17 import org.apache.lucene.index.IndexWriterConfig ; 18 import org.apache.lucene.index.Term ; 19 import org.apache.lucene.queryparser.classic.QueryParser ; 20 import org.apache.lucene.search.BooleanClause.Occur ; 21 import org.apache.lucene.search.BooleanQuery ; 22 import org.apache.lucene.search.IndexSearcher ; 23 import org.apache.lucene.search.NumericRangeQuery ; 24 import org.apache.lucene.search.Query ; 25 import org.apache.lucene.search.ScoreDoc ; 26 import org.apache.lucene.search.TermQuery ; 27 import org.apache.lucene.search.TopDocs ; 28 import org.apache.lucene.store.Directory ; 29 import org.apache.lucene.store.FSDirectory ; 30 import org.apache.lucene.util.Version ; 31 import org.wltea.analyzer.lucene.IKAnalyzer ; 32 import cn.xjy.dao.BookDao ; 33 import cn.xjy.dao.BookDaoImpl ; 34 import cn.xjy.po.Book ; 35  36 public class TestLucene { 37      38     /** 39      * 创建索引库 40      * @throws Exception 41      */ 42     public void lucene() throws Exception { 43         BookDao bookDao = new BookDaoImpl() ; 44         List<Book> books = bookDao.getBooks() ; 45         // 采集数据的目的是为了索引,在索引前需要将原始内容创建成文档(Document), 46         // 文档(Document)中包括一个一个的域(Field)。 47          48         // 1.创建document集合对象 49         List<Document> documents = new ArrayList<Document>() ; 50          51         // 2.循环遍历数据集,根据需求创建不同的filed,添加到对应的document对象中 52         Document document = null ; 53         for (Book book : books) { 54             document = new Document() ; 55             Field id = new IntField("id", book.getId(), Store.YES) ; 56             Field name = new TextField("name", book.getName(), Store.YES) ; 57              58             if (book.getId()==4) 59                 name.setBoost(100f) ;// 设置权重.值越大搜索越靠前 60             Field price = new FloatField("price", book.getPrice(), Store.YES) ; 61             Field pic = new TextField("pic", book.getPic(), Store.YES) ; 62             Field description = new TextField("description", book.getDescription(), Store.NO) ; 63              64             document.add(id) ; 65             document.add(name) ; 66             document.add(price) ; 67             document.add(pic) ; 68             document.add(description) ; 69             documents.add(document) ; 70         } 71          72         // 3.把每个document对象添加到document集合中 73          74         // 4.分析文档,对文档中的内容记性分词,实例化分析器对象,首先创建索引目录 75         Analyzer analyzer = new StandardAnalyzer() ; 76         Directory directory = FSDirectory.open(new File("src/index")) ; 77          78         // 5.创建indexWriterConfig对象 79         IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, analyzer) ; 80          81         // 6.创建indexWriter对象 82         IndexWriter writer = new IndexWriter(directory, config) ; 83          84         // 7.通过indexWriter对象,添加文档对象,写入索引库的过程 85         for (Document doc : documents) { 86             writer.addDocument(doc) ; 87         } 88          89         // 8.关闭indexWriter流 90         writer.close() ; 91          92     } 93      94     /** 95      * 创建索引库,可解析中文 96      * @throws Exception 97      */ 98     public void luceneCN() throws Exception { 99         BookDao bookDao = new BookDaoImpl() ;100         List<Book> books = bookDao.getBooks() ;101         // 采集数据的目的是为了索引,在索引前需要将原始内容创建成文档(Document),102         // 文档(Document)中包括一个一个的域(Field)。103         104         // 1.创建document集合对象105         List<Document> documents = new ArrayList<Document>() ;106         107         // 2.循环遍历数据集,根据需求创建不同的filed,添加到对应的document对象中108         Document document = null ;109         for (Book book : books) {110             document = new Document() ;111             Field id = new IntField("id", book.getId(), Store.YES) ;112             Field name = new TextField("name", book.getName(), Store.YES) ;113             114             if (book.getId()==4)115                 name.setBoost(100f) ;// 设置权重.值越大搜索越靠前116             Field price = new FloatField("price", book.getPrice(), Store.YES) ;117             Field pic = new TextField("pic", book.getPic(), Store.YES) ;118             Field description = new TextField("description", book.getDescription(), Store.YES) ;119             120             document.add(id) ;121             document.add(name) ;122             document.add(price) ;123             document.add(pic) ;124             document.add(description) ;125             documents.add(document) ;126         }127         128         // 3.把每个document对象添加到document集合中129         130         // 4.分析文档,对文档中的内容记性分词,实例化分析器对象,首先创建索引目录131         Analyzer analyzer = new IKAnalyzer();132         Directory directory = FSDirectory.open(new File("src/index")) ;133         134         // 5.创建indexWriterConfig对象135         IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, analyzer) ;136         137         // 6.创建indexWriter对象138         IndexWriter writer = new IndexWriter(directory, config) ;139         140         // 7.通过indexWriter对象,添加文档对象,写入索引库的过程141         for (Document doc : documents) {142             writer.addDocument(doc) ;143         }144         145         // 8.关闭indexWriter流146         writer.close() ;147         148     }149     150     /**151      * 删除指定的索引152      * @throws Exception 153      */154     public void deleteIndex() throws Exception {155         // 1.指定索引库的位置156         Directory directory = FSDirectory.open(new File("src/index")) ;157         158         // 2.创建indexWriterConfig159         IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LATEST, null) ;160         161         // 3.创建indexWriter162         IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig) ;163         164         // 4.删除指定的索引(new Term())165         // indexWriter.deleteDocuments(new Term("id", "1"));//参数是Term()166         // indexWriter.deleteDocuments(new QueryParser("id", new167         // StandardAnalyzer()).parse("id:1"));//参数为query168         indexWriter.deleteAll() ;// 删除所有169         // 5.关闭流170         indexWriter.close() ;171         172         System.out.println("删除成功") ;173         174         // 在查询一遍验证是否删除175         searchIndex() ;176     }177     178     /**179      * 更新索引,180      * 最好的做法是先查出要修改的索引进行更新181      * @throws Exception 182      */183     public void updateIndex() throws Exception {184         // 1.指定索引库185         Directory directory = FSDirectory.open(new File("src/index")) ;186         187         // 2.定义indexReader188         IndexReader indexReader = DirectoryReader.open(directory) ;189         190         // 3.定义indexSearcher191         IndexSearcher indexSearcher = new IndexSearcher(indexReader) ;192         193         Query query = new QueryParser("id", new StandardAnalyzer()).parse("id:1") ;194         // 查询索引库195         TopDocs topDocs = indexSearcher.search(query, 1) ;196         197         // 获取查询到的对象198         ScoreDoc scoreDoc = topDocs.scoreDocs[0] ;199         200         // 获取document对象201         Document document = indexSearcher.doc(scoreDoc.doc) ;202         203         // 更新内容204         document.removeField("name") ;205         document.add(new TextField("name", "这是测试更新的内容", Store.YES)) ;206         207         // 初始化indexWriterConfig和indexWriter对象208         IndexWriterConfig IndexWriterConfig = new IndexWriterConfig(Version.LATEST,209                 new StandardAnalyzer()) ;210         IndexWriter indexWriter = new IndexWriter(directory, IndexWriterConfig) ;211         212         // 开始更新,这个方法第一个参数如果设置为null,则不会删除原来的数据,而且添加了一条更新后的新数据213         // 为了保证数据的严谨性,必须删除为更新之前的数据,添加上更新后的数据就哦了214         indexWriter.updateDocument(new Term("id", "1"), document) ;215         indexWriter.close() ;216         indexReader.close() ;217         218         System.out.println("更新成功") ;219     }220     221     /**222      * 可多条件连接QueryParser会将用户输入的查询表达式解析成Query对象实例。223      * 搜索 Query query = queryParser.parse("*:*") ;224      * @throws Exception 225      */226     public void searchIndex() throws Exception {227         // 创建分析器228         Analyzer analyzer = new StandardAnalyzer() ;229         230         // 查询条件231         QueryParser queryParser = new QueryParser("description", analyzer) ;232         Query query = queryParser.parse("description:个") ;233         234         // 指定搜索目录235         Directory directory = FSDirectory.open(new File("src/index")) ;236         237         // 创建indexReader238         IndexReader indexReader = DirectoryReader.open(directory) ;239         240         // 创建indexSearch对象241         IndexSearcher indexSearcher = new IndexSearcher(indexReader) ;242         243         // 查询索引库244         TopDocs topDocs = indexSearcher.search(query, 10) ;245         246         // 获取前十条记录247         ScoreDoc [] scoreDocs = topDocs.scoreDocs ;248         249         System.out.println("文档个数:" + topDocs.totalHits) ;250         251         for (ScoreDoc scoreDoc : scoreDocs) {252             Document doc = indexSearcher.doc(scoreDoc.doc) ;253             System.out.println(doc) ;254         }255     }256     257     /**258      * 这种不可多条件查询259      * 搜索 Query query = new TermQuery(new Term("id", "1"));260      * @throws Exception 261      */262     public void searchIndex2() throws Exception {263         // 创建分析器264         Analyzer analyzer = new StandardAnalyzer() ;265         266         // 查询条件267         Query query = new TermQuery(new Term("description", "徐景洋驻马店")) ;268         269         // 指定搜索目录270         Directory directory = FSDirectory.open(new File("src/index")) ;271         272         // 创建indexReader273         IndexReader indexReader = DirectoryReader.open(directory) ;274         275         // 创建indexSearch对象276         IndexSearcher indexSearcher = new IndexSearcher(indexReader) ;277         278         // 查询索引库279         TopDocs topDocs = indexSearcher.search(query, 10) ;280         281         // 获取前十条记录282         ScoreDoc [] scoreDocs = topDocs.scoreDocs ;283         284         System.out.println("文档个数:" + topDocs.totalHits) ;285         286         for (ScoreDoc scoreDoc : scoreDocs) {287             Document doc = indexSearcher.doc(scoreDoc.doc) ;288             System.out.println(doc) ;289         }290     }291     292     /**293      * NumericRangeQuery,指定数字范围查询.(创建field类型时,注意与之对应)294      * 搜索 Query query = NumericRangeQuery.newIntRange("id", 1, 9, true, true);295      * @throws Exception 296      */297     public void searchIndex3() throws Exception {298         // 创建分析器299         Analyzer analyzer = new StandardAnalyzer() ;300         301         // 查询条件302         // 创建查询303         // 第一个参数:域名304         // 第二个参数:最小值305         // 第三个参数:最大值306         // 第四个参数:是否包含最小值307         // 第五个参数:是否包含最大值308         309         Query query = NumericRangeQuery.newIntRange("id", 1, 9, true, true) ;310         311         // 指定搜索目录312         Directory directory = FSDirectory.open(new File("src/index")) ;313         314         // 创建indexReader315         IndexReader indexReader = DirectoryReader.open(directory) ;316         317         // 创建indexSearch对象318         IndexSearcher indexSearcher = new IndexSearcher(indexReader) ;319         320         // 查询索引库321         TopDocs topDocs = indexSearcher.search(query, 10) ;322         323         // 获取前十条记录324         ScoreDoc [] scoreDocs = topDocs.scoreDocs ;325         326         System.out.println("文档个数:" + topDocs.totalHits) ;327         328         for (ScoreDoc scoreDoc : scoreDocs) {329             Document doc = indexSearcher.doc(scoreDoc.doc) ;330             System.out.println(doc) ;331         }332     }333     334     /**335      *   1、MUST和MUST表示“与”的关系,即“并集”。 336          2、MUST和MUST_NOT前者包含后者不包含。 337          3、MUST_NOT和MUST_NOT没意义 338          4、SHOULD与MUST表示MUST,SHOULD失去意义; 339          5、SHOUlD与MUST_NOT相当于MUST与MUST_NOT。 340          6、SHOULD与SHOULD表示“或”的概念。341 342      * BooleanQuery,布尔查询,实现组合条件查询。343      * 搜索 BooleanQuery query = new BooleanQuery() ;344      * @throws Exception 345      */346     public void searchIndex4() throws Exception {347         // 创建分析器348         Analyzer analyzer = new StandardAnalyzer() ;349         350         // 查询条件351         BooleanQuery query = new BooleanQuery() ;352         353         Query query1 = new TermQuery(new Term("name", "spring")) ;354         Query query2 = NumericRangeQuery.newFloatRange("price", 60f, 80f, true, true) ;355         356         // MUST:查询条件必须满足,相当于AND357         // SHOULD:查询条件可选,相当于OR358         // MUST_NOT:查询条件不能满足,相当于NOT非359         query.add(query2, Occur.SHOULD) ;360         query.add(query1, Occur.MUST) ;361         362         // 指定搜索目录363         Directory directory = FSDirectory.open(new File("src/index")) ;364         365         // 创建indexReader366         IndexReader indexReader = DirectoryReader.open(directory) ;367         368         // 创建indexSearch对象369         IndexSearcher indexSearcher = new IndexSearcher(indexReader) ;370         371         // 查询索引库372         TopDocs topDocs = indexSearcher.search(query, 10) ;373         374         // 获取前十条记录375         ScoreDoc [] scoreDocs = topDocs.scoreDocs ;376         377         System.out.println("文档个数:" + topDocs.totalHits) ;378         379         for (ScoreDoc scoreDoc : scoreDocs) {380             Document doc = indexSearcher.doc(scoreDoc.doc) ;381             System.out.println(doc) ;382         }383     }384     385 }
View Code

 

 

技术分享
 1 package cn.xjy.test ; 2  3 import org.junit.Test ; 4 import cn.xjy.lucene.TestLucene ; 5  6 public class MyTest { 7      8     @Test 9     public void testIndex() throws Exception {10         TestLucene lucene = new TestLucene() ;11         // lucene.lucene();12         lucene.luceneCN() ;13         System.out.println("创建成功") ;14     }15     16     @Test17     public void testSearch() throws Exception {18         TestLucene lucene = new TestLucene() ;19         // lucene.searchIndex();20         lucene.searchIndex2() ;21         // lucene.searchIndex3();22         // lucene.searchIndex4();23     }24     25     @Test26     public void testDelete() throws Exception {27         TestLucene lucene = new TestLucene() ;28         lucene.deleteIndex() ;29     }30     31     @Test32     public void testUpdate() throws Exception {33         TestLucene lucene = new TestLucene() ;34         lucene.updateIndex() ;35     }36 }
View Code

 

配置文件:IKAnalyzer.cfg.xml

 1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> 3 <properties> 4  5     <comment>IK Analyzer 扩展配置</comment> 6     <!-- 用户可以在这里配置自己的扩展字典 --> 7     <entry key="ext_dict">mydict.dic</entry> 8     <!-- 用户可以在这里配置自己的扩展停用词字典 --> 9     <entry key="ext_stopwords">ext_stopword.dic</entry>10 11 </properties>

 

4      Field域

4.1   Field属性

  Field是文档中的域,包括Field名和Field值两部分,一个文档可以包括多个Field,Document只是Field的一个承载体,Field值即为要索引的内容,也是要搜索的内容。

是否分词(tokenized)

是:作分词处理,即将Field值进行分词,分词的目的是为了索引。

比如:商品名称、商品简介等,这些内容用户要输入关键字搜索,由于搜索的内容格式大、内容多需要分词后将语汇单元索引。

否:不作分词处理

比如:商品id、订单号、身份证号等

是否索引(indexed)

是:进行索引。将Field分词后的词或整个Field值进行索引,索引的目的是为了搜索。

比如:商品名称、商品简介分析后进行索引,订单号、身份证号不用分析但也要索引,这些将来都要作为查询条件。

否:不索引。该域的内容无法搜索到 

比如:商品id、文件路径、图片路径等,不用作为查询条件的不用索引。

是否存储(stored)

是:将Field值存储在文档中,存储在文档中的Field才可以从Document中获取。

比如:商品名称、订单号,凡是将来要从Document中获取的Field都要存储。

否:不存储Field值,不存储的Field无法通过Document获取 

比如:商品简介,内容较大不用存储。如果要向用户展示商品简介可以从系统的关系数据库中获取商品简介。

4.2   Field常用类型

  下边列出了开发中常用 的Filed类型,注意Field的属性,根据需求选择:

  

Field类

数据类型

Analyzed

是否分词

Indexed

是否索引

Stored

是否存储

说明

StringField(FieldName, FieldValue,Store.YES))

字符串

N

Y

Y或N

这个Field用来构建一个字符串Field,但是不会进行分词,会将整个串存储在索引中,比如(订单号,身份证号等)

是否存储在文档中用Store.YES或Store.NO决定

LongField(FieldName, FieldValue,Store.YES)

Long型

Y

Y

Y或N

这个Field用来构建一个Long数字型Field,进行分词和索引,比如(价格)

是否存储在文档中用Store.YES或Store.NO决定

StoredField(FieldName, FieldValue)

重载方法,支持多种类型

N

N

Y

这个Field用来构建不同类型Field

不分析,不索引,但要Field存储在文档中

TextField(FieldName, FieldValue, Store.NO)

TextField(FieldName, reader)

字符串

Y

Y

Y或N

如果是一个Reader, lucene猜测内容比较多,会采用Unstored的策略.

5   使用中文分词器IKAnalyzer

  IKAnalyzer继承Lucene的Analyzer抽象类,使用IKAnalyzer和Lucene自带的分析器方法一样,将Analyzer测试代码改为IKAnalyzer测试中文分词效果。

如果使用中文分词器ik-analyzer,就在索引和搜索程序中使用一致的分词器ik-analyzer。

5.1 添加jar包

      技术分享

  • 代码部分在上面的部分包含有中文解析.

 

全文检索技术---Lucene