RogueMap,HashMap替代品

HashMap 的困境

在处理大规模数据时,传统的 HashMap 面临诸多限制:

  • ❌ 内存瓶颈 - 所有数据必须存储在堆内存,受 JVM 堆大小限制
  • ❌ GC 压力 - 百万级对象导致 Full GC 频繁,影响应用稳定性
  • ❌ 数据易失 - 进程重启后数据全部丢失,无持久化能力
  • ❌ 容量受限 - 超大数据集(10GB+)无法处理,OutOfMemoryError 噩梦
  • ❌ 冷启动慢 - 每次启动都需要重新加载数据,耗时数分钟甚至更久

今天要给大家介绍的RogueMap,就是为了解决这些痛点而生的。

它能做到什么程度?

  • 🚀 读性能提升2.4倍,即使对比HashMap,吞吐量都能从200万ops/s飙到500万ops/s

  • 💾 内存占用减少87%,100w个对象(每个对象10个属性)实测,堆内存从304MB降到40MB

  • ⚡ 进程重启秒级恢复,千万级数据自动加载

  • 📦 API简单到爆,就像用HashMap一样顺滑

一、RogueMap是什么

想象一下,你在玩游戏时有三种存储道具的方式:

  1. 背包(OffHeap模式) - 随身携带,速度快,但下线就清空

  2. 仓库临时箱(Mmap临时模式) - 容量更大,关服清空,适合临时囤货

  3. 银行保险箱(Mmap持久模式) - 容量巨大,永久保存,下次登录自动恢复

RogueMap就是这样一个"三合一"的数据仓库,你可以根据需要选择存储模式:

// 模式1: 背包 - 纯内存,速度极快
RogueMap<String, User> cache = RogueMap.<String, User>offHeap()
    .keyCodec(StringCodec.INSTANCE)
    .valueCodec(new KryoObjectCodec<>(User.class))
    .maxMemory(100 * 1024 * 1024)  // 100MB
    .build();

// 模式2: 临时仓库 - 文件映射,自动清理
RogueMap<Long, Long> tempData = RogueMap.<Long, Long>mmap()
    .temporary()
    .keyCodec(PrimitiveCodecs.LONG)
    .valueCodec(PrimitiveCodecs.LONG)
    .build();

// 模式3: 保险箱 - 持久化,重启恢复
RogueMap<String, Long> scores = RogueMap.<String, Long>mmap()
    .persistent("data/game_scores.db")
    .keyCodec(StringCodec.INSTANCE)
    .valueCodec(PrimitiveCodecs.LONG)
    .build();

scores.put("玩家A", 99999L);
scores.flush();  // 保存
scores.close();

// 进程重启后...
scores = RogueMap.<String, Long>mmap()
    .persistent("data/game_scores.db")
    .keyCodec(StringCodec.INSTANCE)
    .valueCodec(PrimitiveCodecs.LONG)
    .build();

Long score = scores.get("玩家A");  // 99999L - 数据还在!

核心优势:用HashMap的简单API,获得超越其限制的能力。

核心模块

  • RogueMap - 主类,提供 OffHeapBuilder 和 MmapBuilder 两个构建器
  • index - 索引层
    • HashIndex - 基础哈希索引,基于 ConcurrentHashMap
    • SegmentedHashIndex - 分段哈希索引,64 个段 + StampedLock 乐观锁
    • LongPrimitiveIndex - Long 键原始数组索引,节省 81% 内存
    • IntPrimitiveIndex - Integer 键原始数组索引
  • storage - 存储引擎
    • OffHeapStorage - 堆外内存存储
    • MmapStorage - 内存映射文件存储
  • memory - 内存管理
    • SlabAllocator - Slab 分配器,7 个大小类别(16B 到 16KB)
    • MmapAllocator - 内存映射文件分配器,支持超过 2GB 的大文件
    • UnsafeOps - 底层 Unsafe API 操作
  • serialization - 序列化层
    • PrimitiveCodecs - 原始类型零拷贝编解码器
    • StringCodec - String 编解码器
    • KryoObjectCodec - Kryo 对象序列化编解码器(可选)

内存管理机制

SlabAllocator(堆外内存)
  • 分配策略: 7 个 size class (16B, 64B, 256B, 1KB, 4KB, 16KB)
  • 块大小: 1MB
  • 优化: 空闲列表重用,负载因子自适应扩容
  • 内存节省: 相比 HashMap 节省 87% 堆内存
MmapAllocator(文件映射)
  • 特点: 使用 MappedByteBuffer 将文件映射到内存
  • 大文件支持: 单个分段最大 2GB,自动分多段处理
  • 并发安全: CAS 操作分配偏移量
  • 双模式: 支持持久化和临时文件

高并发支持

SegmentedHashIndex 并发机制
  • 分段数量: 64 个独立段
  • 锁策略: 每个段独立的 StampedLock
  • 乐观读: 读操作优先使用乐观读,验证失败时降级为读锁
  • 性能: 高并发场景下读性能提升 15 倍
LongPrimitiveIndex 并发机制
  • 实现: 原始数组 (long[] keys, long[] addresses, int[] sizes)
  • 锁策略: StampedLock 乐观读
  • 内存优化: 节省 81% 内存

二、性能到底有多强

我们用100万条数据做了测试,对比结果让人眼前一亮:

📊 RogueMap vs HashMap

指标

HashMap

RogueMap(Mmap持久)

提升

写入耗时

611ms

547ms

⬆️ 12%

读取耗时

463ms

195ms

⬆️ 137%

读吞吐量

216万ops/s

513万ops/s

⬆️ 137%

堆内存占用

304MB

40MB

⬇️ 87%

数据容量受限于堆大小(通常 < 10GB)无限制,可达 TB 级
堆内存占用100%仅 15.3%
GC 影响严重(Full GC 秒级)几乎无影响
持久化❌ 不支持✅ 支持
进程重启数据全部丢失数据自动恢复
写性能基准1.45 倍提升
读性能基准约 1/4(反序列化开销)
临时文件❌ 不支持✅ 自动清理

🔥 RogueMap vs MapDB (竞品对比)

指标

RogueMap

MapDB

领先倍数

读取速度

202ms

3207ms

15.9x
写入速度

632ms

2764ms

4.4x
读吞吐量

495万ops/s

31万ops/s

15.9x

MapDB是业内知名的嵌入式存储引擎,但在RogueMap面前,读取性能直接被碾压15倍

综合性能对比

方案写入时间读取时间写吞吐量读吞吐量堆内存占用持久化
HashMap1,535ms158ms651K ops/s6,329K ops/s311 MB
FastUtil600ms32ms1,667K ops/s31,250K ops/s276 MB
Caffeine1,107ms2,298ms903K ops/s435K ops/s352 MB
RogueMap OffHeap1,924ms854ms520K ops/s1,171K ops/s48 MB
RogueMap Mmap 持久化1,057ms642ms946K ops/s1,558K ops/s48 MB
RogueMap Mmap 临时1,113ms704ms898K ops/s1,420K ops/s48 MB
MapDB OffHeap8,259ms8,451ms121K ops/s118K ops/s11 MB
MapDB 临时文件9,002ms7,717ms111K ops/s130K ops/s8 MB
MapDB 持久化8,117ms7,709ms123K ops/s130K ops/s8 MB

适用场景

RogueMap 适合这些场景

  • ✅ 写多读少 - 数据采集、日志聚合、指标统计
  • ✅ 需要持久化 - 用户会话、应用状态、缓存数据
  • ✅ 大数据集 - 数据量超过 JVM 堆大小限制
  • ✅ GC 敏感 - 对 Full GC 停顿零容忍的实时系统
  • ✅ 临时数据处理 - 海量临时数据暂存,自动清理避免泄露

RogueMap 不适合这些场景

  • ❌ 读密集型 - 如果你的应用是读多写少,HashMap 或 Caffeine 更合适
  • ❌ 微秒级延迟 - 如果需要极致的读取性能,纯内存方案更好
  • ❌ 小数据集 - 数据量 < 1GB 时,HashMap 的简单性更有优势

推荐使用场景

  • 🏆 写多读少 - 数据采集、日志聚合、消息队列
  • 💾 需要持久化 - 用户会话、缓存数据、临时计算结果
  • 📈 大数据集 - 超过堆大小的数据处理
  • ⚡ GC 敏感 - 对 GC 停顿零容忍的实时系统

三、为什么这么快

看到这里,你可能会问:"怎么做到的?是不是用了什么黑魔法?"

其实没有黑魔法,只有极致的工程优化。让我们用最简单的方式,揭开RogueMap的五大性能秘诀:

🎯 秘诀1: 堆外内存 - 让GC管不着

问题: HashMap把数据存在JVM堆内,数据越多,GC扫描越慢,停顿越久。

RogueMap的做法: 把数据存到堆外内存(DirectByteBuffer)或文件映射(Mmap)。

效果:

  • JVM堆只需存40MB的索引结构

  • GC扫描范围大幅减少,停顿从秒级降到毫秒级

  • 数据量可以突破JVM堆限制,想存100GB都行

类比: 就像你把大量书籍从书桌(JVM堆)搬到书架(堆外内存),书桌变整洁,找东西更快。


⚡ 秘诀2: 零拷贝序列化 - 原始类型直接怼

问题: 传统存储引擎要把Java对象序列化成字节数组,再存储。每次读写都要序列化/反序列化,慢得要命。

RogueMap的做法: 原始类型(Long、Integer等)直接写入内存,不经过任何转换。

// ❌ 传统方式: 序列化开销巨大
byte[] bytes = serialize(value);  // Long -> 字节数组
storage.write(bytes);

// ✅ RogueMap方式: 直接内存操作
UnsafeOps.putLong(address, value);  // 8字节,一次性写入

效果: 读写延迟降低到纳秒级,吞吐量直接起飞。

类比: 就像快递员送包裹,传统方式要拆开检查再装箱,RogueMap直接扔过去。


🔓 秘诀3: 乐观并发 - 99%的操作不加锁

问题: HashMap在高并发场景要加锁,锁竞争严重时性能暴跌。

RogueMap的做法: 使用StampedLock的乐观读模式,大部分读操作完全不加锁。

// 第一步: 乐观读(无锁)
long stamp = lock.tryOptimisticRead();
long value = readData(key);

// 第二步: 验证是否被其他线程修改
if (!lock.validate(stamp)) {
    // 冲突了才降级到读锁重试
    stamp = lock.readLock();
    value = readData(key);
    lock.unlockRead(stamp);
}

效果: 高并发场景下,读性能提升15倍。

类比: 图书馆不用每次借书都登记,只在发现书丢了才追查记录。


💾 秘诀4: 内存映射文件(Mmap) - 操作系统帮你缓存

问题: 传统文件IO需要从磁盘读到内核缓冲区,再拷贝到用户空间,路径太长。

RogueMap的做法: 使用Mmap把文件直接映射到内存地址空间。

效果:

  • 操作系统自动管理页缓存,热数据常驻内存

  • 读取时直接访问内存地址,速度接近纯内存

  • 支持超大文件(>100GB),自动分段管理

类比: 传统方式像图书馆借书要填单子排队,Mmap就像书直接放在你桌上,拿来就看。


🧠 秘诀5: 极致的内存优化 - 原始类型数组索引

问题: HashMap的每个Entry要存储key引用、value引用、hash值、next指针,占用28字节。

RogueMap的做法: 针对Long类型键,用三个long数组存储索引,每条记录只占20字节。

传统HashMap的Entry结构:
┌────────────┬────────────┬────────┬────────┐
│ key引用(8B)│ value引用(8B)│ hash(4B)│ next(8B)│
└────────────┴────────────┴────────┴────────┘
= 28字节/条

RogueMap的LongPrimitiveIndex:
keys[]      : [123, 456, 789, ...]  (8字节/条)
addresses[] : [0x1000, 0x2000, ...] (8字节/条)
sizes[]     : [64, 128, 256, ...]   (4字节/条)
= 20字节/条

效果: 100万条数据,内存占用从104MB降到20MB,**节省81%**。

类比: 传统方式像每本书都配个厚厚的档案袋,RogueMap直接用Excel表格记录,省空间又高效。


四、三大应用场景,总有一个适合你

🎮 场景1: 游戏服务器 - 千万玩家数据秒级恢复

痛点: 游戏服务器维护重启,千万级玩家数据要重新加载,玩家等待时间长。

RogueMap方案:

// 玩家数据持久化
RogueMap<Long, Player> playerDB = RogueMap.<Long, Player>mmap()
    .persistent("data/players.db")
    .keyCodec(PrimitiveCodecs.LONG)  // 玩家ID
    .valueCodec(new KryoObjectCodec<>(Player.class))
    .allocateSize(10L * 1024 * 1024 * 1024)  // 10GB
    .build();

// 写入数据
playerDB.put(10001L, new Player("张三", 99, "战士"));
playerDB.flush();

// 服务器重启后,自动恢复!
playerDB = RogueMap.<Long, Player>mmap()
    .persistent("data/players.db")
    .keyCodec(PrimitiveCodecs.LONG)
    .valueCodec(new KryoObjectCodec<>(Player.class))
    .build();

Player player = playerDB.get(10001L);  // 张三还在!

收益:

  • ✅ 重启恢复从分钟级降到秒级

  • ✅ 堆内存占用减少90%,Full GC基本消失

  • ✅ 数据持久化,玩家数据永不丢失


📊 场景2: 推荐系统 - 亿级用户特征本地缓存

痛点: 推荐系统需要缓存亿级用户特征,Redis成本高,本地HashMap内存爆炸。

RogueMap方案:

// 用户特征缓存
RogueMap<Long, UserFeature> featureCache = RogueMap.<Long, UserFeature>offHeap()
    .keyCodec(PrimitiveCodecs.LONG)  // 用户ID
    .valueCodec(new KryoObjectCodec<>(UserFeature.class))
    .maxMemory(50L * 1024 * 1024 * 1024)  // 50GB堆外内存
    .segmentedIndex(64)  // 64个分段,高并发
    .build();

// 高并发读取
UserFeature feature = featureCache.get(userId);

收益:

  • ✅ 本地缓存,延迟从毫秒级降到微秒级

  • ✅ 节省Redis集群成本,单机搞定

  • ✅ 堆内存占用极低,GC压力小


🔬 场景3: 大数据处理 - 百GB临时数据不落盘

痛点: 数据清洗、ETL任务产生大量临时数据,写磁盘慢,放内存炸。

RogueMap方案:

// 临时数据存储
RogueMap<String, Record> tempData = RogueMap.<String, Record>mmap()
    .temporary()  // 自动清理
    .keyCodec(StringCodec.INSTANCE)
    .valueCodec(new KryoObjectCodec<>(Record.class))
    .allocateSize(100L * 1024 * 1024 * 1024)  // 100GB
    .build();

// 处理海量数据
for (Record record : dataset) {
    String key = computeKey(record);
    tempData.put(key, record);
}

// 任务结束,自动清理临时文件
tempData.close();

收益:

  • ✅ 容量突破内存限制,可达TB级

  • ✅ 操作系统页缓存加速,速度接近内存

  • ✅ 自动清理,不留垃圾文件


五、上手简单,五分钟集成

Maven依赖

<dependency>
    <groupId>com.yomahub</groupId>
    <artifactId>roguemap</artifactId>
    <version>1.0.0-BETA1</version>
</dependency>

快速开始

// 1. 创建RogueMap
RogueMap<String, Long> map = RogueMap.<String, Long>offHeap()
    .keyCodec(StringCodec.INSTANCE)
    .valueCodec(PrimitiveCodecs.LONG)
    .maxMemory(100 * 1024 * 1024)
    .build();

// 2. 使用方式和HashMap一模一样
map.put("apple", 100L);
map.put("banana", 200L);

Long value = map.get("apple");  // 100L
boolean exists = map.containsKey("banana");  // true
map.remove("apple");

// 3. 记得关闭释放资源
map.close();

六、技术亮点总结

让我们用一张表格,快速回顾RogueMap的核心优势:

维度

HashMap

RogueMap

优势

容量限制

JVM堆大小

无限制(取决于磁盘)

⬆️ 突破内存墙

GC压力

极高

极低

⬇️ 减少87%堆内存

读性能

更快

⬆️ 提升2.4倍

持久化

不支持

支持

✅ 秒级恢复

并发性能

中等

优秀

⬆️ 乐观读提升15倍

API复杂度

简单

简单

🎯 一致体验


七、RogueMap的作者是谁

RogueMap的作者是铂赛东,他也是LiteFlow,TLog等框架的作者。

之后RogueMap也会被使用在LiteFlow中。成为本地储存规则,脚本的方案。

其实这也是一开始写这个项目的初衷。


八、常见问题FAQ

Q1: RogueMap支持哪些数据类型?

A:

  • 原始类型: Long、Integer、Short、Byte、Double、Float、Boolean(零拷贝,性能最优)

  • String: 内置StringCodec

  • 对象: 使用KryoObjectCodec(需要Kryo依赖)

Q2: 堆外内存会不会泄漏?

A: RogueMap使用引用计数管理内存,调用close()会自动释放。建议使用try-with-resources:

try (RogueMap<K, V> map = RogueMap.<K, V>offHeap()...build()) {
    // 使用map
}  // 自动释放

Q3: 支持并发吗?

A: 支持!SegmentedHashIndex使用分段锁+乐观读,高并发场景性能优秀。

Q4: 和Redis、RocksDB比如何选择?

A:

  • Redis: 分布式缓存,网络开销大,适合多机共享

  • RocksDB: 功能强大,API复杂,适合复杂查询

  • RogueMap: 本地缓存,API简单,适合单机高性能场景

Q5: 能存多大的数据?

A:

  • OffHeap模式: 受限于机器内存

  • Mmap模式: 受限于磁盘空间,理论上TB级都可以

Q6:为什么现在版本是1.0.0-BETA1

A:对,这是一个新项目,BETA1已经通过了100多个测试用例,已经表现很出色了。之后还会添加List,Set,Queue的支持。大家可以先试用下。希望大家持续关注这个项目。


九、写在最后

在Java生态中,我们不缺HashMap这样的内存数据结构,也不缺RocksDB这样的重量级存储引擎。

但我们缺少一个简单、高效、堆外内存的本地KV存储方案。

RogueMap就是为了填补这个空白而生:

  • ✅ 简单: HashMap级别的API,5分钟上手

  • ✅ 高效: 读性能提升2.4倍,内存节省87%

  • ✅ 灵活: 三种存储模式,覆盖绝大多数场景

  • ✅ 可靠: 持久化+自动恢复,数据不丢失

如果你的应用正在被大数据量、高GC压力、慢速度困扰,不妨试试RogueMap。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值