Lucene--介绍

一、什么是Lucene

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

二、全文检索的应用场景

2.1搜索引擎

搜索引擎.PNG

2.2站内搜索

站内搜索.png

2.3文件系统的搜索

文件系统搜索.png

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

三、全文检索的定义

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

四、Lucene实现全文检索的流程

流程.png

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

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

Lucene本身不能进行视图渲染。

五、入门程序

5.1需求

使用lucene完成对数据库中图书信息的索引和搜索功能。

5.2环境

  • Jdk:1.7及以上
  • Lucene:4.10(从4.8版本以后,必须使用jdk1.7及以上)
  • Ide:indigo
  • 数据库:mysql

5.3数据库脚本

数据库脚本.PNG

5.4Lucene下载

Lucene是开发全文检索功能的工具包,使用时从官方网站下载,并解压。

官方网站:http://lucene.apache.org/
目前最新版本:5.4.0

下载地址:http://archive.apache.org/dist/lucene/java/
下载版本:4.10.3
JDK要求:1.7以上(从版本4.8开始,不支持1.7以下)
1.png
2.png
3.png
4.png

5.5工程搭建

所需要的jar包:

  • mysql5.1驱动包:mysql-connector-java-5.1.7-bin.jar
  • 核心包:lucene-core-4.10.3.jar
  • 分析器通用包:lucene-analyzers-common-4.10.3.jar
  • 查询解析器包:lucene-queryparser-4.10.3.jar
  • junit包:junit-4.9.jar(非必须)
工程.PNG

5.6实现全文检索

第一步:索引流程

对文档索引的过程,就是将用户要搜索的文档内容进行索引,然后把索引存储在索引库(index)中。

1.为什么要采集数据

全文检索搜索的内容的格式是多种多样的,比如:视频、mp3、图片、文档等等。对于这种格式不同的数据,需要先将他们采集到本地,然后统一封装到lucene的文档对象中,也就是说需要将存储的内容进行统一才能对它进行查询。

2.采集数据的方式

  • 对于互联网中的数据,使用爬虫工具(http工具)将网页爬取到本地
  • 对于数据库中的数据,使用jdbc程序进行数据采集
  • 对于文件系统的数据,使用io流采集

因为目前搜索引擎主要搜索数据的来源是互联网,搜索引擎使用一种爬虫程序抓取网页( 通过http抓取html网页信息),以下是一些爬虫项目:

  • Solr(http://lucene.apache.org/solr) ,solr是apache的一个子项目,支持从关系数据库、xml文档中提取原始数据。
  • Nutch(http://lucene.apache.org/nutch), Nutch是apache的一个子项目,包括大规模爬虫工具,能够抓取和分辨web网站数据。
  • jsoup(http://jsoup.org/ ),jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据。
  • heritrix(http://sourceforge.net/projects/archive-crawler/files/),Heritrix 是一个由 java 开发的、开源的网络爬虫,用户可以使用它来从网上抓取想要的资源。其最出色之处在于它良好的可扩展性,方便用户实现自己的抓取逻辑。

3.索引文件的逻辑结构

索引文件的逻辑结构.PNG

文档域:

文档域存储的信息就是采集到的信息,通过Document对象来存储,具体说是通过Document对象中field域来存储数据。

比如:数据库中一条记录会存储一个一个Document对象,数据库中一列会存储成Document中一个field域。

文档域中,Document对象之间是没有关系的。而且每个Document中的field域也不一定一样。

索引域:

索引域主要是为了搜索使用的。索引域内容是经过lucene分词之后存储的。

倒排索引表

传统方法是先找到文件,如何在文件中找内容,在文件内容中匹配搜索关键字,这种方法是顺序扫描方法,数据量大就搜索慢。
倒排索引结构是根据内容(词语)找文档,倒排索引结构也叫反向索引结构,包括索引和文档两部分,索引即词汇表,它是在索引中匹配搜索关键字,由于索引内容量有限并且采用固定优化算法搜索速度很快,找到了索引中的词汇,词汇与文档关联,从而最终找到了文档。

倒排索引表.PNG

4.索引

4.1 采集数据

Book:

public class Book {
    
    private Integer id;
    private String name;
    private Float price;
    private String pic;
    private String description;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Float getPrice() {
        return price;
    }
    public void setPrice(Float price) {
        this.price = price;
    }
    public String getPic() {
        return pic;
    }
    public void setPic(String pic) {
        this.pic = pic;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
}

BookDao:

public interface BookDao {
    
    public List<Book> queryBooks();

}

BookDaoImpl:

public class BookDaoImpl implements BookDao {

    @Override
    public List<Book> queryBooks() {
        // 数据库链接
        Connection connection = null;

        // 预编译statement
        PreparedStatement preparedStatement = null;

        // 结果集
        ResultSet resultSet = null;

        // 图书列表
        List<Book> list = new ArrayList<Book>();

        try {
            // 加载数据库驱动
            Class.forName("com.mysql.jdbc.Driver");
            // 连接数据库
            connection = DriverManager.getConnection(
                    "jdbc:mysql://localhost:3306/solr", "root", "root");

            // SQL语句
            String sql = "SELECT * FROM book";
            // 创建preparedStatement
            preparedStatement = connection.prepareStatement(sql);

            // 获取结果集
            resultSet = preparedStatement.executeQuery();

            // 结果集解析
            while (resultSet.next()) {
                Book book = new Book();
                book.setId(resultSet.getInt("id"));
                book.setName(resultSet.getString("name"));
                book.setPrice(resultSet.getFloat("price"));
                book.setPic(resultSet.getString("pic"));
                book.setDescription(resultSet.getString("description"));
                list.add(book);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        return list;
    }

}

4.2 创建索引

创建索引流程:

创建索引流程.PNG

IndexWriter是索引过程的核心组件,通过IndexWriter可以创建新索引、更新索引、删除索引操作。IndexWriter需要通过Directory对索引进行存储操作。

Directory描述了索引的存储位置,底层封装了I/O操作,负责对索引进行存储。它是一个抽象类,它的子类常用的包括FSDirectory(在文件系统存储索引)、RAMDirectory(在内存存储索引)。

代码:

@Test
public void createIndex() throws Exception {
    // 采集数据
    BookDao dao = new BookDaoImpl();
    List<Book> list = dao.queryBooks();

    // 将采集到的数据封装到Document对象中
    List<Document> docList = new ArrayList<>();
    Document document;
    for (Book book : list) {
        document = new Document();
        // store:如果是yes,则说明存储到文档域中
        // 图书ID
        // 不分词、索引、存储 StringField
        Field id = new StringField("id", book.getId().toString(), Store.YES);
        // 图书名称
        // 分词、索引、存储 TextField
        Field name = new TextField("name", book.getName(), Store.YES);
        // 图书价格
        // 分词、索引、存储 但是是数字类型,所以使用FloatField
        Field price = new FloatField("price", book.getPrice(), Store.YES);
        // 图书图片地址
        // 不分词、不索引、存储 StoredField
        Field pic = new StoredField("pic", book.getPic());
        // 图书描述
        // 分词、索引、不存储 TextField
        Field description = new TextField("description",
                book.getDescription(), Store.NO);

        // 设置boost值
        if (book.getId() == 4)
            description.setBoost(100f);

        // 将field域设置到Document对象中
        document.add(id);
        document.add(name);
        document.add(price);
        document.add(pic);
        document.add(description);

        docList.add(document);
    }

    // 创建分词器,标准分词器
    // Analyzer analyzer = new StandardAnalyzer();
    // 使用ikanalyzer
    Analyzer analyzer = new IKAnalyzer();

    // 创建IndexWriter
    IndexWriterConfig cfg = new IndexWriterConfig(Version.LUCENE_4_10_3,
            analyzer);
    // 指定索引库的地址
    File indexFile = new File("E:\\11-index\\hcx\\");
    Directory directory = FSDirectory.open(indexFile);
    IndexWriter writer = new IndexWriter(directory, cfg);

    // 通过IndexWriter对象将Document写入到索引库中
    for (Document doc : docList) {
        writer.addDocument(doc);
    }

    // 关闭writer
    writer.close();
}

4.3 分词

在对Docuemnt中的内容索引之前需要使用分词器进行分词 ,主要过程就是分词、过虑两步。

  • 分词:将采集到的文档内容切分成一个一个的词,具体应该说是将Document中Field的value值切分成一个一个的词。
  • 过滤:将分好的词进行过滤,包括去除标点符号、去除停用词(的、是、a、an、the等)、大写转小写、词的形还原(复数形式转成单数形参、过去式转成现在式。。。)等。

什么是停用词?

停用词是为节省存储空间和提高搜索效率,搜索引擎在索引页面或处理搜索请求时会自动忽略某些字或词,这些字或词即被称为Stop Words(停用词)。比如语气助词、副词、介词、连接词等,通常自身并无明确的意义,只有将其放入一个完整的句子中才有一定作用,如常见的“的”、“在”、“是”、“啊”等。

Lucene作为了一个工具包提供不同国家的分词器,如下图:

工具包.png

注意由于语言不同分析器的切分规则也不同,本例子使用StandardAnalyzer,它可以对用英文进行分词。

如下是org.apache.lucene.analysis.standard.standardAnalyzer的部分源码:

@Override
protected TokenStreamComponents createComponents(final String fieldName, final Reader reader) {
    final StandardTokenizer src = new StandardTokenizer(getVersion(), reader);
    src.setMaxTokenLength(maxTokenLength);
    TokenStream tok = new StandardFilter(getVersion(), src);
    tok = new LowerCaseFilter(getVersion(), tok);
    tok = new StopFilter(getVersion(), tok, stopwords);
    return new TokenStreamComponents(src, tok) {
      @Override
      protected void setReader(final Reader reader) throws IOException {
        src.setMaxTokenLength(StandardAnalyzer.this.maxTokenLength);
        super.setReader(reader);
      }
    };
}
  • Tokenizer是分词器,负责将reader转换为语汇单元即进行分词,Lucene提供了很多的分词器,也可以使用第三方的分词,比如IKAnalyzer一个中文分词器。
  • tokenFilter是分词过滤器,负责对语汇单元进行过滤,tokenFilter可以是一个过滤器链儿,Lucene提供了很多的分词器过滤器,比如大小写转换、去除停用词等。

如下图是语汇单元的生成过程:

语汇单元的生成过程.png

从一个Reader字符流开始,创建一个基于Reader的Tokenizer分词器,经过三个TokenFilter生成语汇单元Token。

比如下边的文档经过分析器分析如下:

原文档内容:
Lucene is a Java full-text search engine.  

分析后得到的语汇单元:
lucene、java、full、text、search、engine

同一个域中相同的语汇单元(Token)对应同一个Term(词),它记录了语汇单元的内容及所在域的域名等,还包括该token出现的频率及位置。

  • 不同的域中拆分出来的相同的单词对应不同的term。
  • 相同的域中拆分出来的相同的单词对应相同的term。

例如:图书信息里面,图书名称中的java和图书描述中的java对应不同的term

4.4 使用luke工具查看索引

luke工具.png

Luke作为Lucene工具包中的一个工具(http://www.getopt.org/luke/),可以通过界面来进行索引文件的查询、修改。

打开Luke方法:

  • 命令运行:cmd运行:java -jar lukeall-4.10.3.jar
  • 手动执行:双击lukeall-4.10.3.jar
luke1.png
luke2.png
第二步:搜索流程

搜索过程:

搜索过程.PNG
  1. 用户定义查询语句,用户确定查询什么内容(输入什么关键字)
    指定查询语法,相当于sql语句。
  2. IndexSearcher索引搜索对象,定义了很多搜索方法,程序员调用此方法搜索。
  3. IndexReader索引读取对象,它对应的索引维护对象IndexWriter,IndexSearcher通过IndexReader读取索引目录中的索引文件
  4. Directory索引流对象,IndexReader需要Directory读取索引库,使用FSDirectory文件系统流对象
  5. IndexSearcher搜索完成,返回一个TopDocs(匹配度高的前边的一些记录)

1.输入查询语句:

同数据库的sql一样,lucene全文检索也有固定的语法:
最基本的有比如:AND, OR, NOT 等

举个例子,用户想找一个description中包括java关键字和lucene关键字的文档。
它对应的查询语句:description:java AND lucene
如下是使用luke搜索的例子:

使用luke搜索.png

2.搜索分词

和索引过程的分词一样,这里要对用户输入的关键字进行分词,一般情况索引和搜索使用的分词器一致。

比如:输入搜索关键字“java学习”,分词后为java和学习两个词,与java和学习有关的内容都搜索出来了

代码:

@Test
public void indexSearch() throws Exception {
    // 创建query对象
    // 使用QueryParser搜索时,需要指定分词器,搜索时的分词器要和索引时的分词器一致
    // 第一个参数:默认搜索的域的名称
    QueryParser parser = new QueryParser("description",
            new StandardAnalyzer());

    // 通过queryparser来创建query对象
    // 参数:输入的lucene的查询语句(关键字一定要大写)
    Query query = parser.parse("description:java AND lucene");

    // 创建IndexSearcher
    // 指定索引库的地址
    File indexFile = new File("E:\\11-index\\hcx\\");
    Directory directory = FSDirectory.open(indexFile);
    IndexReader reader = DirectoryReader.open(directory);
    IndexSearcher searcher = new IndexSearcher(reader);

    // 通过searcher来搜索索引库
    // 第二个参数:指定需要显示的顶部记录的N条
    TopDocs topDocs = searcher.search(query, 10);

    // 根据查询条件匹配出的记录总数
    int count = topDocs.totalHits;
    System.out.println("匹配出的记录总数:" + count);
    // 根据查询条件匹配出的记录
    ScoreDoc[] scoreDocs = topDocs.scoreDocs;

    for (ScoreDoc scoreDoc : scoreDocs) {
        // 获取文档的ID
        int docId = scoreDoc.doc;

        // 通过ID获取文档
        Document doc = searcher.doc(docId);
        System.out.println("商品ID:" + doc.get("id"));
        System.out.println("商品名称:" + doc.get("name"));
        System.out.println("商品价格:" + doc.get("price"));
        System.out.println("商品图片地址:" + doc.get("pic"));
        System.out.println("==========================");
        // System.out.println("商品描述:" + doc.get("description"));
    }
    // 关闭资源
    reader.close();
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容