导读:本期聚焦于小伙伴创作的《在Java中如何开发简易图书推荐系统?图书推荐模块实践指南》,敬请观看详情。协同过滤是图书推荐模块常用的算法思路,分为基于用户和基于物品两类。简易Java实现可先构建评分矩阵,计算相似度得出邻居,再生成推荐列表。相比调用重型框架,手撸核心逻辑更利于理解推荐原理。本文给出内存版示例,用Map存储用户图书评分,借助余弦相似度找出兴趣相近读者,为其推送未读高分书。该方案适合课程设计或内部工具,若数据量上涨可换数据库与离线计算。

开发一个简易的图书推荐系统并不一定要依赖复杂的机器学习平台。在Java语言中,我们可以利用基础集合类和简单的数学公式,快速实现一个基于协同过滤思想的图书推荐模块。这种方式特别适合教学演示、小型内部工具或者原型验证。

在Java中如何开发简易图书推荐系统?图书推荐模块实践指南

一、推荐模块的核心思路

图书推荐的本质是预测某用户对某本书的偏好程度。协同过滤算法认为,如果读者A和读者B在过去对多本书的评分趋势一致,那么B喜欢而A没读过的书,A也可能喜欢。这种思路不需要知道书的分类标签,只依赖用户行为数据。

在简易实现里,我们把每位用户的图书评分放到一个映射结构中,整体形成一个用户到图书再到分数的二级Map。随后通过余弦相似度来量化两个用户之间的兴趣重合度,挑出最相似的若干邻居,最后汇总邻居的高分图书作为推荐候选。

1.1 数据结构的设计

使用Map<String, Map<String, Double>>来表达评分矩阵非常直观。外层键是用户编号,内层键是图书编号,值为评分,比如五分制。这样在查找和维护时都很方便,也不需要引入额外实体类。

为了避免后续计算时空指针,初始化时要保证内层Map不为空。我们在添加评分的方法里做懒加载处理,如果用户不存在就新建一个内层Map再放分值,保持调用方代码简洁。

import java.util.*;

public class RatingMatrix {
    // 用户-图书-评分
    private Map<String, Map<String, Double>> data = new HashMap<>();

    public void addRating(String user, String book, double score) {
        data.computeIfAbsent(user, k -> new HashMap<>()).put(book, score);
    }

    public Map<String, Double> getRatings(String user) {
        return data.getOrDefault(user, Collections.emptyMap());
    }

    public Set<String> getUsers() {
        return data.keySet();
    }
}

二、相似度计算与邻居查找

余弦相似度把两个用户的评分向量看作空间中的线,用点积除以模长乘积得到夹角余弦值。值越接近1,兴趣越相似。对于没有共同评分书的用户,相似度直接记为0,防止推荐噪声。

实现时先取出两个用户各自的评分Map,遍历交集图书累加乘积与平方,再套公式。由于是简易系统,我们可以在每次推荐时实时计算,数据量不大时性能完全够用。

2.1 余弦相似度代码

下面方法接收两个用户的评分映射,返回双精度相似度。注意处理分母为0的情况,否则会出现算术异常。

public class SimilarityUtil {
    public static double cosine(Map<String, Double> a, Map<String, Double> b) {
        double dot = 0.0, normA = 0.0, normB = 0.0;
        for (String book : a.keySet()) {
            if (b.containsKey(book)) {
                dot += a.get(book) * b.get(book);
            }
            normA += a.get(book) * a.get(book);
        }
        for (double v : b.values()) {
            normB += v * v;
        }
        if (normA == 0 || normB == 0) return 0.0;
        return dot / (Math.sqrt(normA) * Math.sqrt(normB));
    }
}

2.2 查找最近邻居

针对目标用户,我们遍历其他所有用户,算出相似度后放进列表排序,取前N个作为邻居。这个N就是常说的邻居数量参数,太小易片面,太大易引入不相关用户。

在示例里设定邻居数为2,用优先队列或简单排序均可。下面代码用流排序获得最相似的两个用户编号,方便后续汇总推荐。

import java.util.*;

public class NeighborFinder {
    public static List<String> findTopNeighbors(String target, RatingMatrix matrix, int k) {
        Map<String, Double> targetRatings = matrix.getRatings(target);
        return matrix.getUsers().stream()
                .filter(u -> !u.equals(target))
                .map(u -> new AbstractMap.SimpleEntry<>(u,
                        SimilarityUtil.cosine(targetRatings, matrix.getRatings(u))))
                .sorted((e1, e2) -> Double.compare(e2.getValue(), e1.getValue()))
                .limit(k)
                .map(Map.Entry::getKey)
                .toList();
    }
}

三、生成图书推荐列表

得到邻居后,我们把邻居评过而目标用户没评过的书收集起来,按邻居评分加权或简单平均排序。简易版直接取邻居里评分高于某阈值的书,按平均分倒序输出前几条。

这种策略实现成本低,也能体现协同过滤的基本效果。如果希望更平滑,可以用相似度作为权重做加权平均,但核心流程不变。

3.1 推荐方法示例

以下代码整合前面组件,对目标用户给出最多五本推荐图书。先找邻居,再过滤已读,最后算平均分排序。

import java.util.*;

public class BookRecommender {
    public static List<String> recommend(String target, RatingMatrix matrix) {
        List<String> neighbors = NeighborFinder.findTopNeighbors(target, matrix, 2);
        Map<String, Double> targetBooks = matrix.getRatings(target);
        Map<String, Double> scoreSum = new HashMap<>();
        Map<String, Integer> count = new HashMap<>();

        for (String n : neighbors) {
            for (Map.Entry<String, Double> e : matrix.getRatings(n).entrySet()) {
                if (!targetBooks.containsKey(e.getKey()) && e.getValue() >= 4.0) {
                    scoreSum.merge(e.getKey(), e.getValue(), Double::sum);
                    count.merge(e.getKey(), 1, Integer::sum);
                }
            }
        }

        return scoreSum.entrySet().stream()
                .map(e -> new AbstractMap.SimpleEntry<>(e.getKey(),
                        e.getValue() / count.get(e.getKey())))
                .sorted((a, b) -> Double.compare(b.getValue(), a.getValue()))
                .limit(5)
                .map(Map.Entry::getKey)
                .toList();
    }
}

3.2 模块调用演示

写一个简单的main方法填充数据并调用推荐,就能看到结果。这也方便打包成单元测试验证逻辑。

public class Demo {
    public static void main(String[] args) {
        RatingMatrix rm = new RatingMatrix();
        rm.addRating("u1", "b1", 5.0);
        rm.addRating("u1", "b2", 4.0);
        rm.addRating("u2", "b1", 4.5);
        rm.addRating("u2", "b3", 5.0);
        rm.addRating("u3", "b2", 4.0);
        rm.addRating("u3", "b3", 4.5);

        System.out.println(BookRecommender.recommend("u1", rm));
    }
}

四、优缺点与扩展方向

上述方案优点在于零依赖、逻辑透明,初学者能在一百行内搞清推荐流程。但它把所有数据放内存,重启即丢,且相似度实时计算在万级用户时会变慢。

若投入实际使用,可把评分存到关系数据库或Redis,相似度计算改成离线任务,定时更新邻居表。另外可引入基于物品的协同过滤,缓解新用户冷启动问题,或混合内容标签提升准确率。

4.1 冷启动的应对

新用户没有评分时,任何协同过滤都难办。简易系统可先给热门书或编辑精选书做默认推荐,等产生行为后再切入算法推荐,保证体验不空窗。

在Java里可以用一个静态热门列表兜底,当推荐结果为空时返回该列表,代码改动很小却实用。这也是很多小型图书站点起步阶段的有效做法。

Java图书推荐系统协同过滤修改时间:2026-08-10 06:00:35

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。