【项目日记(二)】搜索引擎-索引制作
❣博主主页: 33的博客❣
▶️文章专栏分类:项目日记◀️
🚚我的代码仓库: 33的代码仓库🚚
🫵🫵🫵关注我带你了解更多项目内容
目录
- 1.前言
- 2.索引结构
- 2.1创捷索引
- 2.2根据索引查询
- 2.3新增文档
- 2.4内存索引保存到磁盘
- 2.5把磁盘索引加载到内存
- 3.性能优化
- 3.1多线程
- 3.2线程安全
- 3.3CountDownLatch类
- 4.总结
1.前言
在上一篇文章中,我们已经介绍了索引解析,那么接下来我们继续完善我们的项目,既然已经有了解析好的索引,那么我们就需要把解析的内容添加到倒排索引和正排索引中。
2.索引结构
创建index类,通过这个类来构建索引结构
基本步骤:
- 用ArrayList创建正排索引
- 用HashMap创建倒排索引
- 1.给定docid在正排索引中,查询详细信息
- 2.给定一个词,在倒排索引中查与这个词的关联文档
- 3.往索引中新增文档
- 4.把内存的索引保存到磁盘
- 5.把磁盘的索引结构保存到内存
2.1创捷索引
正排索引
private ArrayList<DocInfo> forwardIndex=new ArrayList<>();
倒排索引
private HashMap<String,ArrayList<Weight>> invertedIndex=new HashMap<>();
DocInfo类:
public class DocInfo {private int docID;private String title;private String url;private String content;public int getDocID() {return docID;}public void setDocID(int docID) {this.docID = docID;}public String getTitle() {return title;}public void setTitle(String title) {this.title = title;}public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}
}
Weight类:
public class Weight {private int docId;private int weight;public int getDocId() {return docId;}public void setDocId(int docId) {this.docId = docId;}public int getWeight() {return weight;}public void setWeight(int weight) {this.weight = weight;}
}
2.2根据索引查询
//1.根据docId查询文档详情,数组小标就是文档idpublic DocInfo getDocInfo(int docId){return forwardIndex.get(docId);}
//2.给定一个词,查在哪些文档中public List<Weight> getInverted(String term){return invertedIndex.get(term);}
2.3新增文档
public void addDoc(String title,String url,String content){//给正排索引新增和倒排索引都新增信息//构建正排索引DocInfo docInfo=buildForward(title,url,content);//创建倒排索引buildInverted(docInfo);}
在正排索引中添加文档:
private DocInfo buildForward(String title, String url, String content) {DocInfo docInfo=new DocInfo();docInfo.setTitle(title);docInfo.setUrl(url);docInfo.setContent(content);//巧妙设计:docInfoId的下标和数组下标一一对应docInfo.setDocID(forwardIndex.size());forwardIndex.add(docInfo);return docInfo;}
在倒排索引中新增文档
1.需要统计每一个词在文档中的出现次数,在根据次数算出权重
2.首先进行分词操作,统计每一个不同的词在标题中出现的次数
3.再进行分词操作,统计每一个词在正文出现的次数
4.设置权重为标题次数*10+正文次数
private void buildInverted(DocInfo docInfo) {class WordCnt{public int titleCount;public int contentCount;}HashMap<String,WordCnt> wordCntHashMap=new HashMap<>();//1.针对标题进行分词操作List<Term> terms= ToAnalysis.parse(docInfo.getTitle()).getTerms();//2.针对分词结果,统计每个词出现的次数for (Term term:terms){String word=term.getName();WordCnt wordCnt=wordCntHashMap.get(word);if (wordCnt==null){WordCnt newwordCnt=new WordCnt();newwordCnt.titleCount=1;newwordCnt.contentCount=0;wordCntHashMap.put(word,newwordCnt);}else {wordCnt.titleCount+=1;}}//3.针对正文进行分词操作List<Term> terms2=ToAnalysis.parse(docInfo.getContent()).getTerms();//4.遍历分词结果,统计每个词出现的次数for (Term term:terms2){String word=term.getName();WordCnt wordCnt=wordCntHashMap.get(word);if (wordCnt==null){WordCnt newWordCnt=new WordCnt();newWordCnt.titleCount=0;newWordCnt.contentCount=1;wordCntHashMap.put(word,newWordCnt);}else {wordCnt.contentCount+=1;}}//5.设置权重为:标题*10+正文//一个对象必须实现了Iterable接口才能使用for each进行遍历,而Map并没有实现该接口,但Set实现了,所以就把Map转换为Setfor(Map.Entry<String,WordCnt> entry:wordCntHashMap.entrySet()) { List<Weight> invertedList=invertedIndex.get(entry.getKey());if (invertedList==null){ArrayList<Weight> newInvertedList=new ArrayList<>();Weight weight=new Weight();weight.setWeight(entry.getValue().titleCount*10+entry.getValue().contentCount);weight.setDocId(docInfo.getDocID());newInvertedList.add(weight);invertedIndex.put(entry.getKey(),newInvertedList);}else {Weight weight=new Weight();weight.setDocId(docInfo.getDocID());weight.setWeight(entry.getValue().titleCount*10+entry.getValue().contentCount);invertedList.add(weight);} }}
2.4内存索引保存到磁盘
索引当前是存储在内存中的,构造索引的过程是非常耗时的,因此我们就不应该再服务器启动时才去构造索引,通常就把这些耗时的操作,单独执行完成之后,然后再让线上的服务器加载构造好的索引。
我们就把内存中构造的索引结构,给变成一个字符串,然后写入文件即可,这个操作就叫序列化。适应Jackson中的ObjectMapper来完成此操作。
private static String INDEX_PATH="D:/doc_searcher_index/";public void save(){long beg=System.currentTimeMillis();System.out.println("保存索引开始!");File indexPathFile=new File(INDEX_PATH);if(!indexPathFile.exists()){indexPathFile.mkdir();}File forwardIndexFile=new File(INDEX_PATH+"forward.txt");File invertedIndexFile=new File(INDEX_PATH+"inverted.txt");try {//利用ObjectMapperJava对象转换为JSON格式//从内存中读取forwardIndex保存到forwardIndexFileobjectMapper.writeValue(forwardIndexFile,forwardIndex);//从内存中读取nvertedIndex保存到invertedIndexFileforwardIndexFileobjectMapper.writeValue(invertedIndexFile,invertedIndex);}catch (IOException e) {e.printStackTrace();}long end=System.currentTimeMillis();System.out.println("保存索引完成!消耗时间:"+(end-beg)+"ms");}
2.5把磁盘索引加载到内存
public void load(){long beg=System.currentTimeMillis();System.out.println("加载索引开始!");File forwardIndexFile=new File(INDEX_PATH+"forward.txt");File invertedIndexFile=new File(INDEX_PATH+"inverted.txt");try {forwardIndex=objectMapper.readValue(forwardIndexFile, new TypeReference<ArrayList<DocInfo>>() {});invertedIndex=objectMapper.readValue(invertedIndexFile, new TypeReference<HashMap<String, ArrayList<Weight>>>() {});}catch (IOException e){e.printStackTrace();}long end=System.currentTimeMillis();System.out.println("加载引擎结束消耗时间:"+(end-beg)+"ms");}
parser相当于制作索引的入口,对应到一个可执行的程序
index相当于实现了索引的数据结构,提供一些Api
接下来我们就在parser里面调用对应的api
在parser类中解析完Html文件时,应添加到索引中
private void parseHTML(File f) {//1.解析HTML标题String title=parseTitle(f);//2.解析HTML的URLString url=parseUrl(f);//3.解析HTML的正文long beg=System.nanoTime();//String content=parseContent(f);String content=parseContentByRegex(f);long mid=System.nanoTime();//把解析出来的信息加载到索引index.addDoc(title,url,content); }
在添加完索引之后,应该把索引保存到磁盘
public void run() {long beg=System.currentTimeMillis();System.out.println("索引制作开始");//1.枚举出INPUT_PATH下的所有html文件ArrayList<File> fileList=new ArrayList<>();enumFile(INPUT_PATH,fileList);//2.解析文档内容for (File f:fileList){System.out.println("开始解析"+f.getAbsolutePath()+"....");parseHTML(f);}//3.把内存构造的索引保存到磁盘index.save();long end=System.currentTimeMillis();System.out.println("索引制作结束!消耗时间:"+(end-beg)+"ms");}
3.性能优化
此时我们已经完成了文档解析和索引制作模块,那么我们进行验证
文档内容正确生成:
但我们观察索引制作的时间:一个消耗了19973ms就是19s,花费的时间是比较长的,那么有什么办法提高效率呢?方法当然是有的,首先我们得清楚具体是哪一个步骤拖慢了执行效率,我们来分析代码:
可以看到解析文档的时候从磁盘读文件,循环遍历文件操作,那么显然效率是非常慢的,既然一个线程串行执行效率非常慢,那么我们就采用多线程并发执行来提高效率。
3.1多线程
我们可以使用创建一个线程池来实现并发操作。通过submit往线程池中提价任务,操作极快(只是把Runnable对象放入阻塞队列中)。
把代码改进成多线程的版本,线程池中的线程数目具体设置成多少才合适呢?最好通过实验来确定。
public void run() {long beg=System.currentTimeMillis();System.out.println("索引制作开始");//1.枚举出INPUT_PATH下的所有html文件ArrayList<File> fileList=new ArrayList<>();enumFile(INPUT_PATH,fileList);ExecutorService executorService= Executors.newFixedThreadPool(6);//2.解析文档内容for (File f:files){executorService.submit(new Runnable() {@Overridepublic void run() {System.out.println("解析:"+f.getAbsolutePath());parseHTML(f); }});}//3.把内存构造的索引保存到磁盘index.save();long end=System.currentTimeMillis();System.out.println("索引制作结束!消耗时间:"+(end-beg)+"ms");}
3.2线程安全
我们既然引入了多线程就要考虑线程安全问题,要注意修改操作和读写操作。当多个线程同时尝试修改同一个共享数据时,需要确保数据的一致性,避免出现竞态条件。读写操作:如果一个线程在读取共享数据的同时另一个线程在修改该数据,可能导致读取到不一致或无效的数据。
那么我们就需要对程序进行加锁操作:
3.3CountDownLatch类
添加锁虽然解决了线程安全问题,依然有新的问题,那就是在所有文件提交完成后就会立即执行save()操作,但是可能文件解析还没有完成。为了解决这样的问题,我们就引入 CountDownLatch类。
CountDownLatch类类似于跑步比赛的裁判,只有所有的选手都撞线了,就认为这场比赛结束了。再构造 CountDownLatch的时候指定一下比赛选手的个数,每个选手撞线都要通知一下countDown(),通过await来等待所有的选手都撞线完毕才执行save()操作。
public void runByThread(){long beg=System.currentTimeMillis();System.out.println("索引开始制作");//1.枚举出INPUT_PATH下的所有html文件ArrayList<File> files=new ArrayList<>();enumFile(INPUT_PATH,files);//2.解析文档内容CountDownLatch latch=new CountDownLatch(files.size());ExecutorService executorService= Executors.newFixedThreadPool(6);for (File f:files){executorService.submit(new Runnable() {@Overridepublic void run() {System.out.println("解析:"+f.getAbsolutePath());parseHTML(f);latch.countDown();}});}try {//await会阻塞,把所有选手都调用countDown以后才会继续执行latch.await();} catch (InterruptedException e) {throw new RuntimeException(e);}//手动的把线程池里面的线程杀掉executorService.shutdown();//3.把内存构造的索引保存到磁盘index.save();long end=System.currentTimeMillis();System.out.println("索引制作结束!消耗时间:"+(end-beg)+"ms");System.out.println("t1:"+t1+"t2"+t2);}
4.总结
这篇文章主要完成了索引制作模块,以及进行了性能优化,在下一篇文章中将进行搜索模块的制作。
下期预告:项目日记(三)
相关文章:
【项目日记(二)】搜索引擎-索引制作
❣博主主页: 33的博客❣ ▶️文章专栏分类:项目日记◀️ 🚚我的代码仓库: 33的代码仓库🚚 🫵🫵🫵关注我带你了解更多项目内容 目录 1.前言2.索引结构2.1创捷索引2.2根据索引查询2.3新增文档2.4内存索引保存到磁盘2.5把…...
K 近邻、K-NN 算法图文详解
1. 为什么学习KNN算法 KNN是监督学习分类算法,主要解决现实生活中分类问题。根据目标的不同将监督学习任务分为了分类学习及回归预测问题。 KNN(K-Nearest Neihbor,KNN)K近邻是机器学习算法中理论最简单,最好理解的算法…...
Eclipse + GDB + J-Link 的单片机程序调试实践
Eclipse GDB J-Link 的调试实践 本文介绍如何创建Eclipse的调试配置,如何控制调试过程,如何查看修改各种变量。 对 Eclipse 的要求 所用 Eclipse 应当安装了 Eclipse Embedded CDT 插件。从 https://www.eclipse.org/downloads/packages/ 下载 Ecli…...
前端代码生成辅助工具
1,Axure Axure设计的界面如何生成HTML文件 https://blog.csdn.net/qq_43279782/article/details/112387511 Axure 生成HTML 文件,并用Chrome打开 https://blog.csdn.net/qq_30718137/article/details/80621025 2,OpenUI [开源] OpenUI …...
静态库与动态库总结
一、库文件和头文件 所谓库文件,可以将其理解为压缩包文件,该文件内部通常包含不止一个目标文件(也就是二进制文件)。 值得一提的是,库文件中每个目标文件存储的代码,并非完整的程序,而是一个…...
深入解析tcpdump:网络数据包捕获与分析的利器
引言 在网络技术日新月异的今天,网络数据包的捕获与分析成为了网络管理员、安全专家以及开发人员不可或缺的技能。其中,tcpdump作为一款强大的网络数据包捕获分析工具,广泛应用于Linux系统中。本文将从技术人的角度,详细分析tcpdu…...
【漏洞复现】科立讯通信有限公司指挥调度管理平台uploadgps.php存在SQL注入
0x01 产品简介 科立讯通信指挥调度管理平台是一个专门针对通信行业的管理平台。该产品旨在提供高效的指挥调度和管理解决方案,以帮助通信运营商或相关机构实现更好的运营效率和服务质量。该平台提供强大的指挥调度功能,可以实时监控和管理通信网络设备、…...
什么是自然语言处理(NLP)?详细解读文本分类、情感分析和机器翻译的核心技术
什么是自然语言处理? 自然语言处理(Natural Language Processing,简称NLP)是人工智能的一个重要分支,旨在让计算机理解、解释和生成人类的自然语言。打个比方,你和Siri对话,或使用谷歌翻译翻译一…...
【linux】gcc快速入门教程
目录 一.gcc简介 二.gcc常用命令 一.gcc简介 gcc 是GNU Compiler Collection(GNU编译器套件)。就是一个编译器。编译一个源文件的时候可以直接使用,但是源文件数量太多时,就很不方便,于是就出现了make 工具 二.gcc…...
【多维动态规划】Leetcode 97. 交错字符串【中等】
交错字符串 给定三个字符串 s1、s2、s3,请你帮忙验证 s3 是否是由 s1 和 s2 交错 组成的。 两个字符串 s 和 t 交错 的定义与过程如下,其中每个字符串都会被分割成若干 非空 子字符串 子字符串 是字符串中连续的 非空 字符序列。 s s1 s2 … snt…...
【JavaScript脚本宇宙】精通前端开发:六大热门CSS框架详解
前端开发的利器:深入了解六大CSS框架 前言 在现代Web开发中,选择适合的前端框架和工具包是构建高效、响应式和美观的网站或应用程序的关键。本文将详细介绍六个广受欢迎的CSS框架:Bootstrap、Bulma、Tailwind CSS、Foundation、Materialize…...
开发技术-Java集合(List)删除元素的几种方式
文章目录 1. 错误的删除2. 正确的方法2.1 倒叙删除2.2 迭代器删除2.3 removeAll() 删除2.4 removeIf() 最简单的删除 3. 总结 1. 错误的删除 在写代码时,想将其中的一个元素删除,就遍历了 list ,使用了 remove(),发现效果并不是想…...
c++ 递归
递归函数是指在函数定义中调用自身的函数。C语言也支持递归函数。 下面是一个使用递归函数计算阶乘的例子: #include <iostream> using namespace std;int factorial(int n) {// 基本情况,当 n 等于 0 或 1 时,阶乘为 1if (n 0 || n…...
RedHat9 | podman容器
1、容器技术介绍 传统问题 应用程序和依赖需要一起安装在物理主机或虚拟机上的操作系统应用程序版本比当前操作系统安装的版本更低或更新两个应用程序可能需要某一软件的不同版本,彼此版本之间不兼容 解决方式 将应用程序打包并部署为容器容器是与系统的其他部分…...
边缘计算项目有哪些
边缘计算项目在多个领域得到了广泛的应用,以下是一些典型的边缘计算项目案例: 1. **智能交通系统**:通过在交通信号灯、监控摄像头等设备上部署边缘计算,可以实时分析交通流量,优化交通信号控制,减少拥堵&…...
计算fibonacci数列每一项时所需的递归调用次数
斐波那契数列是一个经典的数列,其中每一项是前两项的和,定义为: [ F(n) F(n-1) F(n-2) ] 其中,( F(0) 0 ) 和 ( F(1) 1 )。 对于计算斐波那契数列的第 ( n ) 项,如果使用简单的递归方法,其时间复杂度是…...
【教学类65-05】20240627秘密花园涂色书(中四班练习)
【教学类65-03】20240622秘密花园涂色书03(通义万相)(A4横版1张,一大 68张纸136份)-CSDN博客 背景需求: 打印以下几款秘密花园样式(每款10份)给中四班孩子玩一下,看看效果 【教学类…...
Python 学习之基础语法(一)
Python的语法基础主要包括以下几个方面,下面将逐一进行分点表示和归纳: 一、基本语法 1. 注释 a. 单行注释:使用#开头,例如# 这是一个单行注释。 b. 多行注释:使用三引号(可以是三个单引号或三个双引号&…...
日志分析-windows系统日志分析
日志分析-windows系统日志分析 使用事件查看器分析Windows系统日志 cmd命令 eventvwr 筛选 清除日志、注销并重新登陆,查看日志情况 Windows7和Windowserver2008R2的主机日志保存在C:\Windows\System32\winevt\Logs文件夹下,Security.evtx即为W…...
【ARM】MDK工程切换高版本的编译器后出现error A1137E报错
【更多软件使用问题请点击亿道电子官方网站】 1、 文档目标 解决工程从Compiler 5切换到Compiler 6进行编译时出现一些非语法问题上的报错。 2、 问题场景 对于一些使用Compiler 5进行编译的工程,要切换到Compiler 6进行编译的时候,原本无任何报错警告…...
深入 SSH:解锁本地转发、远程转发和动态转发的潜力
文章目录 前言一、解锁内部服务:SSH 本地转发1.1 什么是 SSH 本地转发1.2 本地转发应用场景 二、打开外部访问大门:SSH 远程转发2.1 什么是 SSH 远程转发2.2 远程转发应用场景 三、动态转发:SSH 让你拥有自己的 VPN3.1 什么是 SSH 动态转发3.…...
python如何把一个函数的返回值,当成这个函数的参数值
python如何把一个函数的返回值,当成这个函数的参数值 1. 递归调用 递归是一种函数自己调用自己的方法。在递归调用中,你可以将前一次调用的返回值作为下一次调用的参数。 def recursive_function(x):# 函数逻辑if 条件满足:return 结果else:return rec…...
【融合ChatGPT等AI模型】Python-GEE遥感云大数据分析、管理与可视化及多领域案例应用
随着航空、航天、近地空间遥感平台的持续发展,遥感技术近年来取得显著进步。遥感数据的空间、时间、光谱分辨率及数据量均大幅提升,呈现出大数据特征。这为相关研究带来了新机遇,但同时也带来巨大挑战。传统的工作站和服务器已无法满足大区域…...
SpringBoot: Eureka入门
1. IP列表 公司发展到一定的规模之后,应用拆分是无可避免的。假设我们有2个服务(服务A、服务B),如果服务A要调用服务B,我们能怎么做呢?最简单的方法是让服务A配置服务B的所有节点的IP,在服务A内部做负载均衡调用服务B…...
Typescript 【实用教程】(2024最新版)含类型声明,类型断言,函数,接口,泛型等
简介 TypeScript 是 JavaScript 的超集,是 JavaScript(弱类型语言) 的强类型版本。 拥有类型机制文件后缀 .tsTypescript type ES6TypeScript 和 JavaScript 的关系类似 less 和 css 的关系TypeScript对 JavaScript 添加了一些扩展&#x…...
智慧校园-实训管理系统总体概述
智慧校园实训管理系统,专为满足高等教育与职业教育的特定需求而设计,它代表了实训课程管理领域的一次数字化飞跃。此系统旨在通过革新实训的组织结构、执行流程及评估标准,来增强学生的实践操作技能和教师的授课效率,为社会输送具…...
如何用GPT开发一个基于 GPT 的应用?
原文发自博客:GPT应用开发小记 如何开发一个基于 GPT 的应用?答案就在问题里,那就是用 GPT 来开发基于 GPT 的应用。本文以笔者的一个开源项目 myGPTReader 为例,分享我是如何基于 GPT 去开发这个系统的,这个系统的功能…...
大数据生态体系中各组件的区别面试题(更新)
一、MapReduce与Spark有什么区别? 1、处理方式: MapReduce基于磁盘处理数据,将中间结果保存到磁盘中,减少了内存占用,计算速度慢。 基于内存处理数据,将计算的中间结果保存到内存中,计算速度快。2、资源申请方式&…...
数字信号处理实验一(离散信号及离散系统的MATLAB编程实现)
实验要求: 离散信号及离散系统的MATLAB编程实现(2学时) 要求: 编写一程序,输出一定长度(点数),具有一定幅度、(角)频率和初始相位的实(或复&…...
数字图像处理专栏——introduction
Introduction: 数字图像处理技术是我在深入学习研究的方向之一。本科期间跟随导师做基于AndroidOpenCV的病虫识别app,因此入门,我也对该部分知识有进一步探索的欲望,但更多的是因该脚踏实地一步步记录,一步步成长。 本篇从数字图…...
有wordpress使用经验/google搜索引擎
计算机职称考试金山文字2005考试大纲下面yjbys考试网小编为大家分享了计算机职称考试金山文字2005考试大纲,仅供参考。第一章 金山文字2005基本操作一、内容提示金山文字2005是WPS Office 2005的文字处理组件,具有强大的文字处理功能。本章介绍金山文字2…...
北京网站建设 网络安全/sem是做什么的
网站开发人员经常需要检查网站的兼容性,在各种浏览器中,以确保网站的作品完美地在所有的浏览器。为此,有大量的跨浏览器测试工具,可以帮助开发人员检查他们的网站之前,他们的网站是。 这里是全集合的一些最好的免费以及…...
网页界面设计要中重点掌握/网站快速优化排名软件
生活中,经常会因为有多个账号需要双开或者多开的情况,而目前大部分的安卓手机都可以实现应用多开的功能,对于需要多账号登录的用户来说很方便。但是还有一部分人也需要用电脑版的微信进行日常的使用和办公,然而电脑版的微信并没有…...
wordpress手机端滑动侧栏/百度快照投诉
传送门 在最优的情况下,序列\(s_1,s_2,...,s_k\)中,\(s_i (i \in [2 , k])\)一定会是\(s_{i-1}\)的一个\(border\),即\(s_i\)同时是\(s_{i-1}\)的前缀和后缀,否则一定可以通过减去\(s_{i-1}\)的一个前缀和后缀使得满足条件。 对原…...
模板网站zencart/服务营销论文
今天看到了这个贪心算法的摇摆序列,确实挺难的,自己就没有按照老师所用到的c语言代码编写,并且用了自己所能够理解的方式进行编写代码,代码实现起来有点多余,但是最终还是实现了,大家可以自己进行优化&…...
wordpress 仿百度模板/网站推广app
现在的开发人员都趋向于使用新的编程语言,那么旧的编程语言呢?它们的前途一般是这样两种:仍然可以使用,但逐渐不受大家欢迎;直接完全死去。之前我们对了《5 种将死的编程语言》和之前5中将死的编程语言相反,…...