基于倒排索引的 Java 文档搜索引擎(三)

专栏:基于正倒排 Java 文档搜索引擎

个人主页:手握风云

目录

[一、Web 模块](#一、Web 模块)

[1.1. 整体架构](#1.1. 整体架构)

[1.2. 后端 Web 接口实现](#1.2. 后端 Web 接口实现)

[1.3. 处理停用词](#1.3. 处理停用词)

[1.4. 多路归并](#1.4. 多路归并)


一、Web 模块

Web 模块的核心作用是将后端搜索能力封装为Web 接口,并提供可视化前端页面,让用户通过浏览器完成搜索、查看结果、跳转官方 Java API 文档的完整操作,是整个搜索引擎的交互入口。核心流程:前端发起搜索请求 → 后端 Controller 接收并调用搜索模块 → 返回 JSON 结果 → 前端渲染展示。

1.1. 整体架构

  • 后端:SpringBoot Controller 层,提供搜索接口,对接搜索模块
  • 交互:GET 请求传参,JSON 格式返回数据
  • 优化:搜索结果关键词标红、样式美化、新窗口跳转文档

1.2. 后端 Web 接口实现

核心基于 SpringBoot Web 场景,使用 @RestController 声明控制器(返回 JSON / 字符串,非页面跳转);接口:/searcher,GET 请求,参数query(搜索关键词)。

java 复制代码
package com.yang.java_doc_searcher.controller;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yang.java_doc_searcher.searcher.DocSearcher;
import com.yang.java_doc_searcher.searcher.Result;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class DocSearchController {
    private static DocSearcher searcher = new DocSearcher();
    private static ObjectMapper objectMapper = new ObjectMapper();

    @GetMapping(value = "/searcher", produces = "application/json;charset=utf-8")
    @ResponseBody
    public String search(@RequestParam("query") String query) throws JsonProcessingException {
        List<Result> results = searcher.search(query);
        return objectMapper.writeValueAsString(results);
    }
}

1.3. 处理停用词

如果我们输入的 query 为"Array List",我们会发现查询的摘要里面空格也被算进了分词结果集中来执行倒排索引查询。有些无意义高频词,如 is、the、a、have 进行剔除,可以提升搜索速度与结果精准度。因此我们需要在网上找到现有的停用词表,再通过程序把停用词表加载到内存中。我们可以使用哈希表来存储这些停用词,再针对分词结果在哈希表中进行筛选。

java 复制代码
private static final String STOP_WORD_PATH ="D:\\doc_search_index\\stop_word.txt";
private HashSet<String> stopWords = new HashSet<>();

// 停用词加载方法
private void loadStopWord() {
    try (BufferedReader bufferedReader = new BufferedReader(new FileReader(STOP_WORD_PATH))) {
        while (true) {
            String line = bufferedReader.readLine();
            if (line == null) {
                // 读取文件完毕
                break;
            }
            stopWords.add(line);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}    

1.4. 多路归并

当输入多词查询时,同一文档被多次匹配」的问题,将多个词的权重累加,让同时包含多个查询词的文档排名更靠前。原理类似N 个有序数组的归并,用优先队列实现高效归并。

java 复制代码
private List<Weight> mergeResult(List<List<Weight>> source) {
// 对每个内部列表按DocId进行排序
    for (List<Weight> curRow : source) {
        curRow.sort(new Comparator<Weight>() {
            @Override
            public int compare(Weight o1, Weight o2) {
            // 比较两个Weight对象的DocId,返回差值用于排序
                return o1.getDocId() - o2.getDocId();
            }
        });
    }

// 初始化目标列表和优先队列
    List<Weight> target = new ArrayList<>();
// 创建优先队列,用于存储每个列表当前最小的元素位置
    PriorityQueue<Pos> queue = new PriorityQueue<>(new Comparator<Pos>() {
        @Override
        public int compare(Pos o1, Pos o2) {
            // 比较两个位置对应的Weight对象的DocId
            Weight w1 = source.get(o1.row).get(o1.col);
            Weight w2 = source.get(o2.row).get(o2.col);
            return w1.getDocId() - w2.getDocId();
        }
    });

    for (int row = 0; row < source.size(); row++) {
        queue.offer(new Pos(row, 0));
    }

    while (!queue.isEmpty()) {
        Pos minPos = queue.poll();
        Weight curWeight = source.get(minPos.row).get(minPos.col);

        // 处理相同DocId的Weight合并
        if (target.size() > 0) {
            Weight lastWeight = target.get(target.size() - 1);
            if (lastWeight.getDocId() == curWeight.getDocId()) {
                // 如果DocId相同,合并权重
                lastWeight.setWeight(lastWeight.getWeight() + curWeight.getWeight());
            } else {
                // 如果DocId不同,添加到目标列表
                target.add(curWeight);
            }
        } else {
            // 如果目标列表为空,直接添加当前元素
            target.add(curWeight);
        }

        // 将当前元素的下一个元素加入优先队列
        Pos newPos = new Pos(minPos.row, minPos.col + 1);
        if (newPos.col >= source.get(newPos.row).size()) {
            // 如果当前列表已处理完,跳过
            continue;
        }
        queue.offer(newPos);
    }
    return target;
}
相关推荐
小刘的干货分享16 小时前
浙江必应推广百科:核心价值与本地合规服务商梳理
搜索引擎·微软
RD_daoyi18 小时前
Google SEO第四周:深度站内优化——让网站快速收录、稳定排名的硬核技术
大数据·服务器·人工智能·搜索引擎
小真zzz19 小时前
GEO选型避坑实录:当“参考答案”是假的,如何找到“标准答案”?
大数据·人工智能·搜索引擎·ai·大模型
Elastic 中国社区官方博客19 小时前
6个资源,1条命令:使用 Terraform 全自动化实现 Elastic 异常检测
大数据·人工智能·elasticsearch·搜索引擎·云原生·自动化·terraform
阿乔外贸日记1 天前
2026尼日利亚五项清关政策更新,拉高能源装备进口综合成本
大数据·人工智能·搜索引擎·智能手机·云计算·能源
Elastic 中国社区官方博客1 天前
Elasticsearch DiskBBQ:使用原生 SIMD Blocks 实现快 40% 的向量评分计算
大数据·人工智能·elasticsearch·搜索引擎·ai·全文检索·diskbbq
Elastic 中国社区官方博客1 天前
Kibana:使用 AI Chat 及 MCP 轻松创建 AI 原生仪表板
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·信息可视化
可乐ea1 天前
【知识获取与分享社区项目 | 项目日记第 21 天】索引构建与联想建议:Outbox 增量更新 + Completion Suggester
java·大数据·mysql·elasticsearch·搜索引擎
醉颜凉2 天前
深度解析 Elasticsearch 搜索过程:Query Then Fetch 两阶段详解
大数据·elasticsearch·搜索引擎
陕西企来客2 天前
2026 西安 GEO 优化技术解析:前沿技术与行业规范深度企来客科技行业白皮书声明
开发语言·搜索引擎·php