# fastmap
**Repository Path**: hdwang123/fastmap
## Basic Information
- **Project Name**: fastmap
- **Description**: 一个支持等值查找、范围查找、数据过期、键排序等功能的线程安全Map,适合做本地缓存。
- **Primary Language**: Java
- **License**: MIT
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2022-07-02
- **Last Updated**: 2026-06-20
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# FastMap
FastMap 是一个面向本地缓存场景的线程安全 `Map` 实现,在标准 `Map` 能力之上提供:
- 基于 `HashMap` 的等值查询
- 可选的键排序与范围查询
- 毫秒级 TTL 和自动过期
- 过期回调
- 原子化的 `compute`、`merge`、`putIfAbsent` 等复合操作
- 支持写回原 Map 的 `keySet()`、`values()` 和 `entrySet()` 视图
FastMap 使用 `HashMap` 将等值查询保持在平均 `O(1)`,同时使用 `TreeMap` 提供 `O(log n)` 的排序和范围查询;即以额外内存和写入成本换取更快的查询。项目使用 Java 8 和 Maven。
## 核心原理
FastMap 同时维护数据索引、排序索引和过期元数据,并使用同一把
`ReentrantReadWriteLock` 保证复合操作的原子一致性。
```mermaid
flowchart LR
Client["业务线程
Map / TTL / 范围查询"] --> API["FastMap API"]
API --> Lock{"统一读写锁"}
Lock --> Hash["HashMap
等值查询 O(1)"]
Lock --> Tree["TreeMap(可选)
排序与范围查询 O(log n)"]
Lock --> Expire["过期元数据
key → 截止时间
截止时间 → keys"]
Timer["全局守护定时器
每秒扫描"] --> Cleanup["过期清理"]
Expire --> Cleanup
Cleanup --> Lock
Cleanup --> Callback["有界回调线程池"]
Views["keySet / values / entrySet
线程安全视图"] --> API
```
### 数据一致性
启用排序后,同一份数据会同时写入:
- `HashMap`:负责 `get()`、`containsKey()` 等等值查询
- `TreeMap`:负责 `firstKey()`、`subMap()` 等排序和范围查询
写入、删除、过期清理和批量更新都在统一写锁内执行。排序写入失败时不会留下只更新一个索引的中间状态。
### 过期机制
过期数据通过三种时机清理:
1. 调用查询方法时主动清理已经到期的数据
2. 后台守护线程每秒扫描一次
3. 设置过期回调时,在截止时间触发一次定向清理
重复调用 `expire()` 会重置 TTL,并取消旧的回调定时任务。回调由有界线程池执行,避免大量 key 同时过期时无限创建线程。
启用过期功能的 FastMap 实例通过弱引用注册到全局清理器,不会因为后台定时任务而永久无法被垃圾回收。
## 快速开始
```java
import com.hdwang.fastmap.FastMap;
import com.hdwang.fastmap.IFastMap;
public class Main {
public static void main(String[] args) throws Exception {
IFastMap map = new FastMap<>();
map.put(1L, "张三");
System.out.println(map.get(1L));
// 2 秒后过期
map.expire(1L, 2_000L);
System.out.println("TTL: " + map.ttl(1L) + " ms");
// 重置 TTL,并注册过期回调
map.expire(1L, 3_000L,
(key, value) -> System.out.println(
key + " 已过期,原值为 " + value));
Thread.sleep(3_500L);
System.out.println(map.get(1L)); // null
}
}
```
## 排序与范围查询
### 使用 Key 的自然顺序
```java
IFastMap map = new FastMap<>(false, true);
map.put(3L, "王五");
map.put(1L, "张三");
map.put(2L, "李四");
System.out.println(map.firstKey()); // 1
System.out.println(map.lastKey()); // 3
System.out.println(map.subMap(1L, 3L)); // {1=张三, 2=李四}
System.out.println(map.headMap(3L)); // {1=张三, 2=李四}
System.out.println(map.tailMap(2L)); // {2=李四, 3=王五}
```
构造器的两个布尔参数依次表示:
```java
new FastMap<>(enableExpire, enableSort);
```
### 使用自定义 Comparator
```java
IFastMap map =
new FastMap<>(true, Comparator.reverseOrder());
map.put("Alice", 1);
map.put("Bob", 2);
```
`Comparator` 必须与 `equals()` 保持一致。如果比较器把两个不相等的 key 判断为相等,FastMap 会拒绝写入,避免 `HashMap` 和 `TreeMap` 对 key 的认知不一致。
## Map View 行为
`keySet()`、`values()` 和 `entrySet()` 返回的是由原 Map 支持的视图,不是独立副本。
```java
FastMap map = new FastMap<>(false);
map.put(1, "A");
map.put(2, "B");
map.values().remove("A"); // 同时从 map 删除 key=1
map.keySet().remove(2); // 同时从 map 删除 key=2
```
支持的写回操作包括:
- `view.remove(...)`
- `view.clear()`
- `Iterator.remove()`
- `entrySet()` 中 `Map.Entry.setValue(...)`
所有写回操作都会经过 FastMap 的锁、双索引同步和 TTL 元数据清理逻辑。
为了降低并发遍历期间的风险,视图的迭代器基于创建迭代器时的数据快照;因此它不会反映迭代开始后的新增数据,但 `Iterator.remove()` 仍会删除原 Map 中对应的 key。
## 构造方式
| 构造方式 | 过期功能 | 排序功能 |
| --- | --- | --- |
| `new FastMap<>()` | 开启 | 关闭 |
| `new FastMap<>(enableExpire)` | 可配置 | 关闭 |
| `new FastMap<>(enableExpire, enableSort)` | 可配置 | 可配置 |
| `new FastMap<>(enableExpire, comparator)` | 可配置 | 开启 |
未启用排序时调用范围查询、`firstKey()` 或 `lastKey()` 会抛出异常。
## TTL API
```java
Long expireAt = map.expire(key, 5_000L);
Long ttl = map.ttl(key);
```
- `expire(key, ms)`:设置或重置 TTL
- `expire(key, ms, callback)`:设置或重置 TTL,并注册回调
- `ttl(key)`:返回剩余毫秒数
- key 不存在时,`expire()` 返回 `null`
- key 不存在、已过期或未设置 TTL 时,`ttl()` 返回 `null`
- `ms` 不能为负数
调用 `remove()`、`clear()` 或通过 Map View 删除数据时,对应的 TTL 和回调任务也会一起取消。
## 线程安全说明
- 普通读操作使用读锁
- 写入、删除、过期设置及清理使用写锁
- `compute`、`merge`、`putIfAbsent` 等复合方法具有原子性
- 用户提供的过期回调在内部写锁释放后执行
- 回调异常不会中止后续过期清理
- `equals()` 和 `hashCode()` 遵循标准 `Map` 契约
用户传入 `compute`、`merge`、`replaceAll` 等方法的函数会在写锁内执行,应避免耗时操作以及跨线程等待。
## 使用约束
1. 启用自然排序时,Key 必须实现 `Comparable`
2. 自定义 `Comparator` 必须与 `equals()` 一致
3. FastMap 是进程内缓存,不提供持久化和分布式一致性
4. TTL 精度为毫秒,但实际删除时间受线程调度影响
5. 范围查询返回当前时刻的独立 `LinkedHashMap` 快照,修改它不会影响原 Map
## 构建与测试
```bash
mvn clean test
mvn package
```
保留的自动化测试覆盖:
- 并发读写与续期
- 原子 `compute`
- 双索引一致性与异常回滚
- TTL 删除、续期和回调
- Map View 写回
- `equals()` / `hashCode()` 契约
- 超大 TTL 溢出保护
- 后台实例弱引用和回调异常隔离
## License
[MIT License](LICENSE)