【从0做项目】Java搜索引擎(4)——性能优化~烧脑~~~
本篇文章将对项目搜索引擎(1)~(3)进行性能优化,包括测试,优化思路,优化前后对比
目录
一:文件读取
二:实现多线程制作索引
1:代码分析
2:代码测试
(1)第一次测试
(2)第二次测试
(3)测试总结
三:多线程实现代码
1:线程池的选用
2:索引save执行时机
(1)问题分析
(2)解决思路
四:线程安全问题
1:索引结构中新增文档线程安全分析
2:buildForward方法内部代码分析
3:builderInverted方法构建倒排索引内部代码分析
4:加锁对象的创建原理
5:优化结果对比
6:思考是否线程数量越多越好呢?
7:守护线程
(1)现象
(2)关于守护线程
一:文件读取
在进行文档正文解析的时候我们使用BufferReader来进行文件读取
可以理解为,BufferReader提供了一个缓冲区,每次文档可以加载一部分内容到内存中的缓冲区(默认大小是8192字节)里面,BufferReader就可以直接从内存中读了,减少了硬盘的IO操作,
try (BufferedReader bufferedReader = new BufferedReader(new FileReader(f), 1024 * 1024)) {//缓冲区设置为1M,默认的为8192字节太小
// FileReader fileReader = new FileReader(f);//这里是从硬盘读,我们改成提前读好,之后从内存中读效率会更高
我们的HTMl文档比较大,就设置为1M大小了

二:实现多线程制作索引
1:代码分析
思考:我们的的制作索引方法中核心的三步是,枚举文件,解析文件(包含解析标题,url,正文),保存文件。

2:代码测试
public static void main(String[] args) throws IOException, InterruptedException {Parser parser = new Parser();parser.run();
// parser.runByThread();//制作索引}
(1)第一次测试

(2)第二次测试

(3)测试总结
细心的小伙伴能发现,同样是run方法单线程制作索引,第一次和第二次测试,时间相差7s悬殊,这里其实跟电脑第一次启动有关,后续我会单独拿出来讲解优化
这里我们先看用第二次测试结果:很明显,遍历解析文件耗费的时间非常大,因为我们要对每一个文件进行:读文件+分词+解析内容。这里我们可以进行优化

三:多线程实现代码
public void runByThread() throws InterruptedException {long beg = System.currentTimeMillis();System.out.println("制作索引开始!");//1:枚举所有文件ArrayList<File> files = new ArrayList<>();enumFile(INPUT_PATH, files);long endEnumFile = System.currentTimeMillis();System.out.println("枚举文件完毕,消耗时间为:" + (endEnumFile - beg) + "ms");//2:循环遍历文件,多线程制作索引CountDownLatch latch = new CountDownLatch(files.size());//计数锁存器ExecutorService executorService = Executors.newFixedThreadPool(4);//线程池for (File f : files) {executorService.submit(new Runnable() {@Overridepublic void run() {System.out.println("开始解析" + f.getAbsolutePath());parseHTML(f);latch.countDown();}}); //解析每一个html文件}//await方法会阻塞,直到所有选手都调用contDown撞线之后,才能阻塞结束latch.await();//手动干掉非守护线程executorService.shutdown();long endFor = System.currentTimeMillis();System.out.println("遍历文件完毕!消耗时间为:" + (endFor - endEnumFile) + "ms");//3:把在内存中构造好的索引数据结构,保存到指定的文件中index.save();long end = System.currentTimeMillis();System.out.println("多线程下索引制作完毕!消耗总时间为:" + (end - beg) + "ms");System.out.println("t1:" + t1 + ", t2:" + t2);}
1:线程池的选用
不用ThreadPoolExecutor,这里面我们要设置的参数太多啦,包括核心线程数,最大线程数,存活时间,时间单位,工作任务,线程工厂太多了

这里我们使用Executor中的静态工厂方法newFixedThreadPool,只用传参线程数量参数即可,返回类型为ExecutorService,它是一个接口,继承于Executor接口。
可以通过ExecutorService类型变量的引用来调用线程池的各种方法,例如:任务提交,任务执行,线程池关闭。
2:索引save执行时机
(1)问题分析
这里我们用了4个线程来并发解析我们html文件,那么问题来了,是否会存在submit把文件都提交完毕了,但是线程池还没解析完这些文档,就进行save索引保存方法了呢?显然是有可能的。
那这里我们要确保所有的文档都被解析完了之后,才进行save,类似运动会跑步比赛,我们要等最后一名选手撞线了才能宣布比赛结束~~
(2)解决思路
这里我们使用了计数锁存器CountDownLatch,先记录下枚举出来了所有文件个数,每解析完毕一个文件,就countDown一次,只有countDown到0之后才不会进行阻塞,也就是latch.await才会放行!!
四:线程安全问题
三个解析方法不涉及共同对象的修改,因此不存在线程安全问题


1:索引结构中新增文档线程安全分析
不能在addDoc方法那里加锁,这里加锁的话,你并发执行又变成串行了


2:buildForward方法内部代码分析

3:builderInverted方法构建倒排索引内部代码分析

4:加锁对象的创建原理
如果这两个方法的加锁对象为this(index),明显是不很合理的,因为正排和倒排是两个不同的对象,所以我们可以设置不同的加锁对象,这样效率会更好,所以这里我们创建了两个不同的加锁对象
//创建两个锁对象private Object locker1 = new Object();private Object locker2 = new Object();
就好比
5:优化结果对比
时间的提升还是非常明显了,提升了近1倍的速度

t1是用来衡量解析全部html文件中,解析全部Url所耗费的时间
t2则是解析全部content所耗费的时间,这里之所以不采用打印的方式,是因为打印本身就是一个耗时的操作,所以用累加的方式精确到纳秒
6:思考是否线程数量越多越好呢?
不是的,线程数量越多,其实彼此间的锁竞争越激烈,优化的空间很小了,4个线程数量再往上提提升不大了
7:守护线程
(1)现象
我们线程执行完毕了,但是进程还没有退出。

(2)关于守护线程
如果一个线程是守护线程(后台线程),那么它的运行状态是不会影响进程结束的
相反,一个线程是非守护线程,它的运行状态是会影响到进程结束的。
通俗一点举例理解:

我们用这行代码创建出来的是非守护线程,当我们这些线程执行的任务结束后,这些线程还是处于整装待发的状态——等你提交任务,所以线程的这种状态就会影响到进程的结束!
//手动干掉非守护线程executorService.shutdown();
这里我们手动干掉创建出来的这些线程,女少!~
相关文章:
【从0做项目】Java搜索引擎(4)——性能优化~烧脑~~~
本篇文章将对项目搜索引擎(1)~(3)进行性能优化,包括测试,优化思路,优化前后对比 目录 一:文件读取 二:实现多线程制作索引 1:代码分析 2:代码…...
【HarmonyOS Next】鸿蒙应用进程和线程详解
【HarmonyOS Next】鸿蒙应用进程和线程详解 一、前言 进程的定义: 进程是系统进行资源分配的基本单位,是操作系统结构的基础。 在鸿蒙系统中,一个应用下会有三类进程: (1) 主进程, (2) ExtensionAbility进程ÿ…...
【前端ES】ECMAScript 2023 (ES14) 引入了多个新特性,简单介绍几个不为人知但却好用的方法
Array.prototype.toSorted() 返回一个新的已排序数组副本,不改变原数组。 let arr [5, 4, 2, 3, 1]; console.log(arr.toSorted()); // [1, 2, 3, 4, 5]Array.prototype.with() 允许根据索引修改数组中的单个元素,并返回新数组。 const arr ["…...
【EndNote】WPS 导入EndNote 21
写在前面:有没有人有激活码,跪求! EndNote,在文献管理和文献引用方面很好用。写文章的时候,使用EndNote引入需要的文献会很方便。我目前用的WPS,想把EndNote的CWYW(Cite While You Write&#…...
网上购物|基于SprinBoot+vue的网上购物系统(源码+数据库+文档)
网上购物系统目录 基于SprinBootvue的网上购物 一、前言 二、系统设计 三、系统功能设计 5.1 管理员功能实现 5.1.1 论坛管理 5.1.2 商品管理 5.1.3 商品评价管理 5.1.4 商品订单管理 5.2 用户功能实现 5.2.1 商品信息 5.2.2 确认下单 5.2.3 商品订单 5.2.4 购物…...
AI 语言模型发展史:统计方法、RNN 与 Transformer 的技术演进
引言 自然语言处理(NLP)是 AI 领域的重要分支,而语言模型(Language Model, LM)是 NLP 的核心技术。语言模型经历了从 统计方法 到 RNN(循环神经网络),再到 Transformer 的演进&…...
Pycharm中查找与替换
1、Edit -> Find -> Find 在当前文件中查找 2、Edit -> Find -> Find in Files 在所有文件中查找 3、Edit -> Find -> Replace 在当前文件中执行替换 4、Edit -> Find -> Replace in Files 在所有文件中执行替换...
有向图的强连通分量: Kosaraju算法和Tarjan算法详解
在上一篇文章中, 我们了解了图的最小生成树算法. 本节我们来学习 图的强连通分量(Strongly Connected Component, SCC) 算法. 什么是强连通分量? 在 有向图 中, 若一组节点内的任意两个节点都能通过路径互相到达(例如 A → B A \rightarrow B A→B 且 B → A B \rightarro…...
mac相关命令
显示和隐藏usr等隐藏文件文件 terminal输入: defaults write com.apple.Finder AppleShowAllFiles YESdefaults write com.apple.Finder AppleShowAllFiles NO让.bashrc每次启动shell自动生效 编辑vim ~/.bash_profile 文件, 加上 if [ -f ~/.bashrc ]; then. ~/.bashrc fi注…...
代码随想录算法训练营第六天| 242.有效的字母异位词 、349. 两个数组的交集、202. 快乐数 、1. 两数之和
242.有效的字母异位词 题目链接:242.有效的字母异位词 文档讲解:代码随想录有效的字母异位词 视频讲解:LeetCode:有效的字母异位词 状态:学会了 思路: 数组其实是简单哈希表。 哈希表用来快速判断元素是否在…...
dify实现分析-rag-关键词索引的实现
概述 在dify中有两种构建索引的方式,一种是经济型,另一种是高质量索引(通过向量数据库来实现)。其中经济型就是关键词索引,通过构建关键词索引来定位查询的文本块,而关键词索引的构建是通过Jieba这个库来完…...
【小白学HTML5】一文讲清常用单位(px、em、rem、%、vw、vh)
html5中,常用的单位有px、em、rem、%、vw、vh(不常用)、cm、m等,这里主要讲解px、em、rem、%、vw。 学习了解:主流浏览器默认的字号:font-size:16px,无论用什么单位,浏览器最终计算…...
Fastgpt学习(5)- FastGPT 私有化部署问题解决
1.☺ 问题描述: Windows系统,本地私有化部署,postgresql数据库镜像日志持续报错" data directory “/var/lib/postgresql/data” has invalid permissions ",“ DETAIL: Permissions should be urwx (0700) or urwx,gr…...
ubuntu下安装TFTP服务器
在 Ubuntu 系统下安装和配置 TFTP(Trivial File Transfer Protocol)服务器可以按照以下步骤进行: 1. 安装 TFTP 服务器软件包 TFTP 服务器通常使用 tftpd-hpa 软件包,你可以使用以下命令进行安装: sudo apt update …...
深入解析 iText 7:从 PDF 文档中提取文本和图像
在现代开发中,PDF 文件的操作是不可避免的一部分。无论是生成报告、解析文档,还是从文件中提取信息,我们常常需要处理 PDF 文件。iText 是一个非常强大的库,广泛应用于 PDF 文件的创建、修改和解析。自 iText 7 发布以来ÿ…...
Rust编程语言入门教程 (六)变量与可变性
Rust 系列 🎀Rust编程语言入门教程(一)安装Rust🚪 🎀Rust编程语言入门教程(二)hello_world🚪 🎀Rust编程语言入门教程(三) Hello Cargo…...
事务--实操演示
目录 一、准备工作 二、在MySQL中操作事务(重点) 第一种方式:使用命令的方式 第二种方式:设置MySQL事务不默认提交的方式 结 三、在JDBC中操作事务(掌握) 第一种方式:使用命令的方式 第…...
PHP是如何并行异步处理HTTP请求的?
文章精选推荐 1 JetBrains Ai assistant 编程工具让你的工作效率翻倍 2 Extra Icons:JetBrains IDE的图标增强神器 3 IDEA插件推荐-SequenceDiagram,自动生成时序图 4 BashSupport Pro 这个ides插件主要是用来干嘛的 ? 5 IDEA必装的插件&…...
【Spring详解一】Spring整体架构和环境搭建
一、Spring整体架构和环境搭建 1.1 Spring的整体架构 Spring框架是一个分层架构,包含一系列功能要素,被分为大约20个模块 Spring核心容器:包含Core、Bean、Context、Expression Language模块 Core :其他组件的基本核心ÿ…...
在 Vue 3 中使用 Lottie 动画:实现一个加载动画
在现代前端开发中,动画是提升用户体验的重要元素之一。Lottie 是一个流行的动画库,它允许我们使用 JSON 文件来渲染高质量的动画。本文将介绍如何在 Vue 3 项目中集成 Lottie 动画,并实现一个加载动画效果。 如果对你有帮助请帮忙点个&#x…...
XCTF-web-easyupload
试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
DAY 47
三、通道注意力 3.1 通道注意力的定义 # 新增:通道注意力模块(SE模块) class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...
【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...
如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...
大学生职业发展与就业创业指导教学评价
这里是引用 作为软工2203/2204班的学生,我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要,而您认真负责的教学态度,让课程的每一部分都充满了实用价值。 尤其让我…...
蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
