项目背景
Oracle 官网虽然提供了 Java API 文档, 但是并没有提供搜索模块, 只是将这些 API 按字典序排序了, 这导致要查询 API 的时候十分麻烦, 因而这个项目就诞生了.
项目介绍
用户输入一个查询词, 服务器会返回与查询词相关的若干条结果, 每个结果都由 标题, 文档描述, 展示 URL 组成, 点击标题即可跳转到对应结果的网页.
项目开发
-
索引模块
首先去 Oracle 官网下载 Java API 文档到本地, 然后根据这些 API 建立起正排索引和倒排索引. 正排索引是一个顺序表的结构, 每一个元素由 文档 id, 文档标题, 文档的展示 URL , 文档内容组成, 倒排索引是一个哈希表的结构, key 是查询词, value 是一个顺序表的结构, 这个顺序表的每个元素都是一个权重类的对象, 这个权重类包含两个属性, 即 文档 id + 文档权重, 权重表示的是文档与查询词的相关性, 后面展示给用户的时候就以权重降序展示, 让用户更可能看到自己想要的搜索结果.
正排索引和倒排索引结构如下图
正排索引和倒排索引功能介绍
倒排索引: 里面存储着各种查询词, 只要是文档出现了的词, 就会被加入到倒排索引中, 后续根据用户输入的查询词来倒排索引中查询到文档 id, 再根据 id 去正排索引中查询, 找到查询词对应的文档列表.正排索引: 组织 API 文档, 每个文档由 文档 id, 文档标题, 文档的展示 URL , 文档内容组成, 后续通过倒排索引查询到了对应文档的 id , 就通过这个 id 来正排索引中查询到对应 id 的文档, 最后经过包装以展示给用户.
建立正排索引和倒排索引
建立正排索引
获取文档标题: 下载 API 文档到本地, 然后遍历所有的 html 文件(不需要关注别的文件), 直接用文件的 getName() 方法得到文档标题.
获取文档展示 URL : 注意得到展示 URL, 由于本地的 API 文档路径和 Oracle 在线的 API 文档的 Web 路径的后大半部分是一致的, 所以只要将本地的后部分路径和 Oracle 文档的 Web 路径前部分路径拼接起来, 就可以得到每个文档的展示 URL 了.
获取文档内容: 关于 API 文档的内容, 直接读取每个文件, 去除标签和部分标签中的内容, 再遍历字符得到正文.建立倒排索引
约定计算权重的方式: 首先就要想好计算权重的方式, 以一个 int 类型的数值来评判权重大小, 这里直接遍历标题和正文, 标题出现了的查询词权重 + 10, 正文出现的查询词权重 + 1.
对文档标题和正文进行分词: 遍历已经建立好的正排索引, 拿到每个文档, 通过 Ansj 分别对每个文档的标题和正文进行分词, 每个分词就是一个查询词, 以两个列表分别接收标题的分词和正文的分词.
统计每个分词出现的次数: 先建立一个 hashMap, 用来统计词出现的次数, value 是分词, key是一个对象, 有两个属性, 标题出现次数 + 正文出现次数, 然后先遍历标题的分词, 计算每个词出现的次数, 再遍历正文的分词, 计算每个分词出现的次数, 后续通过这个 hashMap 来计算权重和插入所有的分词到倒排索引中.
计算权重并添加查询词到倒排索引中: 遍历这个 hashMap, 计算每个查询词在该文档中的权重, 注意这个权重计算方式, 标题出现次数 * 10 + 正文出现次数. 再查看这个查询词是否在倒排索引中, 不存在则添加查询词和查询词对应的权重列表, 并添加这个权重对象到权重列表中, 存在则直接添加权重对象到查询词对应的权重列表中. -
搜索模块
对查询词进行分词: 用户输入查询词, 然后对用户输入的查询词进行分词, 用一个列表接收分词结果.
去掉分词中的停用词: 遍历这个分词列表, 对这个列表进行停用词去重, 把一些不相干的词去掉, 例如 a, an, hava …
倒排索引查询分词并汇总权重列表: 遍历分词列表, 去倒排索引中查询每个分词, 记录每一个分词返回的权重列表, 并进行汇总.
合并权重: 遍历汇总后的权重列表, 进行合并权重, 因为存在输入多个查询词, 搜索到了相同结果的情况, 例如输入 array list , 就会搜索出两个 arraylist 文档. 合并权重, 首先进行文档 id 的升序排序, 这里建立一个堆, 优先级为文档 id, 建立一个返回的权重列表, 存在 id 相同的文档, 则权重相加, 进行合并, 最后返回这个合并后的权重列表.
文档排序: 对列表进行权重排序, 把权重最高的文档排在最前面.
文档包装和查询词逻辑标红: 遍历权重列表, 根据 文档 id 去正排索引里查询对应的文档, 准备返回给用户, 每个结果由 文档标题 + 文档描述 + 文档展示 URL 组成, 标题和展示 URL 在正排索引中存在, 这里只要得到描述即可, 直接遍历权重列表, 这里采取的策略是去正文中查找首个存在的查询词, 在这个查询词前后获取部分字符组成描述, 然后再遍历描述中存在的所有查询词, 进行逻辑标红, 即对每个查询词左右添加标签, 后续前端再对这些标签进行渲染.
返回结果: 用一个对象接收这些结果, 再将返这些结果以 Json 的格式返回给前端. -
前端模块
输入查询词: 用户输入查询词, 点击搜索按钮即发送查询词给服务器.
接收查询结果: 发送给服务器后, 服务器进行查询, 返回结果给前端.
判断查询状态: 前端解析后端传来的 Json 格式的结果, 根据返回结果的状态码进行相应的处理, 这里约定 -1 表示不存在该查询词或者表示出现了意料外的异常, 1 表示查询成功.
展示结果: 前端根据状态码来进行不同的操作, -1 直接 alert, 不存在该查询词或服务器处理异常, 请稍后再试. 这个取决于 -1 对应的message 描述, 1 进行展示搜索结果, 遍历后端返回的结果列表, 拿到每个结果的 文档标题, 文档描述, 文档展示 URL , 创建展示 div, 展示所有的结果.