【从0做项目】Java文档搜索引擎(9)烧脑终章!
阿华代码,不是逆风,就是我疯
你们的点赞收藏是我前进最大的动力!!
希望本文内容能够帮助到你!!
目录
文章导读
零:项目结果展示
一:导入
二:问题引入
1:情景引入
2:思考
3:处理设计
(1)问题总结
(2)设计
(3)核心思路
三:代码讲解
1:search方法
2:mergeResult
(1)Pos定位类
(2)看图说话
(3)步骤拆解
四:前后优化结果对比
文章导读
阿华将发布项目复盘系列的文章,旨在:
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);//如果查不到就返回一个nullif(invertedList == null){continue;}
// allTermResult.addAll(invertedList);//把集合中所有Weight对象都扔到allTermResult中termResult.add(invertedList);}//3:[合并]对多个分词结果处发出的相同文档,进行权重合并List<Weight> allTermResult = mergeResult(termResult);//4: 按权重降序排序allTermResult.sort(new Comparator<Weight>() {@Overridepublic 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>() {@Overridepublic 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>() {@Overridepublic 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不相等就直接入targettarget.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文档搜索引擎博客讲解就结束了,这里的图解和测试,花费了阿华很大的精力,希望这个系列能够帮助到你~~塔塔开!
相关文章:

【从0做项目】Java文档搜索引擎(9)烧脑终章!
阿华代码,不是逆风,就是我疯 你们的点赞收藏是我前进最大的动力!! 希望本文内容能够帮助到你!! 目录 文章导读 零:项目结果展示 一:导入 二:问题引入 1:情…...

python: SQLAlchemy (ORM) Simple example using mysql in Ubuntu 24.04
mysql sql script: create table School 表 (SchoolId char(5) NOT NULL comment主鍵primary key,學校編號,SchoolName nvarchar(500) NOT NULL DEFAULT comment 學校名稱,SchoolTelNo varchar(8) NULL DEFAULT comment電話號碼,PRIMARY KEY (SchoolId) #主…...

如何为自己的 PDF 文件添加密码?在线加密 PDF 文件其实更简单
随着信息泄露和数据安全问题的日益突出,保护敏感信息变得尤为重要。加密 PDF 文件是一种有效的手段,可以确保只有授权用户才能访问或修改文档内容。本文将详细介绍如何使用 CleverPDF 在线工具为你的 PDF 文件添加密码保护,确保其安全性。 为…...

echarts 折线图动态基准线设置超出基准线标红
基准线属性:markLine 线条标红关键属性:visualMap 小于: lt (less than) 大于:gt (greater than) 小于等于:lte (Less than or equal to) 大于等于:gte (Greater than or equal to) 1、基础应用——2条基准…...

Part 3 第十二章 单元测试 Unit Testing
概述 第十二章围绕单元测试展开,阐述了单元测试的实践与重要性,通过对比其他测试类型,突出其特点,还介绍了单元测试的最佳实践、避免的反模式以及与测试替身相关的内容,为编写高质量单元测试提供指导。 章节概要 1…...
C++与Python:两种编程语言的区别
C和Python都是当今编程领域广泛使用的语言,它们各有特色,适用于不同的开发场景。本文将从语言特性、性能、学习难度、应用领域等多个方面探讨C与Python之间的区别。 一、语言特性 类型系统: C:是一种静态类型语言…...
Springboot 高频面试题
以下是Spring Boot的高频面试题及答案和底层原理解释: 基础概念 什么是Spring Boot,其主要特点是什么? 答案: Spring Boot本质上是一个建立在Spring框架之上的快速应用开发框架。其主要特点包括: 启动器:一…...

常用电脑,护眼软件推荐 f.lux 3400K | 撰写论文 paper
常用电脑?平均每天用 5 个小时?你就要考虑用一个护眼软件了,对皮肤也好。因为电脑屏幕有辐射,比如蓝光。 f.lux 作为一款专业护眼软件,值得使用。之前用了三年的 Iris Pro,现在 f.lux 做的更好了。 使用…...

MacOS下使用Ollama本地构建DeepSeek并使用本地Dify构建AI应用
目录 1 大白话说一下文章内容2 作者的电脑配置3 DeepSeek的本地部署3.1 Ollamal的下载和安装3.2 选择合适的deepseek模型3.3 安转deepseek 4 DifyDeepSeek构建Al应用4.1 Dify的安装4.1.1 前置条件4.1.2 拉取代码4.1.3 启动Dify 4.2 Dify控制页面4.3 使用Dify实现个“文章标题生…...
如何有效利用MYSQL的连接数
连接数配置2500~3000 依然发现连接不够用? -- 查看当前最大连接数 SHOW VARIABLES LIKE MAX_CONNECTIONS; -- 查看当前总链接数 SHOW STATUS LIKE Threads_connected; -- 查看当前进程明细 SHOW PROCESSLIST; 合理设置以下参数: 1. MySQL 的参数设置 …...

调用click.getchar()时Windows PyCharm无法模拟键盘输入
文章目录 问题描述解决方案参考文献 问题描述 调用 click.getchar() 时,Windows PyCharm 无法模拟键盘输入 解决方案 Run → Edit Configurations… → Modify options → Emulate terminal in output console 参考文献 Terminal emulator | PyCharm Documentati…...

使用Hardhat实现ERC20 代币合约详解
ERC20 代币合约详解 💰 1. 合约概览 // SPDX-License-Identifier: MIT pragma solidity ^0.8.20;import "openzeppelin/contracts/token/ERC20/ERC20.sol";contract MyToken is ERC20 {constructor() ERC20("MyToken", "MTK") {_min…...
清华大学《DeepSeek与AI幻觉》(无套路免费分享)
随着人工智能技术的飞速发展,以DeepSeek为代表的国产大模型正逐渐成为各行各业的重要工具。然而,AI在生成内容时常常会出现“幻觉”——即生成与事实不符、逻辑断裂或脱离上下文的内容。 清华大学新闻与传播学院与人工智能学院联合推出的这篇教程《Deep…...
代码随想录算法【Day52】
Day51 101. 孤岛的总面积 思路 从周边找到陆地然后 通过 dfs或者bfs 将周边靠陆地且相邻的陆地都变成海洋,然后再去重新遍历地图 统计此时还剩下的陆地 代码 #include <iostream> #include <vector> using namespace std; int dir[4][2] {-1, 0, …...
Spark 和 Flink
Spark 和 Flink 都是目前流行的大数据处理引擎,但它们在架构设计、应用场景、性能和生态方面有较大区别。以下是详细对比: 1. 架构与核心概念 方面Apache SparkApache Flink计算模型微批(Micro-Batch)为主,但支持结构…...

Unity结合Vuforia虚拟按键实现AR机械仿真动画效果
零、最终效果 待上传 一、资源准备 1、Vuforia Vuforia版本不能高于10.17.4(往上的版本虚拟按键功能被删除) 2、Unity Unity版本必须要高于2022.3.x,不然使用Vuforia插件时会出现bug 二、主要内容 1、添加虚拟按钮 2、为虚拟按钮设置…...
PL/SQL 异常处理
目录 一、命名的系统异常 1.常见命名的系统异常 2.预定义的系统异常数量以及描述 3.处理命名的系统异常的一般步骤 二、程序员定义的异常 1.概念 2.自定义异常的定义与抛出 3.自定义异常的处理 三、未命名的系统异常 1.概述 2.处理未命名的系统异常的方法 3.使用场景 …...
【自学笔记】Spring Boot框架技术基础知识点总览-持续更新
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 Spring Boot框架技术基础知识点总览一、Spring Boot简介1.1 什么是Spring Boot?1.2 Spring Boot的主要特性 二、Spring Boot快速入门2.1 搭建Spring Boo…...
Redis 缓存穿透、击穿、雪崩:问题与解决方案
在使用 Redis 作为缓存中间件时,系统可能会面临一些常见的问题,如 缓存穿透、缓存击穿 和 缓存雪崩。这些问题如果不加以解决,可能会导致数据库压力过大、系统响应变慢甚至崩溃。本文将详细分析这三种问题的起因,并提供有效的解决…...
第一个CMAKE项目hello cmake
#注意! 这篇文章是为WINDWOS用户写的 #请检查你的电脑上已经安装了MINGW和CMAKE 快速检查方法如下 C:\Users\Basicoperation>g --version g (x86_64-win32-seh-rev1, Built by MinGW-Builds project) 14.2.0 Copyright (C) 2024 Free Software Foundation, Inc. …...
vscode里如何用git
打开vs终端执行如下: 1 初始化 Git 仓库(如果尚未初始化) git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...

对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...

如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...

Vue ③-生命周期 || 脚手架
生命周期 思考:什么时候可以发送初始化渲染请求?(越早越好) 什么时候可以开始操作dom?(至少dom得渲染出来) Vue生命周期: 一个Vue实例从 创建 到 销毁 的整个过程。 生命周期四个…...

mac:大模型系列测试
0 MAC 前几天经过学生优惠以及国补17K入手了mac studio,然后这两天亲自测试其模型行运用能力如何,是否支持微调、推理速度等能力。下面进入正文。 1 mac 与 unsloth 按照下面的进行安装以及测试,是可以跑通文章里面的代码。训练速度也是很快的。 注意…...
Linux安全加固:从攻防视角构建系统免疫
Linux安全加固:从攻防视角构建系统免疫 构建坚不可摧的数字堡垒 引言:攻防对抗的新纪元 在日益复杂的网络威胁环境中,Linux系统安全已从被动防御转向主动免疫。2023年全球网络安全报告显示,高级持续性威胁(APT)攻击同比增长65%,平均入侵停留时间缩短至48小时。本章将从…...
JS红宝书笔记 - 3.3 变量
要定义变量,可以使用var操作符,后跟变量名 ES实现变量初始化,因此可以同时定义变量并设置它的值 使用var操作符定义的变量会成为包含它的函数的局部变量。 在函数内定义变量时省略var操作符,可以创建一个全局变量 如果需要定义…...
用神经网络读懂你的“心情”:揭秘情绪识别系统背后的AI魔法
用神经网络读懂你的“心情”:揭秘情绪识别系统背后的AI魔法 大家好,我是Echo_Wish。最近刷短视频、看直播,有没有发现,越来越多的应用都开始“懂你”了——它们能感知你的情绪,推荐更合适的内容,甚至帮客服识别用户情绪,提升服务体验。这背后,神经网络在悄悄发力,撑起…...

【1】跨越技术栈鸿沟:字节跳动开源TRAE AI编程IDE的实战体验
2024年初,人工智能编程工具领域发生了一次静默的变革。当字节跳动宣布退出其TRAE项目(一款融合大型语言模型能力的云端AI编程IDE)时,技术社区曾短暂叹息。然而这一退场并非终点——通过开源社区的接力,TRAE在WayToAGI等…...

Python爬虫(52)Scrapy-Redis分布式爬虫架构实战:IP代理池深度集成与跨地域数据采集
目录 一、引言:当爬虫遭遇"地域封锁"二、背景解析:分布式爬虫的两大技术挑战1. 传统Scrapy架构的局限性2. 地域限制的三种典型表现 三、架构设计:Scrapy-Redis 代理池的协同机制1. 分布式架构拓扑图2. 核心组件协同流程 四、技术实…...