针对Java文档的搜索引擎

2023-10-29

项目介绍

       本项目是一个基于SpringBoot的针对Java文档的站内搜索引擎,当用户在页面上输入查询词后,就会快速的查询出相关的文档然后跳转至在线页面,弥补了Java在线文档中没有搜索查询的缺陷

因为当前这个项目只是针对 Java 官方文档设计的一个搜索引擎,总共也就一万多条记录,所以我们可以提前将 Java官方文档下载到本地, 然后对其进行预处理。
此处的预处理就是构建正排索引,倒排索引

  1. 正排索引 : 给一个 id,可以查出对应文档的基本信息,包括文档标题,文档正文,文档 URL
  2. 倒排索引 : 给一个查询词,查那些文档和这个词有关联,文档肯定很多,所以返会一个List

项目模块划分及分析

1.索引模块

  • 整个索引模块主要涉及2个类 . Parser类主要负责解析文件 , Index类主要负责把在内存中构造好的索引数据结构,保存到指定的文件中

Parser 类核心业务

在这里插入图片描述

  1. 根据指定的路径,使用 enumFile() 枚举 出该路径中所有的 html 文件,这个过程需要把所有子目录中的文件都能获取到
  private  void enumFile(String inputPath, ArrayList<File> fileList) {
        File rootPath = new File(inputPath);

        //使用 listFiles 只能看到一级目录,看不到子目录里的内容
        //要想看到子目录里的内容,还要进行递归
        File[] files = rootPath.listFiles();//listFiles 能够获取到 rootPath 当前目录下所包含的文件/目录
        for(File f : files){
            //根据当前 f 的类型,是否要来递归
            //如果 f 是一个普通文件,就把 f 加入到 listFiles 结果中
            //如果 f 是一个目录,就递归的调用 enumFile 这个方法,进一步获取子目录中的内容
            if(f.isDirectory()){
                enumFile(f.getAbsolutePath(),fileList);
            }else{
                if(f.getAbsolutePath().endsWith(".html")){
                    //有些是图片,文字等等,js文件
                    //找到以 .html结尾的文件,然后在添加
                    fileList.add(f);
                }
            }
            }
        }
  1. 针对上面罗列出的文件的路径,打开文件,读取文件内容并进行解析parseHTML(),并构建索引
  2. 把解析的结果调用index里面的addDoc 方法,构建倒排和正排索引
  private  void parseHTML(File f) {
        //1.解析标题
        String title = parseTitle(f);
        //2.解析对应的URL
        String url = parseUrl(f);
        //3.解析对应的正文(描述)
        //String content = parseContent(f);
        String content = parseContentByRegex(f);
        //4.把解析出来的信息,加入到索引当中
        index.addDoc(title,url,content);
    }
  1. 保存索引

在这里插入图片描述

  1. 解析URL
    在这里插入图片描述

Index 核心业务

在这里插入图片描述

  1. 构建正排索引 ,给定一个id,查对应文档的详细信息返回DocInfo类的对象
    在这里插入图片描述
public class DocInfo {

    private int docId;
    private String title;
    private  String url;
    private  String content;
}

在这里插入图片描述

  1. 构建倒排索引,给定一个词,查那些文档和这个词有关联,文档肯定很多,所以返会一个List
    在这里插入图片描述
 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){
            //先判定 term 是否存在
            String word = term.getName();
            WordCnt wordCnt = wordCntHashMap.get(word);
            if(wordCnt == null){
                //如果不存在,就创建一个新的键值对,插入进去,
                WordCnt newWordCnt = new WordCnt();
                newWordCnt.titleCount = 1;//标题中出现的次数+1
                newWordCnt.contentCount = 0;
                wordCntHashMap.put(word,newWordCnt);
            }else{
                //如果存在,就找到之前的值,然后把对应的 titleCount +1
                wordCnt.titleCount += 1;
            }
        }
        //3.针对正文进行分词
        terms = ToAnalysis.parse(docInfo.getContent()).getTerms();
        //4.遍历分词结果,统计每个词出现的次数
        for(Term term:terms){
            //先判定 term 是否存在
            String word = term.getName();
            WordCnt wordCnt = wordCntHashMap.get(word);
            if(wordCnt == null){
                //如果不存在,就创建一个新的键值对,插入进去,
                WordCnt newWordCnt = new WordCnt();
                newWordCnt.titleCount = 0;
                newWordCnt.contentCount = 1;//文章中出现的次数+1
                wordCntHashMap.put(word,newWordCnt);
            }else{
                //如果存在,就找到之前的值,然后把对应的 titleCount +1
                wordCnt.contentCount += 1;
            }
        }
        //5.把上面的结果汇总到一个HashMap中,最终文档的权重,就设定成 标题中出现的次数*10+正文中出现的次数
        //6.遍历这个HashMap,依次更新倒排索引中的结构
        for (Map.Entry<String ,WordCnt> entry: wordCntHashMap.entrySet()) {
            //Map不好遍历,先把Map转成Set然后才可以进行遍历
            synchronized (locker2){
                List<Weight>  invertedList = invertedIndex.get(entry.getKey());//一个值
                if(invertedList == null){
                    //如果为空,插入新的键值对
                    ArrayList<Weight> newInvertedList = new ArrayList<>();
                    //把新的文档(当前DocInfo)构造成 search.Weight 对象,插入进来
                    Weight  weight = new Weight();
                    weight.setDocId(docInfo.getDocId());
                    weight.setWeight(entry.getValue().titleCount * 10 + entry.getValue().contentCount);
                    newInvertedList.add(weight);
                    invertedIndex.put(entry.getKey(),newInvertedList);
                }else {
                    //如果非空,就把当前文档,构造成一个 search.Weight 对象,
                    Weight  weight = new Weight();
                    weight.setDocId(docInfo.getDocId());
                    weight.setWeight(entry.getValue().titleCount * 10 + entry.getValue().contentCount);
                    invertedList.add(weight);
                }
            }
        }
    }

  • 添加文档,parser类在构建索引的时候调用该方法
    在这里插入图片描述

  • 查正排

  • 因为我们在存放正排索引时 , 已经将文章按照顺序保存在 ArrayList 中 , 所以可以直接根据get(i)方法获取到文章信息

public  DocInfo getDocInfo(int docId){
        return forwardIndex.get(docId);
    }
  • 查倒排
  • 直接通过 key 获取 value , 返回 Weight 类 数组 , 每个 Weight 存放该词所在的文章及在这篇文章中的权重(或者说与这篇文章的关联性)

  • 我们认为一个词 在标题中出现比在文章中出现重要的多,最终文档的权重,就设定成

  • weight = 标题中出现的次数 *20 + 正文中出现的次数

 public List<Weight> getInverted(String term){
        return invertedIndex.get(term);
    }

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

多线程制作索引

  • 创建固定数量的线程池
  • 使用submit 提交任务到线程池中

提交任务,是否存在submit已经提交完任务了,但是线程池还没有把文档解析完
必须要等待所有的线程把所有的文档任务都处理完,才能保存索引

  • 使用CountDownLatch方法,引入计数器

在这里插入图片描述

构建索引的核心是解析HTML文件

  • 构建正排可能会有几个线程同时向1个位置添加,因此会出现线程安全问题
    构建倒排更新倒排索引的时候,也有可能会有线程安全问题
  • 如果直接把Synchronized 加到parseHTML,那只能顺序的执行解析标题,解析正文,添加索引,速度又慢了,并且解析标题,和解析正文,并不会有线程安全问题,
    或者直接锁到 addDoc()这样的方法上,锁的粒度太粗了,并发程度不高
    都针对index 实例加锁,构建正排的时候,就不能构建倒排
  • 构建正排和构建倒排,操作不同的对象,不应该产生锁竞争,因此加上不同的锁

2.搜索模块

调用索引模块,来完成搜索的核心过程

  1. 分词:针对用户输入的查询词进行分词,(因为用户输入的查询词,可能不是一个词,可能是一句话)
  2. 触发:拿着每个分词结果, 去倒排索引中查,找到具有相关性的文档(调用Index类里面的查倒排的方法)
 List<Weight> allTermResult = new ArrayList<>();
        for(Term term : terms){
            String word = term.getName();//获取每个词的内容
            List<Weight> invertedList = index.getInverted(word);
            if(invertedList == null){
                continue;
            }
            allTermResult.addAll(invertedList);
        }
  1. 排序,针对上面触发的结果,按照相关性,降序排序
    在这里插入图片描述

  2. 包装结果,根据排序后的结果,依次去查正排,获取到每个文档的详细信息,保装成一定结构的数据返回去

 List<Result> results = new ArrayList<>();
        for (Weight weight : allTermResult){
            DocInfo docInfo = index.getDocInfo(weight.getDocId());
            Result result = new Result();

            result.setTitle(docInfo.getTitle());
            result.setUrl(docInfo.getUrl());
            result.setDesc(GenDesc(docInfo.getContent(),terms));

            results.add(result);
        }
        return results;

分词

  • 用户在搜索引擎中输入的可能是一个词,也可能是一句话,并不会按照你的理想情况进行输入,因此要进行分词
  • 针对标题进行分词,针对文档进行分词
  • 遍历分词结果,统计每个词出现的次数

        //1.针对文档标题进行分词
       List<Term>  terms = ToAnalysis.parse(docInfo.getTitle()).getTerms();
        //2.遍历分词结果,统计每个词出现的次数
        for(Term term:terms){
            //先判定 term 是否存在
            String word = term.getName();
            WordCnt wordCnt = wordCntHashMap.get(word);
            if(wordCnt == null){
                //如果不存在,就创建一个新的键值对,插入进去,
                WordCnt newWordCnt = new WordCnt();
                newWordCnt.titleCount = 1;//标题中出现的次数+1
                newWordCnt.contentCount = 0;
                wordCntHashMap.put(word,newWordCnt);
            }else{
                //如果存在,就找到之前的值,然后把对应的 titleCount +1
                wordCnt.titleCount += 1;
            }
        }

生成描述

  • 前端页面展示的有标题,描述,URL
    描述就是正文的一段摘要,摘要来自于正文,可以获取到所有查询词的分词结果,遍历分词结果,看哪个结果在正文中出现
  • 针对当前这个文档 来说,不一定会包含所有的分词结果
    就针对这个被包含的分词结果,去正文中查找,找到对应的位置,以这个词的位置为中心,往前截取40个字符,作为描述的开始,再从描述开始,往后截取190个字符,作为整个描述
  • 这个数字不是唯一的,可以根据情况来定
 private String GenDesc(String content, List<Term> terms) {
        //先遍历分词结果,看看那个结果是在 content 中存在
        int firstPos = -1;
        for(Term term : terms){
            String word = term.getName();
            firstPos = content.toLowerCase().indexOf(" "+ word+" ");//正文转成小写
            if(firstPos >= 0){
                break;
            }
        }
        if(firstPos == -1){
            //所有的分词结果都不在正文中存在
            if(content.length() > 160){
                return content.substring(0,160) + "...";
            }
            return content;
        }
        String desc ="";
        int descBegin = firstPos < 60 ? 0:firstPos-60;//找描述的起始位置,firstPos 小于 60直接返回0,否则向前找60个字符
        if(descBegin + 160 > content.length()){
            //  descBegin + 160 大于正文的长度了,直接就是从它开始一直截到末尾
            desc = content.substring(descBegin);
        }else{
            //否则的话,就往后再找160个字符
            desc = content.substring(descBegin,descBegin + 160) + "...";
        }

停用词

  • 当我们的查询词为array空格list的时候,分词结果为:array,空格,list
    因此会出现,不含array,list 但是包含空格的搜索结果,这样的就叫停用词,就是很高频,但是又没有什么意义,比如,a,is,hava,
  • 把暂停词加载到内存的HashSet里面
    针对查询词分词以后,使用暂停词表处理分词后的查询词,遍历分词结果,在暂停词里面查找
  • 如果该词在暂停词表里面存在,则该词无意义删掉,
    如果不存在,就把它加入新的分词结果List里面
    在这里插入图片描述

3.Web模块

  • 即就是前端页面,编写一个简单的页面,展示搜索结果,点击搜索结果标题能跳转到对应的 Java API 文档页面

展示

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

针对Java文档的搜索引擎 的相关文章

随机推荐

  • HTML属性

    目录 HTML 属性 属性实例 更多 HTML 属性实例 属性例子 1 属性例子 2 属性例子 3 HTML 提示 使用小写属性 始终为属性值加引号 HTML 属性参考手册 一个完整的实例 属性为 HTML 元素提供附加信息 HTML 属性
  • 安装Firefly错误-Unable to find vcvarsall.bat

    晚上尝试安装Firefly时 在安装twisted时出现错误而退出 根据错误提示如下 error Setup script exited with error Microsoft Visual C 9 0 is required Unabl
  • 5、基于注解的AOP配置

    一 开启注解支持 1 概述 1 Spring AOP如同IoC一样支持基于XML和基于注解两种配置方式 基于注解所需的依赖和基于XML所需的依赖一致 其中spring context包含了Spring IoC Spring AOP等核心依赖
  • 改进的北方苍鹰算法优化VMD参数,最小包络熵、样本熵、信息熵、排列熵(适应度函数可自行选择,一键修改)包含MATLAB源代码...

    今天给大家带来一期由改进的北方苍鹰算法 SCNGO 优化VMD的两个参数 同样以西储大学数据集为例 选用105 mat中的X105 BA time mat数据中1000个数据点 没有数据的看这篇文章 西储大学轴承诊断数据处理 matlab免
  • linux之数据库操作,Linux之MySQL数据库常用操作

    8种机械键盘轴体对比 本人程序员 要买一个写代码的键盘 请问红轴和茶轴怎么选 因为最近在使用云服务器部署项目 所以也难免要在服务器上使用MySQL 所以就想把MySQL常使用记录下来 以便日后查看并熟悉 也希望能够对大家有所帮助 首先你需要
  • Linux内核TCP参数调优全面解读

    Linux内核TCP参数调优全面解读 前言 TCP 性能的提升不仅考察 TCP 的理论知识 还考察了对于操心系统提供的内核参数的理解与应用 TCP 协议是由操作系统实现 所以操作系统提供了不少调节 TCP 的参数 如何正确有效的使用这些参数
  • redis配置超时时间

    redis配置超时时间 cd usr local redis etc vim redis conf 找到timeout 0 改为 timeout 30
  • 在sublime text中用终端来编译运行java

    在sublime text中用终端来编译运行java sublime text是一款非常优秀的轻量编辑器 今天就来给大家演示下如何用sublime来编译运行java 安装Terminus 首先需要在sublime中安装terminus这个终
  • JAVA学习之——计算机网络(背诵版)

    1 简述OSI七层协议 物理层 主要解决两台物理机之间的通信 通过二进制比特流的传输来实现 二进制数据表现为电流电压上的强弱 到达目的地再转化为二进制机器码 网卡 集线器工作在这一层 在局部局域网上传送数据帧 它负责管理计算机通信设备和网络
  • 【转】C语言的学习路线

    http topic csdn net u 20110922 08 391f0557 6bbc 490d 8394 b7dede44fa0e html seed 1927482974 r 75671683 r 75671683 UNIX下C
  • Java中判断两个类是否相等

    Java中判断两个类是否相等 当有参数的类生成对象时 当两个对象给的参数相同时 会将第二个对象指向第一个对象的地址 如实例中展示 会输出true public class test1 public static void main Stri
  • GLSL 程序与使用

    核心模式OpenGL GLSL程序 GLSL程序简介和在QT中向GLSL程序变量传递数据 数据类型 包含基本数据类型 int float double uint bool 两种容器类型 向量 Vector 标识符 含义 vecn n个flo
  • 宏任务与微任务

    首先执行顺序 同步任务 gt 异步任务 异步任务又分为 宏任务与微任务 所以整个顺序为 同步任务 gt 微观任务 gt 宏观任务 微观任务大概有Promise then Object observe MutationObserver pro
  • shinelon笔记本进bios设置u盘启动_系统重装U盘启动进BIOS按键查询

    点击蓝字 关注我们 总的来讲 设置电脑从U盘启动一共有两种方法 第一种是开机时候按快捷键然后选择U盘启动 第二种进Bios然后设置U盘 PART ONE 一 U盘启动 组装机主板 品牌笔记本 品牌台式机 主板品牌 启动按键 笔记本品牌 启动
  • WIN32_FIND_DATA、FILETIME、FindFirstFile对文件的操作

    WIN32 FIND DATA FILETIME对文件的操作 include stdafx h include
  • 解决Agora声网音视频在后台没有声音的问题

    前言 本文会介绍 Android 与 iOS 两个平台的处理方式 一 Android高版本在应用退到后台时 系统为了省电会限制应用的后台活动 因此我们需要开启一个前台服务 在前台服务中发送常驻任务栏通知 以此来保证App 退到后台时不会被限
  • 一篇文章看懂Oracle开窗函数

    聚合类开窗函数 聚合类开窗函数类似分组函数group by中的sum avg count max min 等等 但是开窗函数不会像分组聚合函数一样按照分组返回结果 而是有多少行记录就返回多少个结果 结果输出的形式是单独一列进行输出 举个例子
  • mqtt安卓客户端

    1 MQTT 消息队列遥测传输协议 是一种基于 发布 订阅 publish subscribe 模式的 轻量级 通讯协议 该协议构建于TCP IP协议上 MQTT最大优点在于 可以以极少的代码和有限的带宽 为连接远程设备提供实时可靠的消息服
  • 在职场中比能力更重要是什么?

    一个人能力很重要 但是比能力更重要的是一个人的人品 如果一个人的人品有问题 那么很难给予重任 如果只有能力 没有人品 人就会残缺不全 人品决定态度 态度决定行为 行为决定着最后的结果 没有一个公司会愿意重用一个人品欠缺的人 那么比能力更重要
  • 针对Java文档的搜索引擎

    针对Java文档的搜索引擎 项目介绍 项目模块划分及分析 1 索引模块 Parser 类核心业务 Index 核心业务 多线程制作索引 2 搜索模块 分词 生成描述 停用词 3 Web模块 展示 项目介绍 本项目是一个基于SpringBoo