IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Java项目之搜索引擎 -> 正文阅读

[Java知识库]Java项目之搜索引擎

前置知识

搜索引擎的核心功能,就是查找到一组和用户输入的词/一句话 相关联的网页

关键字:搜索词;搜索结果的标题,搜索结果的描述,展示URL,跳转过去的目标页面,称为“落地页”;

对于一个搜索引擎来说,首先,需要获取到很多很多的网页;然后再根据用户输入的查询词,在这些网页中进行查找。

1.搜索引擎的网页是怎么获取到的?

此处主要是涉及到“爬虫”这样的程序(Http客户端)

2.用户输入了查询词之后,如何去让查询词和当前的这些网页进行匹配?

假设当前已经爬取到了一亿个网页,用户输入了一个"蛋糕"这样的查询词。这样的暴力搜索,是非常低效的,而搜索引擎需要能够让用户非常高效的获取到结果。

这时候就需要一种特殊的数据结构,倒排索引。

倒排索引

先介绍几个概念

  1. 文档(document):指的是每个待搜索的网页
  2. 正排索引:指的是 文档id => 文档内容
  3. 倒排索引:指的是 词 => 文档id列表

举个例子
正排索引:

文档 id 为1的内容为 : 我要出去实习挣钱
文档 id 为2的内容为:我要发论文顺利毕业

倒排索引:

 我要       1,2
 出去	   1
 发论文     2
 实习       1
 顺利       2
 挣钱       1
 毕业	   2

项目目标:实现一个针对Java文档的搜索引擎

获取Java8官方文档下载地址

在本地基于离线文档来制作索引。实现搜索,当用户在搜索结果页点击具体的搜索结果的时候,就自动跳转到在线文档的页面。

模块划分

  1. 索引模块
    ①扫描下载到的文档,分析文档的内容,构建出正排索引+倒排索引。并且把索引内容保存到文件中
    ②加载制作好的索引,并提供一些API实现查正排和查倒排这样的功能

  2. 搜索模块
    调用索引模块,实现一个搜索的完整过程
    输入:用户的查询词
    输出:完整的搜索结果(包含了很多条记录,每个记录都有标题,描述,展示URL,并且点击能够跳转)

  3. web模块
    需要实现一个简答的web程序,能够通过网页的形式来和用户进行交互
    包含了后端和前端

创建 Maven 项目

针对用户输入的内容 进行分词

分词的原理

  1. 基于词库
    尝试把所有的 “词” 都进行穷举,把这些穷举结果放到词典文件中
    然后就可以依次的取句子中的内容。每隔一个字,在词典里查一下;每隔两个字,查一下。

  2. 基于统计
    收集到很多很多的“语料库” => 人工标注=>也就知道了那些字在一起的概率比较大

使用现成的第三方库 ansj 来实现分词。

import org.ansj.domain.Term;
import org.ansj.splitWord.analysis.ToAnalysis;

import java.util.List;

public class TestAnsj {
    public static void main(String[] args) {

        String str = "我要赶快做好简历,准备实习,先出去实习再说";
        // ToAnalysis.parse(str) 返回的是 Result 类型
        List<Term> terms = ToAnalysis.parse(str).getTerms();

        // Term 就表示一个分词结果
        for (Term term : terms) {
            System.out.println(term.getName());
        }
    }
}

代码运行的警告是因为没有配置字典。

Parser 类(根据文档路径 读取文件,构建索引 并 保存到指定文件中)

解析出 HTML 对应的 URL 说明

Java API 文档有两份:

  1. 线上文档
  2. 线下文档(下载到本地的文档)

线上: https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html
线下:D:\Java\javaDocument\docs\api\java\util\ArrayList.html

期望达到的结果是,用户点击搜索结果,能够跳转到对应的线上文档的页面。
以 https://docs.oracle.com/javase/8/docs/api/ 为固定前缀。
将本地文档的后半部分 java\util\ArrayList.html 提出出来与固定前缀进行拼接。

倒排索引

倒排索引,词 => 文档 id 之间的映射关系

首先就需要先知道当前这个文档,里面都有那些词

因此就需要针对当前文档也进行分词

  1. 针对标题
  2. 针对正文

因此就可以先针对当前文档进行分词,然后根据每个分词结果;去倒排索引中,找到对应的 value,然后把当前文档 id 给加入到对应的 value列表中即可。

如何来确定权重的值?

这个值描述了 词 和 文档 之间的“相关性”

此处单纯的通过 词 出现的次数,来表示相关性。

权重计算公式:标题中出现的次数+10 + 正文中出现的次数

保存索引结构到磁盘中

索引当前是存储在 内存 中的。构建索引的过程是比较耗时的。

需要把内存中的索引结构,变成一个“字符串”,然后写文件即可。

对应的,把特定结构的字符串,反向解析成一些结构化数据(类/对象/基础数据结构) (反序列化)

通过 json库 来实现序列化和反序列化。ObjectMapper对象

	objectMapper.readValue(forwardIndexFile, new TypeReference<ArrayList<DocInfo>>() {});
    objectMapper.readValue(invertedIndexFile,new TypeReference<HashMap<String,ArrayList<Weight>>>() {});

创建一个匿名内部类,这个类实现了 TypeReference.同时再创建一个这个匿名内部类的实例。创建这个实例最主要的目的,就是为了把 ArrayList 这个类型信息,告诉 readValue 方法。

关于 Parser 和 Index 之间的关系

Parser 相当于制作索引的入口 => 对应到一个 “可执行” 程序
Index 相当于实现了索引的数据结构,提供了一些 api

Index 类就要给 Parser 进行调用,才能完成整个制作索引的功能

性能优化

在这里插入图片描述

通过测试,发现当前主要的性能瓶颈,在循环便利文件上。
每次循环都要针对一个文件进行解析。读文件+分词+解析内容(主要还是卡在CPU运算上)

单个线程的情况,任务都是串行执行的。
多个线程,这些任务就可以并发执行.

线程池之保证所有文档处理完毕再保存索引

CountDownLatch 类似于跑步比赛的裁判
只有所有的选手都撞线,就认为这场比赛就结束了

给制作索引代码加锁

如果直接把 synchronized 加到 parseHTML 或者 addDoc 这样的方法上,是不科学的,这样做锁的粒度太粗了,并发程度不高,提高的效率有限。因此要把枷锁的粒度搞细一点。

在这里插入图片描述

解决进程不退出的问题

关于守护线程
如果要是一个线程是守护线程(后台线程),此时这个线程的运行状态,不会影响到进程结束。
如果要是一个线程不是守护线程,这个线程的运行状态,就会影响到进程结束。

前面学过的线程创建的手段,默认创建出来的都是非守护线程,需要通过 setDaemon 方法手动设置,才能成为守护线程。

通过线程池创建的线程,并不是守护线程。
当main方法执行完了,这些线程仍然在工作(仍然在等待新任务的到来)

开机之后,首次制作索引特别慢的问题

是由于缓存,parseContent的核心操作,就是读取文件。从磁盘进行访问,操作系统就会对“经常读取的文件”进行缓存。
首次运行的时候,当前的这些Java文档,都没有在内存中缓存。因此读取的时候只能从硬盘上读取。
后面再运行的时候,由于前面已经读取过这些文档了,这些文档在操作系统中其实已经有了一份缓存(在内存中),这次的读取不必直接读硬盘,而是直接读内存的缓存(速度就会快很多)

正则表达式

正则里面主要就是有很多特殊符号
. 表示匹配一个非换行字符(不是 \n 或者不是 \r )
* 表示前面的字符可以出现若干次
.* 匹配非换行字符出现若干次

去除script标签和内容
<script.*?>(.*?)</script>

去除 普通 的标签(不去掉内容)
<.*? > 既能匹配到开始标签,也能匹配到结束标签

? 表示“非贪婪匹配”,匹配到一个符合条件的最短结果
不带?的.* 表示“贪婪匹配” 匹配到一个符合条件的最长结果

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-05-09 12:26:14  更:2022-05-09 12:28:48 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 23:59:28-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码