项目实战--文档搜索引擎

在我们的学习过程中,会阅读很多的文档,例如jdk的API文档,但是在这样的大型文档中,如果没有搜索功能,我们是很难找到我们想查阅的内容的,于是我们可以实现一个搜索引擎来帮助我们阅读文档。

1. 实现思路

1.1 获取文档 

第一点,要搜索指定内容,首先要先获取到内容,我们以实现Java API文档搜索引擎来说,我们要先获取到Java的API文档,我们可以在Oracle的官网找到:Overview (Java SE 17 & JDK 17) (oracle.com)

Oracle官网提供了在线和离线两种文档,我们可以下载离线文档,通过离线文档来实现。

离线文档下载地址:Java 开发工具包 17 文档 (oracle.com)

下载好后解压缩,在 jdk-17.0.11_doc-all\docs\api 目录和子目录下的所有html文件就是所有的api文档

1.2 通过关键词查询

获取到了文档,我们还需要能够通过关键词定位到相关的文档,这里需要用到索引 

  • 正排索引: 给每个文档引入一个文档id,文档id是每个文档的身份标识,不能重复,通过文档id快速获取到对应文档就叫正排索引。
  • 倒排索引:通过一个或几个关键词查询到与之有关的所有文档的文档id,这种方式就叫到排索引。

于是要实现关键词查询,我们只需要给下载好的Java API文档实现一个正排索引和倒排索引,通过到排索引查询到相关的文档的id,要查看某个文档时再用查询到的id使用正排索引查询到对应文档。

1.3 如何返回查询到的结果

查询到对应的api文档之后,如何返回给用户,这里我的想法是返回一个在线文档的url,当用户想要查看某个文档时,返回Oracle官方的在线文档对应的页面的url。

那么此种方式就需要我们把在线文档的url和离线文档联系起来:

我们观察某个文档的url和在线文档的本地路径:

在线文档:

离线文档: 

 

 我们发现相同api文档的在线版本的url和离线版本路径,它们的后半部分是相同的,所有我们只需要通过一些字符串拼接操作,就可以通过离线文档的文件路径得到在线文档的url。

1.4 模块划分

通过上面的叙述,我们可以对我们的程序进行一个模块划分:

  • 索引模块:扫描并解析所有的本地文档并构建出索引;提供一些API实现查正排/到排的功能
  • 搜索模块:调用索引模块通过关键词查询到相关文档信息,并处理后返回
  • Web模块:实现一个简单的Web程序,能通过网页的形式和用户交互

2. 索引模块实现

创建一个Spring项目

2.1 实现Parser类 

实现一个Parser类用于扫描并解析本地的离线文档:

package org.example.docsearcher.config;

@Configuration
public class Parser {
    //指定文档文件的路径
    private static final String FILE_PATH = "D:/桌面/jdk-17.0.11_doc-all/docs/api";


    //解析文档
    private void parser() {
        //1.找出所有html文件
        List<File> fileList = new ArrayList<>();
        enumFile(FILE_PATH, fileList);

        //2.对每个HTML文件进行解析
       for(File f : fileList) {
           parserHTML(f);
       }
    }

    //枚举出所有的html文件
    private void enumFile(String filePath, List<File> fileList) {

    }

    //解析出html文件的内容
    private void parserHTML(File file) {

    }
}

实现enumFile方法:

    private void enumFile(String filePath, List<File> fileList) {
        File file = new File(filePath);
        //获取目录下的文件列表
        File[] files = file.listFiles();
        for(File f : files) {
            if(f.isDirectory()) {
                //如果f是目录则递归添加文件
                enumFile(f.getAbsolutePath(), fileList);
            }else if(f.getName().endsWith(".html")){
                //如果f是html文件则添加到fileList中
                fileList.add(f);
            }
        }
    }

实现parserHTML方法:

要实现parserHTML方法我们要先理清楚,html文件中有什么和我们需要什么:

  • 标题:返回查询结果时,可以展示给用户以供选择
  • 正文:用于提取关键词构建倒排索引
  • url:用户点击时通过url跳转到对应页面
    private void parserHTML(File file) {
        //a.解析出标题
        String title = parserTitle(file);

        //b.解析出url
        String url = parserUrl(file);

        //c.解析出正文
        String content = parserContent(file);

    }

    private String parserContent(File file) {
        StringBuilder content = new StringBuilder();
        //按字节读,这里使用BufferedReader 提高速度
        try(BufferedReader bufferedReader = new BufferedReader(new FileReader(file), 1024 * 1024)) {
            while(true) {
                int ch = bufferedReader.read();
                if(ch == -1) {
                    //文件读完了
                    break;
                }
                content.append((char)ch);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        String ret = content.toString();

        //使用正则表达式替换掉js代码
        ret = ret.replaceAll("<script.*?>.*?</script>", "");
        //使用正则表达式替换html标签
        ret = ret.replaceAll("<.*?>", "");
        //把换行符和连续的多个空格替换为一个空格使内容更美观
        ret = ret.replaceAll("\\s+", " ");
    }

    private String parserUrl(File file) {
        //拼接出在线文档对应的url
        String s1 = "https://docs.oracle.com/en/java/javase/17/docs/api";
        String s2 = file.getAbsolutePath().substring(FILE_PATH.length()).replace("\\", "/");
        return s1 + s2;
    }

    private String parserTitle(File file) {
        String fileName = file.getName();
        return fileName.substring(0, fileName.length() - ".html".length());
    }

 注意:FileReader的read方法是每次从磁盘里读取一个字符到内存中,BuferedReader 内部带有一个缓存区,会一次把多个字符加载到缓存区中,调用read方法时会从缓存区中读取字符,减少直接访问磁盘的次数提高了速度,构造方法中的第二个参数就是设置缓冲区的大小,单位是字节

2.2 实现Index类

实现Index类用于创建索引和通过关键词和索引查询相关文档:

前排索引由文档id和文档组成,要求能够通过文档id快速查询到文档,索引我们可以使用一个List来储存前排索引,即通过数组下标当作文档id,数组的内容即为文档的信息,于是我们创建一个DocInfo类用于存储文档信息:

package org.example.docsearcher.model;

import lombok.Data;

@Data
public class DocInfo {
    //储存一个文档的相关信息
    private int docId;
    private String title;
   
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ting-yu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值