应选 ConcurrentHashMap 而非 HashMap 或 ArrayList;ConcurrentHashMap 保证并发安全,配合 CopyOnWriteArrayList 存明细;防重投需数据库唯一约束或 Redis SETNX;接口须 POST+token+限流;结果刷新优先轮询而非 WebSocket。

投票数据结构怎么选:用 Map 还是 List?
核心问题不是“能存”,而是“查得快、改得稳、并发不崩”。HashMap 最常用,键是选项名(如 "A"),值是票数;但多线程环境下直接用会出错——ConcurrentHashMap 是更稳妥的选择。别用 ArrayList 存原始投票记录再遍历统计,那是 O(n) 查询,10 万条数据就明显卡顿。
如果需要保留用户 ID 和投票时间,得建实体类:
public class Vote {
private String userId;
private String option;
private long timestamp;
}此时用 ConcurrentHashMap 统计,再配一个 CopyOnWriteArrayList 存明细(写少读多场景下合适)。
如何防止重复投票:校验逻辑放哪?
不能只靠前端限制,后端必须做唯一性校验。常见错误是把 userId 存在 session 或内存 map 里,服务重启就失效。正确做法是持久化记录,哪怕只是用本地文件或 H2 内存数据库:
- 用
HashSet存已投票userId—— 仅限单机、临时测试,重启即丢 - 真实项目必须查数据库表(如
voted_users(userId VARCHAR PRIMARY KEY)),插入前用INSERT ... ON CONFLICT DO NOTHING(PostgreSQL)或INSERT IGNORE(MySQL)避免异常 - 若用 Redis,用
SETNX命令 + 过期时间(如SETNX voted:123 true EX 86400),比单纯存 key 更安全
投票接口怎么设计才不容易被刷:简单但有效的防护
没加防护的 /vote?option=A&user=abc 接口,用 curl 循环调 1000 次就乱套。基础防护三件事必须做:
- 接口必须是
POST,参数走 body(如 JSON:{"option":"A","token":"xyz"}),拒绝 GET 参数式投票 - 每个用户首次投票发一个短期有效 token(如 JWT,有效期 5 分钟),验证
token签名和过期时间,且服务端记录该 token 是否已被使用 - 加简单限流:用
Guava RateLimiter限制单个userId每分钟最多投 1 票,RateLimiter limiter = RateLimiter.create(1.0); // 1 per minute if (!limiter.tryAcquire()) { throw new IllegalStateException("Too many requests"); }
统计结果实时刷新怎么做:别一上来就上 WebSocket
小规模投票(百人内)根本不需要 WebSocket。先用最省事的方案:
立即学习“Java免费学习笔记(深入)”;
- 前端每 5 秒发一次
GET /api/results,返回 JSON:{"A": 42, "B": 31, "C": 19} - 后端用
@RestController+ConcurrentHashMap直接返回,无锁读取,毫秒级响应 - 真要“实时”且并发高,再考虑用 Spring WebFlux +
Flux.interval()推送,或者用 Redis Pub/Sub 让投票成功时PUBLISH results-update "",多个实例都能监听
很多人一想到“实时”就堆技术,其实多数内部投票系统,轮询 + 缓存响应头(Cache-Control: no-cache)已经够用。真正难的是数据一致性和防刷,不是刷新快慢。










