【从0做项目】Java文档搜索引擎(9)烧脑终章!

   

阿华代码,不是逆风,就是我疯

你们的点赞收藏是我前进最大的动力!!

希望本文内容能够帮助到你!!

文章导读

阿华将发布项目复盘系列的文章,旨在:

1:手把手细致带大家从0到1做一个完整的项目,保证每2~3行代码都有详细的注解

2:通过文字+画图的方式,对项目进行整个复盘,更好的理解以及优化项目

3:总结自己的优缺点,扎实java相关技术栈,增强文档编写能力

零:项目结果展示

项目目前已经上线,小伙伴们可以进行使用!!!

Java 文档搜索

简述:在我的搜索引擎网站,用户进行关键字搜索,就可以查询到与这个关键字相关的java在线文档,(包含标题,关键字附近的简述,url),用户点击标题,即可跳转到相关在线文档,适用于JDK17版本。

一:导入

在前文(8)中我们使用停用词表对用户的搜索词句进行了过滤,并且在后端处理正文描述的时候使用正则表达式进行优化,让返回结果更加合理。本篇文章将会有点烧脑~

二:问题引入

1:情景引入

这里我们同样搜索array空格list

惊奇的发现array这个文档返回了两次,什么鬼~~!! 

2:思考

为什么一个文档会返回两次。想后端处理逻辑,我们拿到array这个词,在倒排索引中返回一堆docId;再拿到list这个词,再在倒排索引中返回一堆docId

注:(这里拿到的其实是一个集合,里面有好多Weight对象,对象里包含docId和weight权重,这里这么说是方便大家理解)

思考:那有没有一种可能就是说,一个文档中既包含array,又包含list,所以这个文档被查到了两次,就返回给前端两遍,显然,这种情况是非常有可能的!! 

不多bb直接上图,这里图解可能更清楚。

3:处理设计

(1)问题总结

①一个文档不能出现两次 

②像Array.html这样的文档,同时包含多个分词结果,意味着这个文档的“相关性”更高——所以就应该提高这个文档的权重!!

设计

(2)设计

①去重:把多个分词结果触发出来的文档,按照docId进行去重

②权重合并

(3)核心思路

①把分词结果进行排序处理(按照docId升序排序)

②对于docId相同的情况,进行权重的相加

注意:这里的分词结果可能不止两个,当有多个的时候,每一个分词都对应一个list集合,这里就是多路数组的归并了。

这里不理解的看下面这个图文字

不多bb上图理解

三:代码讲解

1:search方法

不要捉急,我们一点点的看代码

在search方法中我们使用mergeResult方法来进行合并,这里的参数传递,可以理解成把所有查到的docId相关文档作为参数进行传参,实际上传的是一个双重集合,这个集合中装的全都是Weight对象

    public List<Result> search(String query){
        //1:对query分词
        List<Term> oldTerms = ToAnalysis.parse(query).getTerms();//未过滤的分词结果集合
        List<Term> terms = new ArrayList<>();//过滤后的分词结果集合
        //针对分词结果,使用暂停词表进行过滤
        for(Term term : oldTerms){
            if(stopWords.contains(term.getName())){
                continue;
            }
            terms.add(term);
        }
        //2:对分词查倒排
        List<List<Weight>> termResult = new ArrayList<>();
//        List<Weight> allTermResult = new ArrayList<>();
        for (Term term : terms){
            String word = term.getName();
            List<Weight> invertedList = index.getInverted(word);//如果查不到就返回一个null
            if(invertedList == null){
                continue;
            }
//            allTermResult.addAll(invertedList);//把集合中所有Weight对象都扔到allTermResult中
            termResult.add(invertedList);
        }
        //3:[合并]对多个分词结果处发出的相同文档,进行权重合并
        List<Weight> allTermResult = mergeResult(termResult);

        //4: 按权重降序排序
        allTermResult.sort(new Comparator<Weight>() {
            @Override
            public int compare(Weight o1, Weight o2) {
                return o2.getWeight() - o1.getWeight();//降序排列
            }
        });
        //5:查正排,构造出想要的Result,返回结果
        List<Result> results = new ArrayList<>();
        for(Weight weight : allTermResult){//对每一个Weight都构建result,可能最后的结果会很多,但是用户一般只看第一页查询出来的信息,一般懒得翻页
            DocInfo docInfo = index.getDocInfo(weight.getDocId());//获取当前Weight对应的文档信息
            Result result = new Result();
            result.setTitle(docInfo.getTitle());
            result.setUrl(docInfo.getUrl());
//            result.setDesc(docInfo.getContent());//很明显把正文全部返回不合理
            result.setDesc(GenDesc(docInfo.getContent(),terms));//搞个正文简述,这个词前60个字符为起始,往后截取160个
            results.add(result);
        }
        return results;
    }

2:mergeResult

(1)Pos定位类

用来描述,我们Weight对象所在的位置

    static class Pos{
        public int row;
        public int col;
        public Pos(int row , int col){
            this.row = row;
            this.col = col;
        }
    }

(2)看图说话

搞一个优先级队列,比较规则就是,docId值更小的往里面放

(3)步骤拆解

①对每一路按docId的升序给Weight对象排个序

②new一个集合用来存放最后的Weight对象的合集

③把每一行的第一个元素放进队列中(初始化)

④优先级队列的比较规则是docId升序排列,放的是Pos对象也就是Weight对象的位置!!

⑤当队列不为空时,循环弹出元素Pos,找到对应的Weight对象,将这个Weight对象与我们target集合中最后一个位置的Weight对象进行对比看是不是同一个对象,若不是则直接加入集合,若是则合并权重。

⑥指针移动

喵喵喵~~妙脆角!跟着我的注解,看着图,敲一遍代码会更清楚内部的一个逻辑!

 private List<Weight> mergeResult(List<List<Weight>> source) {
        //把多路合并成一路
        //1:先给每一路按升序排个序
        for (List<Weight> curRow : source){
            curRow.sort(new Comparator<Weight>() {
                @Override
                public int compare(Weight o1, Weight o2) {
                    return o1.getDocId()- o2.getDocId();
                }
            });
        }
        //2:借优先级队列合并多路
        List<Weight> target = new ArrayList<>();
        PriorityQueue<Pos> queue = new PriorityQueue<>(new Comparator<Pos>() {
            @Override
            public int compare(Pos o1, Pos o2) {
                Weight w1 = source.get(o1.row).get(o1.col);//用下标找到Weight对象
                Weight w2 = source.get(o2.row).get(o2.col);
                return w1.getDocId() - w2.getDocId();
            }
        });
        //2.1:初始化队列——把每一行第一个元素放到队列当中
        for(int row = 0 ; row < source.size() ; row++){
            queue.offer(new Pos(row,0));
        }
        //2.2:循环取队首元素(也就是当前若干行中最小的元素)
        while(!queue.isEmpty()){
            Pos curMinPos = queue.poll();
            Weight curWeight = source.get(curMinPos.row).get(curMinPos.col);

            //2.3:检查当前的Weight对象,与上一个插入到target中的对象是否是相同的对象,这里可以用Weight对象中的docId作为比较依据
            if(target.size() > 0){
                Weight lastWeight = target.get(target.size()-1);
                if(lastWeight.getDocId() == curWeight.getDocId()){
                    //文档id若相等则合并
                    int weightSum = lastWeight.getWeight() + curWeight.getWeight();
                    lastWeight.setWeight(weightSum);
                }else{
                    //文档id不相等就直接入target
                    target.add(curWeight);
                }
            }else{
                //若当前的target为空,就直接加入
                target.add(curWeight);
            }

            //2.4:考虑移动光标,当前元素处理完了之后,要把对应的这个元素光标往后移动,取这一行的下一个元素
            Pos newpos = new Pos(curMinPos.row , curMinPos.col+1);
            if(newpos.col > source.get(newpos.row).size() - 1){
                //说明光标已经超出这一行的范围了,到达末尾了,这一行就处理完了
                continue;//直接进入下一次循环
            }
            //否则把新的坐标扔到队列当中
            queue.offer(newpos);
        }
        return target;
    }

四:前后优化结果对比

暴减500条结果,说明有200多个结果都是重复的。

至此Java文档搜索引擎博客讲解就结束了,这里的图解和测试,花费了阿华很大的精力,希望这个系列能够帮助到你~~塔塔开!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/973069.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

cs106x-lecture12(Autumn 2017)-SPL实现

打卡cs106x(Autumn 2017)-lecture12 (以下皆使用SPL实现&#xff0c;非STL库&#xff0c;后续课程结束会使用STL实现) travel Write a recursive function named travel that accepts integers x and y as parameters and uses recursive backtracking to print all solution…

vue取消全选功能按钮注意事项

这里这个功能是通过各种条件查出数据,但只取一条数据进行后续业务,虽然每一条数据前面都有多选框,但只需要选一个,所以在业务上分析可以把这个全选按钮取消掉 这里不是简单的把多选组件的selection-change"handleSelectionChange"和handleSelectionChange方法去掉,因…

三维扫描仪:如何快速获取产品外部结构尺寸?

在精密制造与质量控制领域&#xff0c;传统测量方法因接触式检测效率低、数据维度单一等问题&#xff0c;正面临数字化升级的迫切需求。 传统测量方法的局限性&#xff1a; 传统的测量工具&#xff0c;如卡尺、千分尺和三坐标测量仪&#xff0c;虽然在精度上有一定的保证&…

无人机避障——感知篇(采用Livox-Mid360激光雷达获取点云数据显示)

电脑配置&#xff1a;Xavier-nx、ubuntu 18.04、ros melodic 激光雷达&#xff1a;Livox_Mid-360 1、安装激光雷达驱动 下载安装Livox-SDK2 如果git clone不了&#xff0c;在github上下载相应的zip进行手动安装&#xff0c;安装网址如下&#xff1a; https://github.com/L…

ubuntu22.04使用minikube安装k8s

ubuntu使用minikube安装k8s 准备工作安装步骤安装docker安装kubectl安装minikube导入相关镜像安装相关指令启动minikube服务 安装dashboard组件导入相关镜像创建服务账号安装组件本体验证安装结果 准备工作 下载离线安装包&#xff0c;安装包内容如下&#xff1a; 软件说明ki…

西门子1200下载、上传程序。

下载 第一种 直接点击图标下载&#xff0c;此种方式PLC会停机。 第二种 这三种的区别&#xff1a; 上传 创建新的项目。

基于Openlayers对GeoServer发布的数据进行增删改

使用GeoServer进行图斑数据管理 本文将介绍如何使用GeoServer进行图斑数据的新增、删除和修改。我们将通过一个Vue.js应用来演示这些功能。 设置Vue.js应用 首先&#xff0c;我们设置Vue.js应用&#xff0c;并添加必要的组件和交互逻辑。 Check.vue Check.vue文件包含初始…

自动化之ansible(二)

一、ansible中playbook&#xff08;剧本&#xff09; 官方文档&#xff1a; Ansible playbooks — Ansible Community Documentation 1、playbook的基本结构 一个基本的playbook由以下几个主要部分组成 hosts: 定义要执行任务的主机组或主机。 become: 是否需要使用超级用户…

函数执行中的栈和寄存器调用

函数执行中的栈和寄存器调用 函数执行过程中主要用到的寄存器有程序计数器和栈指针。 程序计数器&#xff08;IP&#xff09;&#xff1a;指向下一条执行指令的地址&#xff0c;其值用%rip来表示 栈指针&#xff1a;指向栈顶地址&#xff0c;其值用%rsp来表示 当过程P调用过…

纯新手教程:用llama.cpp本地部署DeepSeek蒸馏模型

0. 前言 llama.cpp是一个基于纯C/C实现的高性能大语言模型推理引擎&#xff0c;专为优化本地及云端部署而设计。其核心目标在于通过底层硬件加速和量化技术&#xff0c;实现在多样化硬件平台上的高效推理&#xff0c;同时保持低资源占用与易用性。 最近DeepSeek太火了&#x…

建筑兔零基础自学python记录22|实战人脸识别项目——视频人脸识别(下)11

这次我们继续解读代码&#xff0c;我们主要来看下面两个部分&#xff1b; 至于人脸识别成功的要点我们在最后总结~ 具体代码学习&#xff1a; #定义人脸名称 def name():#预学习照片存放位置path M:/python/workspace/PythonProject/face/imagePaths[os.path.join(path,f) f…

【Java消息队列】应对消息丢失、重复、顺序与积压的全面策略

应对消息丢失、重复、顺序与积压的全面策略 引言kafka消息丢失生产者消费者重复消费顺序消费消息积压生产者消费者其他RabbitMQ消息丢失生产者事务机制,保证生产者发送消息到 RabbitMQ Server发送方确认机制,保证消息能从交换机路由到指定队列保证消息在 RabbitMQ Server 中的…

PHP会务会议系统小程序源码

&#x1f4c5; 会务会议系统 一款基于ThinkPHPUniapp框架&#xff0c;精心雕琢的会议管理微信小程序&#xff0c;专为各类高端会议场景量身打造。它犹如一把开启智慧殿堂的金钥匙&#xff0c;为会议流程优化、开支精细化管理、数量精准控制、标准严格设定以及供应商严格筛选等…

Unity通过Vosk实现离线语音识别方法

标注&#xff1a;deepseek直接生成&#xff0c;待验证 在Unity中实现离线语音识别可以通过集成第三方语音识别库来实现。以下是一个使用 Unity 和 Vosk&#xff08;一个开源的离线语音识别库&#xff09;的简单示例。 准备工作 Vosk&#xff1a;一个开源的离线语音识别库&am…

架构学习第七周--Prometheus

目录 一、监控系统基础 二、Prometheus介绍 三、Prometheus单机部署 四、服务发现与告警功能 4.1&#xff0c;服务发现 4.2&#xff0c;告警功能实现 五、Prometheus与Kubernetes 5.1&#xff0c;Kubernetes指标 5.2&#xff0c;Prometheus集群部署 一、监控系统基础…

技术总结 | MySQL面试知识点

MySQL面试知识点 1.存储引擎1.1 Archive1.2 BlackHole1.3 MyISAM1.4 InnoDB (重点记一下哦)1.5 Memory1.6 CSV 2. 事务2.1. 什么是事务2.2. 事务的特性2.3. 事务的操作sql2.4. 事务的隔离级别 3.三大日志3.1. undo log 回滚日志3.2. redo log 重做日志3.3. bin log 二进制日志4…

DeepSeek模型快速部署教程-搭建自己的DeepSeek

前言&#xff1a;在人工智能技术飞速发展的今天&#xff0c;深度学习模型已成为推动各行各业智能化转型的核心驱动力。DeepSeek 作为一款领先的 AI 模型&#xff0c;凭借其高效的性能和灵活的部署方式&#xff0c;受到了广泛关注。无论是自然语言处理、图像识别&#xff0c;还是…

图论 之 BFS

文章目录 3243.新增道路查询后的最短距离1311.获取你好友已观看的视频 BFS:广度优先搜索&#xff08;BFS&#xff09; 是一种常用的算法&#xff0c;通常用于解决图或树的遍历问题&#xff0c;尤其是寻找最短路径或层级遍历的场景。BFS 的核心思想是使用队列&#xff08;FIFO 数…

VSCode集成deepseek使用介绍(Visual Studio Code)

VSCode集成deepseek使用介绍&#xff08;Visual Studio Code&#xff09; 1. 简介 随着AI辅助编程工具的快速发展&#xff0c;VSCode作为一款轻量级、高度可扩展的代码编辑器&#xff0c;已成为开发者首选的工具之一。DeepSeek作为AI模型&#xff0c;结合Roo Code插件&#x…

Qt中利用httplib调用https接口

httplib中如果要调用https接口&#xff0c;需要开启OPENSSL支持&#xff0c;经过半天坑爹得摸索&#xff0c;总结下经验。 1&#xff0c;下载 并安装Win64OpenSSL 地址如下&#xff0c;我Qt版本是5.15.2 &#xff0c;openssl选择的是 64位&#xff08;Win64OpenSSL-3_3_3.msi…