第九阶段模块二 Lucene

本文围绕全文检索展开,先介绍数据分类及结构化、非结构化数据搜索方法,指出可用Lucene实现全文检索,并阐述其使用场景与特性。接着详细讲解Lucene实现全文检索的流程,包括索引和搜索流程、创建索引、倒排索引及查询索引。最后通过实战展示生成职位信息索引库及检索数据的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

内容输出来源:拉钩教育Java就业训练营

1 全文检索

1.1 数据分类

结构化数据:指具有固定格式或有限长度的数据,如数据库,元数据等。

非结构化数据:指不定长或无固定格式的数据,如邮件,word 文档等磁盘上的文件

1.2 结构化数据搜索

常见的结构化数据也就是数据库中的数据。

在这里插入图片描述

数据库中的数据存储是有规律的,有行有列而且数据格式、数据长度都是固定的。所以数据库搜索很容易。

1.3 非结构化数据查询方法

(1 ) 顺序扫描法(Serial Scanning)

用户搜索----->文件

所谓顺序扫描,比如要找内容包含某一个字符串的文件,就是一个文档一个文档的看,对于每一个文档,从头看到尾,如果此文档包含此字符串,则此文档为我们要找的文件,接着看下一个文件,直到扫描完所有的文件。如利用 windows 的搜索也可以搜索文件内容,只是相当的慢

(2 ) 全文检索(Full-text Search)

用户通过查询索引库---->生成索引----->文档

​ 全文检索是指计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方法。这个过程类似于通过字典的目录查字的过程。

​ 将非结构化数据中的一部分信息提取出来,重新组织,使其变得有一定结构,然后对此有一定结构的数据进行搜索,从而达到搜索相对较快的目的。这部分从非结构化数据中提取出的然后重新组织的信息,我们称之索引

1.4 如何实现全文检索

可以使用 Lucene 实现全文检索。

Lucene使用场景

在应用中为数据库中的数据提供全文检索实现。
开发独立的搜索引擎服务、系统

Lucene的特性:

  1. 稳定、索引性能高
  • 每小时能够索引150GB以上的数据
  • 对内存的要求小,只需要1MB的堆内存
  • 增量索引和批量索引一样快
  • 索引的大小约为索引文本大小的20%~30%

2.高效、准确、高性能的搜索算法

良好的搜索排序

  • 强大的查询方式支持:短语查询、通配符查询、临近查询、范围查询等
  • 支持字段搜索(如标题、作者、内容)
  • 可根据任意字段排序
  • 支持多个索引查询结果合并

3.跨平台

Lucene架构:

在这里插入图片描述

2 Lucene 实现全文检索的流程

2.1 索引和搜索流程图

在这里插入图片描述

  • 绿色表示索引过程,对要搜索的原始内容进行索引构建一个索引库,索引过程包括:
    确定原始内容即要搜索的内容–>采集文档–>创建文档–>分析文档–>索引文档

  • 红色表示搜索过程,从索引库中搜索内容,搜索过程包括:
    用户通过搜索界面–>创建查询–>执行搜索,从索引库搜索–>渲染搜索结果

2.2 创建索引

Document:

用户提供的源是一条条记录,它们可以是文本文件、字符串或者数据库表的一条记录等等。一条记录经过索引之后,就是以一个Document的形式存储在索引文件中的。用户进行搜索,也是以Document列表的形式返回。

Field

一个Document可以包含多个信息域,例如一篇文章可以包含“标题”、“正文”、“最后修改时间”等信息域,这些信息域就是通过Field在Document中存储的。

Field有两个属性可选:存储和索引。通过存储属性你可以控制是否对这个Field进行存储;通过索引属性你可以控制是否对该Field进行索引。

Term:

Term是搜索的最小单位,它表示文档的一个词语,Term由两部分组成:它表示的词语和这个词语所出现的Field的名称。

创建过程

  1. 获得原始文档:就是从mysql数据库中通过sql语句查询需要创建索引的数据
  2. 创建文档对象(Document),把查询的内容构建成lucene能识别的Document对象,获取原始内容的目的是为了索引,在索引前需要将原始内容创建成文档,文档中包括一个一个的域(Field),这个域对应就是表中的列。
  3. 分析文档:将原始内容创建为包含域(Field)的文档(document),需要再对域中的内容进行分析,分析的过程是经过对原始文档提取单词、将字母转为小写、去除标点符号、去除停用词等过程生成最终的语汇单元,可以将语汇单元理解为一个一个的单词。
  4. 创建索引:对所有文档分析得出的语汇单元进行索引,索引的目的是为了搜索,最终要实现只搜索被索引的语汇单元从而找到 Document(文档)。

PS:创建索引是对语汇单元索引,通过词语找文档,这种索引的结构叫 倒排索引结构。

在这里插入图片描述

2.3 倒排索引

倒排索引记录每个词条出现在哪些文档,及在文档中的位置,可以根据词条快速定位到包含这个词条的文档及出现的位置。

创建倒排索引,分为以下几步:

1)创建文档列表:
lucene首先对原始文档数据进行编号(DocID),形成列表,就是一个文档列表

2)创建倒排索引列表
对文档中数据进行分词,得到词条(分词后的一个又一个词)。对词条进行编号,以词条创建索引。然后记录下包含该词条的所有文档编号(及其它信息)。

搜索的过程
当用户输入任意的词条时,首先对用户输入的数据进行分词,得到用户要搜索的所有词条,然后拿着这些词条去倒排索引列表中进行匹配。找到这些词条就能找到包含这些词条的所有文档的编号。然后根据这些编号去文档列表中找到文档

2.4 查询索引

查询索引也是搜索的过程。搜索就是用户输入关键字,从索引(index)中进行搜索的过程。根据关键字搜索索引,根据索引找到对应的文档

  1. 创建用户接口:用户输入关键字的地方
  2. 创建查询 指定查询的域名和关键字
  3. 执行查询
  4. 渲染结果

3 Lucene 实战

生成职位信息索引库,从索引库检索数据

3.1 开发环境
  1. 创建一个SpringBoot项目

  2. 导入依赖

    <dependencies>
            <!--web依赖-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!--测试依赖-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <!--lombok工具-->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.4</version>
                <scope>provided</scope>
            </dependency>
            <!--热部署-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <optional>true</optional>
            </dependency>
            <!--mybatis-plus-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>3.3.2</version>
            </dependency>
            <!--pojo持久化使用-->
            <dependency>
                <groupId>javax.persistence</groupId>
                <artifactId>javax.persistence-api</artifactId>
                <version>2.2</version>
            </dependency>
            <!--mysql驱动-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
            <!--引入Lucene核心包及分词器包-->
            <dependency>
                <groupId>org.apache.lucene</groupId>
                <artifactId>lucene-core</artifactId>
                <version>4.10.3</version>
            </dependency>
            <dependency>
                <groupId>org.apache.lucene</groupId>
                <artifactId>lucene-analyzers-common</artifactId>
                <version>4.10.3</version>
            </dependency>
            <dependency>
                <groupId>org.testng</groupId>
                <artifactId>testng</artifactId>
                <version>RELEASE</version>
                <scope>test</scope>
            </dependency>
            <!--IK中文分词器-->
            <dependency>
                <groupId>com.janeluo</groupId>
                <artifactId>ikanalyzer</artifactId>
                <version>2012_u6</version>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <!--编译插件-->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>11</source>
                        <target>11</target>
                        <encoding>utf-8</encoding>
                    </configuration>
                </plugin>
                <!--打包插件-->
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <executions>
                        <execution>
                            <goals>
                                <goal>repackage</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    
  3. 创建引导类

    @SpringBootApplication
    @MapperScan("com.lagou.lucene.mapper")
    public class LuceneApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(LuceneApplication.class, args);
        }
    
    }
    
  4. 配置yml文件

    server:
      port: 9000
    Spring:
      application:
        name: lagou-lucene
      datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/es?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
        username: root
        password: 19970821
    
    #开启驼峰命名匹配映射
    mybatis:
      configuration:
        map-underscore-to-camel-case: true
    
    
  5. 创建实体类、mapper、service

实体类:

@Data
@Table(name = "job_info")
public class JobInfo {
  @Id
  private long id;
  private String companyName;
  private String companyAddr;
  private String companyInfo;
  private String jobName;
  private String jobAddr;
  private String jobInfo;
  private int salaryMin;
  private int salaryMax;
  private String url;
  private String time;
}

mapper:

public interface JobInfoMapper extends BaseMapper<JobInfo> {
}

service

@Service
public class JobInfoServiceImpl implements JobInfoService{

    @Autowired
    private JobInfoMapper jobInfoMapper;

    @Override
    public JobInfo selectById(long id) {
        return jobInfoMapper.selectById(id);
    }

    @Override
    public List<JobInfo> selectAll() {
        QueryWrapper<JobInfo> queryWrapper = new QueryWrapper<>();
        List<JobInfo> jobInfoList = jobInfoMapper.selectList(queryWrapper);
        return jobInfoList;
    }
}
3.2 创建索引
@Autowired
    private JobInfoService jobInfoService;

    /**
     * 创建索引
     */
    @Test
    public void create()throws Exception{
        //1.指定索引文件的存储位置,索引具体的表现形式就是一组有规则的文件
        Directory directory = FSDirectory.open(new File("D:/class/index"));
        //2.配置版本及其分词器
        Analyzer analyzer = new IKAnalyzer();
        IndexWriterConfig config = new IndexWriterConfig(Version.LATEST,analyzer);
        //3.创建IndexWriter对象,作用就是创建索引
        IndexWriter indexWriter = new IndexWriter(directory,config);
        //先删除已经存在的索引库
        indexWriter.deleteAll();
        //4.获得索引源/原始数据
        List<JobInfo> jobInfoList = jobInfoService.selectAll();
        //5. 遍历jobInfoList,每次遍历创建一个Document对象
        for (JobInfo jobInfo: jobInfoList) {
            //创建Document对象
            Document document = new Document();
            //创建Field对象,添加到document中
            document.add(new LongField("id",jobInfo.getId(), Field.Store.YES));
            //切分词、索引、存储
            document.add(new TextField("companyName",jobInfo.getCompanyName(), Field.Store.YES));
            document.add(new TextField("companyAddr",jobInfo.getCompanyAddr(), Field.Store.YES));
            document.add(new TextField("companyInfo",jobInfo.getCompanyInfo(), Field.Store.YES));
            document.add(new TextField("jobName",jobInfo.getJobName(), Field.Store.YES));
            document.add(new TextField("jobAddr",jobInfo.getJobAddr(), Field.Store.YES));
            document.add(new TextField("jobInfo",jobInfo.getJobInfo(), Field.Store.YES));
            document.add(new IntField("salaryMin",jobInfo.getSalaryMin(), Field.Store.YES));
            document.add(new IntField("salaryMax",jobInfo.getSalaryMax(), Field.Store.YES));
            document.add(new StringField("url",jobInfo.getUrl(), Field.Store.YES));
            //将文档追加到索引库中
            indexWriter.addDocument(document);
        }
        //关闭资源
        indexWriter.close();
        System.out.println("create index success!");
    }

Field的特性:

Document(文档)是Field(域)的承载体, 一个Document由多个Field组成. Field由名称和值两部分组成,Field的值是要索引的内容, 也是要搜索的内容.

  • 是否分词

    是: 将Field的值进行分词处理, 分词的目的是为了索引. 如: 商品名称, 商品描述. 这些内容用户会通过输入关键词进行查询, 由于内容多样, 需要进行分词处理建立索引.
    否: 不做分词处理. 如: 订单编号, 身份证号, 是一个整体, 分词以后就失去了意义, 故不需要分词.

  • 是否索引

    是: 将Field内容进行分词处理后得到的词(或整体Field内容)建立索引, 存储到索引域. 索引的目的是为了搜索. 如: 商品名称, 商品描述需要分词建立索引. 订单编号, 身份证号作为整体建立索引. 只要可能作为用户查询条件的词, 都需要索引.
    否: 不索引. 如: 商品图片路径, 不会作为查询条件, 不需要建立索引.

  • 是否存储

    是: 将Field值保存到Document中. 如: 商品名称, 商品价格. 凡是将来在搜索结果页面展现给用户的内容, 都需要存储.
    否: 不存储. 如: 商品描述. 内容多格式大, 不需要直接在搜索结果页面展现, 不做存储. 需要的时候可以从关系数据库取.

常用的Field类型:

在这里插入图片描述

3.3 查询索引
@Test
    public void query()throws Exception{
        //1.指定索引文件的存储位置,索引具体的表现形式就是一组有规则的文件
        Directory directory = FSDirectory.open(new File("D:/class/index"));
        //2.IndexReader对象
        IndexReader indexReader = DirectoryReader.open(directory);
        //3.创建查询对象,IndexSearcher
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);
        //使用term,查询公司名称中包含"北京"的所有的文档对象
        Query query = new TermQuery(new Term("companyName","北京"));
        TopDocs topDocs = indexSearcher.search(query, 100);
        //获得符合查询条件的文档数
        int totalHits = topDocs.totalHits;
        System.out.println("符合条件的文档数:"+totalHits);
        //获得命中的文档  ScoreDoc封装了文档id信息
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        for(ScoreDoc scoreDoc : scoreDocs){
            //文档id
            int docId = scoreDoc.doc;
            //通过文档id获得文档对象
            Document doc = indexSearcher.doc(docId);
            System.out.println("id:"+doc.get("id"));
            System.out.println("companyName:"+doc.get("companyName"));
            System.out.println("companyAddr:"+doc.get("companyAddr"));
            System.out.println("companyInfo:"+doc.get("companyInfo"));
            System.out.println("jobName:"+doc.get("jobName"));
            System.out.println("jobInfo:"+doc.get("jobInfo"));
            System.out.println("*******************************************");
        }
        //资源释放
        indexReader.close();
    }

Name"));
System.out.println(“companyAddr:”+doc.get(“companyAddr”));
System.out.println(“companyInfo:”+doc.get(“companyInfo”));
System.out.println(“jobName:”+doc.get(“jobName”));
System.out.println(“jobInfo:”+doc.get(“jobInfo”));
System.out.println("*******************************************");
}
//资源释放
indexReader.close();
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值