专业网站建设费用/熊猫关键词工具

专业网站建设费用,熊猫关键词工具,网站qq登录 开发,WordPress图片无缝背景: 基于java文档的搜索引擎,可以输入搜索词,然后就可以查询出与搜索词相关的文档。该项目的最主要的工作是要构建索引,就是正排和倒排索引。正排索引:根据文档id获取到文档;倒排索引:根据搜…

背景:

基于java文档的搜索引擎,可以输入搜索词,然后就可以查询出与搜索词相关的文档。该项目的最主要的工作是要构建索引,就是正排和倒排索引。正排索引:根据文档id获取到文档;倒排索引:根据搜索词分词结果获取到相关文档。

此项目主要有3个板块:解析板块(Parser),索引板块(Index),服务板块(DocSearchService)

完整代码

1.解析板块

此版块的主要作用就是从本地文档(java在线文档)中加载文件到内存中,然后解析加载到内存中的文件(主要就是解析标题,Url,正文),解析完成后,构造索引结构,最后在进行硬盘的保存(以便于下一次启动时更快,不需要重新构建索引结构)

1.1 加载文件(enumFile)

此处是采用了一个递归的写法,首先找到本地文件路径,然后打开文件,文件中肯定不止一个文件,肯定会有很多子文件,所以需要递归来找出后缀名为html的所有文件,最终放入一个list集合。

1.2 解析文件(pathFile)

此处主要的完成工作是解析标题,解析Url,解析文章内容,然后添加文档(这一步就是构建索引结构,这一步放在索引模块来仔细说明)

1.2.1 解析标题(pathTitle)

搜索结果标题可以从文件名上获取,一般文件名都是文件内存最相关的内容,所以可以直接用字符串的截取获取即可。

1.2.2 解析Url(pathUrl)

解析Url就相当于拼接字符串,在线路径+本地路径的拼接,java在线文档的路径,对于前面来说都是固定的路径:,而后面就是就是本地文档路径了。

1.2.3 解析content(pathContent)

解析内容主要完成的是去掉标签,这里去标签,一是去script标签(标签和内容都不需要),二是去普通标签 。

去标签这里使用了正则表达式进行匹配,去之前还得将文件中的内容读到字符串中。

ps:去script标签必须在去普通标签之前。如果先取普通标签的话,那么就会将script标签给去掉,但是script标签中的内容还在,那后进行去script标签的时候就找不到script,从而script标签中的内容就去不掉了。

1.2.4 添加文档(addDoc)

主要包括构建正排和倒排索引,这部分放到后面索引板块来讲。

1.3 硬盘保存(save)

上述步骤完成之后,正排,倒排索引已经构建完毕了,用ObjectMapper类进行Json字符串的转化然后写入硬盘文件中,这部分放到索引板块细讲。

上述的所有方法都是在run方法中实现的,Parser是一个启动类入口,对于run方法固然有可以用单线程的方式来写,但是这里还实现了多线程的版本:

首先定义一个线程池(newFixedThreadPool:需要设置线程数量,队列是无限大的),队列无限大意味着任务可以一直添加,也就是任务添加完毕过后,但是任务(指的是解析文件:pathFile这一步)可能还没处理完就直接保存了,所以还需要一个计数器(CountDownLatch)来进行计数。设置当前CountDownLatch的大小为文件的数量,每当一个文件被解析完毕后,就减1,直到减为0以后才进行硬盘上的保存。

既然已经使用了多线程,那么在操作集合的时候就需要注意,否则会出现线程安全问题,这部分在后面的索引板块会体现出来。

2.索引板块

此模块的主要作用是创建正排,倒排索引,硬盘保存索引结构,从硬盘上加载索引数据。

2.1 创建正排索引(buildForward)

正排索引就是根据文档id获取文档,那么可以构造一个数组,数组的下标就是文档id,根据文档id就能获取对应文档。

由于上面在进行构造正排索引的时候使用了多线程,所以这里在操作集合的时候,要格外注意,所以在进行修改的时候,应该加锁。

2.2 创建倒排索引(buildInverted)

倒排索引就是根据搜索词寻找出相关文档,相关二字就涉及到权重问题(当前搜索词和文档的关联程度),由于是根据词来搜索文档,所以可以使用一个Map来进行当亲索引的创建。

private void buildInverted(DocInfo docInfo) {class WordCnt {public int titleCount;public int contentCount;}synchronized (object2) {//当前map用来统计分词结果HashMap<String,WordCnt> wordCntHashMap = new HashMap<>();//1.针对文档标题进行分词//已经对大写进行转换成小写//仔细观察,HEllo和hello搜索出来的结果一样,所以遇到这两个单词,应该记两次List<Term> titleTerms = ToAnalysis.parse(docInfo.getTitle()).getTerms();//2.遍历分词结果,统计出每个单词出现的次数for(Term term : titleTerms) {String word = term.getName();WordCnt wordCnt = wordCntHashMap.get(word);if(wordCnt == null) {WordCnt newWordCnt = new WordCnt();newWordCnt.titleCount = 1;wordCntHashMap.put(word,newWordCnt);}else {wordCnt.titleCount += 1;wordCntHashMap.put(word,wordCnt);}}//3.针对正文页进行分词List<Term> contentTerms = ToAnalysis.parse(docInfo.getContent()).getTerms();//4.遍历分词结果,统计每个词出现的次数for(Term term : contentTerms) {String word = term.getName();WordCnt wordCnt = wordCntHashMap.get(word);if(wordCnt == null) {WordCnt newWordCnt = new WordCnt();newWordCnt.contentCount = 1;wordCntHashMap.put(word,newWordCnt);}else {wordCnt.contentCount += 1;wordCntHashMap.put(word,wordCnt);}}//5.把上面的结果汇总到一个hashMap中,最终文档的权重,就设定成标题中出现的次数 * 10 + 正文中出现的次数//6.遍历刚才的hashMap,以此来更新倒排索引中的结构了for(Map.Entry<String,WordCnt> entry : wordCntHashMap.entrySet()) {//先根据这里的词去倒排索引中查一查ArrayList<Weight> invertedList = invertedIndex.get(entry.getKey());if(invertedList == null) {//表示倒排索引中没有相关词的文档//要进行构造ArrayList<Weight> newInvertedList = new ArrayList<>();Weight weight = new Weight();weight.setDocId(docInfo.getDocId());//权重为标题中出现的次数*10 + 正文中出现的次数weight.setWeight(entry.getValue().titleCount*10 + entry.getValue().contentCount);newInvertedList.add(weight);invertedIndex.put(entry.getKey(), newInvertedList);}else {//如果不为空,就把当前这个文档构造出一个weight对象,然后添加到已有的倒排队列中Weight weight = new Weight();weight.setDocId(docInfo.getDocId());weight.setWeight(entry.getValue().titleCount*10 + entry.getValue().contentCount);invertedList.add(weight);}}}
}

注意点:

(1)这个方法中有一个内部类WordCnt,有两个属性(一个用来统计在标题中出现的次数,一个用来统计在文档中出现的次数),并且用标题中出现的次数*10+文档中出现的次数(这里的权重计算比较简单,如果有更好的自行更换)。

(2)在操作map集合的时候,如果是在多线程环境下,没有进行线程安全问题的考虑的话是可能会出现问题的,所以这里也进行了加锁,保障了线程安全。可以看见构建正排和倒排的索引的锁是不相同的,如果使用同一把锁,由于是多线程的,那么正排和倒排索引是不能同时进行的,但如果是使用不同的锁,是可以同时进行的。

2.3 保存索引(save)

这一步主要是使用ObjectMapper类,进行对象到json字符串的转化,然后进行写硬盘。

2.4 加载硬盘(load)

也是使用ObjectMapper类进行,json字符串到对象的转换,然后进行读硬盘。

2.5 新增文档(addDoc)

主要完成的就是构建正排和倒排索引,也就是由buildForward和buildInverted组成。

 3.服务板块

该板块主要完成分词,去停用词,多个分词触发相同文档进行权重合并,结果构造。

3.1 分词

主要使用分词类ToAnalysis对搜索词进行分词,然后去掉停用词。

去停用词的必要性: 

进行去分词结果中的停用词,是为了避免用户输入带有空格,换行符等比较通用的词语,一旦这样的搜索词在被分词后,就当相于没有搜索了,因为只要是一篇比较长的文档都会带有这些符号,那不是全都会被查询出来,所以在进行搜索之前应该要这些停用词(网络上有文档可以自行下载)给去掉。

3.2 触发

针对分词结果进行倒排索引的查询。

3.3 合并

该步是很重要的一步,如果不行合并,那么对于一个文档中出现了多个分词结果,那么在最终返回的结果里面是会有重复的文档的,所以要进行权重的合并。

这里的合并就是多个List的合并,下面设计一个算法:

private List<Weight> mergeResult(List<List<Weight>> source) {//在进行合并的时候,是把多个行合并成一行了//合并的过程中势必是要操作这个二维数组里面的每个元素//操作元素就涉及到“行”“列”这样的概念,要想确定二维数组中的一个元素,就需要明确知道行和列//1.先针对每一行进行排序(按照id进行升序排序),因为必须是有序的,才可以进行下面的权重合并for(List<Weight> weights : source) {weights.sort((o1,o2)->{return o1.getDocId().compareTo(o2.getDocId());});}//2.借助一个优先级队列,进行合并//target表示合并的结果List<Weight> target = new ArrayList<>();//创建优先级队列,按照docId创建小根堆PriorityQueue<Pos> posPriorityQueue = new PriorityQueue<>((o1,o2)->{Weight w1 = source.get(o1.row).get(o1.col);Weight w2 = source.get(o2.row).get(o2.col);return w1.getDocId().compareTo(w2.getDocId());});//初始化队列,把每一行的第一个元素放到优先级队列中for(int i = 0; i < source.size(); i++) {posPriorityQueue.offer(new Pos(i,0));}//循环的取队首元素(也就是相当于这若干行中的最小元素)while (!posPriorityQueue.isEmpty()) {Pos minPos = posPriorityQueue.poll();Weight minWeight = source.get(minPos.row).get(minPos.col);//看看这个取到的weight是否和前一个插入到target中的结果是相同的docId//如果是就合并if(target.size() > 0) {Weight lastWeight = target.get(target.size()-1);if(lastWeight.getDocId().equals(minWeight.getDocId())) {//此时代表是同一个文档,应该进行合并lastWeight.setWeight(lastWeight.getWeight()+minWeight.getWeight());}else {target.add(minWeight);}}else {//当前结果集中没有对应的文档,直接插入即可target.add(minWeight);}//把当前元素处理完成之后,就需要进行把这个元素的对应光标位置往后移,去取当前位置(当前行)的下一个元素Pos newPos = new Pos(minPos.row,minPos.col+1);if(newPos.col >= source.get(newPos.row).size()) {//如果光标移动到超出了这一行的列数的话,就说明到末尾了//到达末尾之后说明当前行已经处理完毕continue;}posPriorityQueue.offer(newPos);}return target;
}

解析:

(1)先对每一个list进行文档id降序排序。

(2)创建一个优先级队列,按照文档id构建小根堆。

(3)把每一个list的第一个weight添加进队列中,然后取出队首的,与结果集合中的最后一个元素进行比较(因为里面结果集合中的每一个元素都是按照文档id进行排序的,所以只需要和结果集合中的最后一个元素比较即可),如果结果集合为空或者是最后一个元素的文档id与当前文档不同的话,说明不同合并直接添加进去即可,否则如果相同就进行权重的合并。

ps:这里的Pos类是为了更好的操作为List<List<Weight>>,这就相当于一个二维数组,并且也方便后面进行下一个元素的比较。

3.4 包装结果

根据查询出来的Weight(带有docId,weight两个属性)权重集合去查正排索引,查询出现的结果保存在最终返回的结果集合中。

可以看到这里还有一个方法GenDesc(生存描述),对于描述可以参考一些网站的搜索结果,搜索发现这些结果的描述中都是包含有关键词的,所以我们可以在正文中先找到关键词,从当前词向后150字符,向前60字符。如果当前位置是小于150字符直接返回所有文章内容即可,否则则按上述约定规则来进行生成描述。

private String GenDesc(String content, List<Term> terms) {int firstPos = 0;//拿分词结果去正文中查看是否存在当前分词for(Term term : terms) {String word = term.getName();//这里的匹配如果只是word的话,进行匹配list的话,会将ArrayList也进行匹配出来,而我们只是想要匹配出list//所以加上空格在两边//注意此处要进行小写转换,否则匹配会出现问题//因为分词的时候就已经进行了小写转换//这里只进行了了单个单词的匹配,如果现在有单词list),如果还是想搜索list就会搜索失败//所以可以采用如下方法进行替换://法一:由于indexOf不支持正则匹配的,所以可以采用第三方库进行正则匹配,但是比较复杂//法二:使用正则表达式将上述形式替换成旁边带有两个空格的形式content = content.replaceAll("\\b" + word +"\\b"," " + word + " ");firstPos= content.toLowerCase().indexOf(" " + word + " ");if(firstPos > 0) {//找到了break;}}if(firstPos == -1) {//代表当前没有找到//此时就直接返回一个空的描述或者也可以取正文的前150字符返回if(content.length() <= 150) {return content;}return content.substring(0,150) + "...";}//找到的第一个字符往前60字符,然后从当前位置往后150个字符为描述int begIndex = firstPos < 50 ? 0 : firstPos - 50;int endIndex = begIndex + 150 < content.length() ? (begIndex + 150) : content.length();String desc = content.substring(begIndex,endIndex) + "...";for (Term term : terms) {String word = term.getName();//当查询词为list的时候,不能把arraylist标红,所以加上空格//(?i)代表不区分大小写进行匹配desc = desc.replaceAll("(?i) " + word + " ","<i>" + word + "</i>");}return desc;
}

这里我们首先使用\b+word+\b进行正则匹配成 空格+word+空格的形式,是防止一旦出现word,的形式,使用空格+word+空格的形式就匹配不到了;使用空格+word+空格的时候,是为了避免想要匹配出list而匹配出Arraylist的情况。

最后还进行了word两边的替换成了i标签,是为了让前面能够选中i标签,从而给这个关键词设置一些形式,比如:标红。

前端略,重点在后端的描述,可以自行前往gitee查看前端代码。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Flink性能指标详解MetricsAnalysis

文章目录 Flink 组成1.JobManager2.TaskManager3.ResourceManager4.Dispatcher5.Client6. Env JobManager MetricsTaskManager Metrics Flink 组成 1.JobManager 管理任务 作业调度&#xff1a;负责接收和调度作业&#xff0c;分配任务到 TaskManager。资源管理&#xff1a;…

< 自用文儿 > CertBot 申请 SSL 证书 使用 challenge 模式 避开防火墙的阻挡

环境&#xff1a; 腾讯 VPS 腾讯会向你销售 SSL &#xff0c; 这个本是免费的。CertBot 默认申请证书要用到 80 端口&#xff0c;会蹭边什么什么条款&#xff0c;备案法律来阻止80端口的通讯&#xff0c;没有网站也一样被阻拦。 通过腾讯买的域名&#xff1a; bestherbs.cn …

树莓派3B+的初步使用

树莓派3B的初步使用 一、安装使用树莓派系统1.将系统写入SD卡2.登录树莓派系统3.用C和Python编译运行hello world 一、安装使用树莓派系统 1.将系统写入SD卡 首先&#xff0c;准备至少16GB大小的SD卡以便装入树莓派系统&#xff0c;将SD卡插入读卡器后连接电脑准备给SD卡写入…

基于Windows11的DockerDesktop安装和布署方法简介

基于Windows11的DockerDesktop安装和布署方法简介 一、下载安装Docker docker 下载地址 https://www.docker.com/ Download Docker Desktop 选择Download for Winodws AMD64下载Docker Desktop Installer.exe 双点击 Docker Desktop Installer.exe 进行安装 测试Docker安装是…

文档处理控件Aspose.Total教程:使用 C# 将 Obsidian Markdown 转换为 OneNote

Obsidian 是一款广泛使用的基于 Markdown 的笔记应用程序。它提供了一种强大而有效的方式来构建和组织想法。用户可以无缝地连接他们的想法&#xff0c;提高清晰度和工作效率。另一方面&#xff0c;OneNote 是 Microsoft 的一款功能强大的笔记应用程序。它还可以帮助用户组织他…

第5章:vuex

第5章&#xff1a;vuex 1 求和案例 纯vue版2 vuex工作原理图3 vuex案例3.1 搭建vuex环境错误写法正确写法 3.2 求和案例vuex版细节分析源代码 4 getters配置项4.1 细节4.2 源代码 5 mapState与mapGetters5.1 总结5.2 细节分析5.3 源代码 6 mapActions与mapMutations6.1 总结6.2…

测试是如何跟进和管理 bug

测试在跟进和管理 Bug定位精确、问题反馈及时、修复闭环高效 三大关键环节中起到了至关重要的作用。Bug定位精确 是整个流程的基础&#xff0c;通过详细记录和复现问题&#xff0c;可以帮助开发团队迅速找出缺陷根源&#xff1b;而及时有效的反馈机制则确保问题不会被遗漏&…

运动控制卡--固高实用

目录 组件 配置参数 编程控制 组件 我手头有固高卡&#xff0c;记录使用。 用运动控制卡 伺服&#xff08;步进&#xff09;电机搭建一个运动控制系统&#xff0c;主要包括&#xff1a;1、控制器 2、端子板 1、控制器 2、端子板 3、伺服&#xff08;步进&#xff09;…

springboot429-基于springboot的教务管理系统(源码+数据库+纯前后端分离+部署讲解等)

&#x1f495;&#x1f495;作者&#xff1a; 爱笑学姐 &#x1f495;&#x1f495;个人简介&#xff1a;十年Java&#xff0c;Python美女程序员一枚&#xff0c;精通计算机专业前后端各类框架。 &#x1f495;&#x1f495;各类成品Java毕设 。javaweb&#xff0c;ssm&#xf…

Best practice-生产环境中加锁的最佳实践

什么是死锁&#xff1f; 场景&#xff1a;图书馆有两个相邻的储物柜&#xff08;柜子A和柜子B&#xff09;&#xff0c;小明和小红需要同时使用这两个柜子才能完成借书流程。 互斥资源 每个柜子只有一把钥匙&#xff0c;且一次只能被一人使用&#xff08;资源不可共享&#x…

黄昏时间户外街拍人像Lr调色教程,手机滤镜PS+Lightroom预设下载!

调色介绍 黄昏时分有着独特而迷人的光线&#xff0c;使此时拍摄的人像自带一种浪漫、朦胧的氛围 。通过 Lr 调色&#xff0c;可以进一步强化这种特质并根据不同的风格需求进行创作。Lr&#xff08;Lightroom&#xff09;作为专业的图像后期处理软件&#xff0c;提供了丰富的调色…

信息系统项目管理师--整合管理

信息系统项目管理师–整合管理

关于tomcat使用中浏览器打开index.jsp后中文显示不正常是乱码,但英文正常的问题

如果是jsp文件就在首行加 “<% page language"java" contentType"text/html; charsetUTF-8" pageEncoding"UTF-8" %>” 如果是html文件 在head标签加入&#xff1a; <meta charset"UTF-8"> 以jsp为例子&#xff0c;我们…

AVM 环视拼接 鱼眼相机

https://zhuanlan.zhihu.com/p/651306620 AVM 环视拼接方法介绍 从内外参推导IPM变换方程及代码实现&#xff08;生成AVM环视拼接图&#xff09;_avm拼接-CSDN博客 经典文献阅读之--Extrinsic Self-calibration of the Surround-view System: A Weakly... (环视系统的外参自…

【哇! C++】类和对象(三) - 构造函数和析构函数

目录 一、构造函数 1.1 构造函数的引入 1.2 构造函数的定义和语法 1.2.1 无参构造函数&#xff1a; 1.2.2 带参构造函数 1.3 构造函数的特性 1.4 默认构造函数 二、析构函数 2.1 析构函数的概念 2.2 特性 如果一个类中什么成员都没有&#xff0c;简称为空类。 空类中…

【五.LangChain技术与应用】【11.LangChain少样本案例模板:小数据下的AI训练】

深夜的创业孵化器里,你盯着屏幕上的医疗AI项目,手里攥着仅有的97条标注数据——这是某三甲医院心内科攒了三年的罕见病例。投资人刚刚发来最后通牒:“下周demo要是还分不清心肌炎和感冒,就撤资!” 这时你需要掌握的不是更多数据,而是让每个样本都变成会复制的孙悟空的毫毛…

2005-2019年各省城镇人口数据

2005-2019年各省城镇人口数据 1、时间&#xff1a;2005-2019年 2、来源&#xff1a;国家统计局、统计年鉴 3、指标&#xff1a;地区、年份、城镇人口(万人) 4、范围&#xff1a;31省 5、指标解释&#xff1a;‌城镇人口是指居住在城市、集镇的人口&#xff0c;主要依据人群…

MCP与RAG:增强大型语言模型的两种路径

引言 近年来&#xff0c;大型语言模型&#xff08;LLM&#xff09;在自然语言处理任务中展现了令人印象深刻的能力。然而&#xff0c;这些模型的局限性&#xff0c;如知识过时、生成幻觉&#xff08;hallucination&#xff09;等问题&#xff0c;促使研究人员开发了多种增强技…

ue5.5崩溃报gpu错误快速修复注册表命令方法

网上已经有很多方法了&#xff0c;自己写了个regedit比处理dos批处理命令&#xff0c;启动时需要win 管理员身份拷贝后&#xff0c;将以下代码&#xff0c;保存为 run.bat格式批处理文件&#xff0c;右键鼠标&#xff0c;在弹出菜单中&#xff0c;选择用管理员身份运行。即可。…

能量石[算法题]

题目来源&#xff1a;第十五届蓝桥杯大赛软件赛省赛Java 大学 B 组&#xff08;算法题&#xff09; 可以参考一下&#xff0c;本人也是比较菜 不喜勿喷&#xff0c;求求求 import java.util.Scanner;​public class Main {public static void main(String[] args) {Scanner s…