FastUtil高性能集合
FastUtil是由意大利计算机科学家 Sebastiano Vigna 维护的开源库,它为Java原始类型()提供了类型特化的集合实现,性能通常比 JDK 集合快 2~5 倍,内存占用降低40%~70%。在高性能后端、游戏服务器、大数据处理、量化交易等场景中,几乎是标配。
·
文章目录
- 1 FastUtil
-
- 1.1 简介
- 1.2 为什么选择 FastUtil
- 1.3 核心集合类型速查表
- 1.4 最佳实践代码示例
-
- 1.4.1 基本替换(最常见)
- 1.4.2 推荐初始化方式(避免频繁扩容)
- 1.4.3 高频操作性能对比 & 推荐写法
- 1.4.4 List 使用技巧
- 1.4.5 Set 使用技巧
- 1.4.6 与 Java Stream 配合(推荐方式)
- 1.4.7 序列化注意事项
- 1.4.8 String-Object / String-String 替换(最常见场景)
- 1.4.9 高频操作性能对比 & 推荐写法(Object 版)
- 1.4.10 List/Set 使用技巧(String 版)
- 1.4.11 与 Java Stream 配合(Object 流优化)
- 1.4.12 序列化注意事项(Object 版)
- 1.5 生产环境避坑清单
1 FastUtil
1.1 简介
FastUtil 是由意大利计算机科学家 Sebastiano Vigna 维护的开源库,它为 Java 原始类型(primitive types)提供了类型特化的集合实现,性能通常比 JDK 集合快 2~5 倍,内存占用降低 40%~70%。在高性能后端、游戏服务器、大数据处理、量化交易等场景中,几乎是标配。
1.2 为什么选择 FastUtil
| 场景 | JDK HashMap<Integer, Long> | FastUtil Int2LongOpenHashMap |
|---|---|---|
| 内存占用(1000万条) | ~1.1 GB | ~320 MB |
| put/get 速度 | 基准 | 2.8~4.5× |
| GC 压力 | 高(大量 Integer/Long 包装对象) | 极低(零装箱) |
| 场景 | JDK HashMap<String, Object> | FastUtil Object2ObjectOpenHashMap<String, Object> |
|---|---|---|
| 内存占用(1000万条) | ~1.2 GB | ~720 MB |
| put/get 速度 | 基准 | 1.5~3×(优化哈希 + 引用相等) |
| GC 压力 | 中等(String 不可变,但 Object 可能有引用) | 低(高效迭代 + 零额外包装) |
关键:String 不是原始类型,所以无 String2StringOpenHashMap 专用类,但 Object2ObjectOpenHashMap<String, String> 等通用类已足够高效。启用引用相等(reference equality)可进一步加速(用 == 而非 equals())。
Maven/Gradle 依赖
<!-- Maven -->
<dependency>
<groupId>it.unimi.dsi</groupId>
<artifactId>fastutil</artifactId>
<version>8.5.15</version>
</dependency>
1.3 核心集合类型速查表
| 原始类型 | List | Set | Map(key→value) |
|---|---|---|---|
| Object (e.g., String) | ObjectArrayList | ObjectOpenHashSet | Object2ObjectOpenHashMap<String, Object>、Object2ObjectOpenHashMap<String, String> |
| int | IntArrayList | IntOpenHashSet | Int2IntOpenHashMap、Int2ObjectOpenHashMap |
| long | LongArrayList | LongOpenHashSet | Long2LongOpenHashMap、Long2ObjectOpenHashMap |
| double | DoubleArrayList | DoubleOpenHashSet | Double2DoubleOpenHashMap |
| float | FloatArrayList | FloatOpenHashSet | —— |
| byte | ByteArrayList | ByteOpenHashSet | Byte2IntOpenHashMap |
| char | CharArrayList | CharOpenHashSet | —— |
| short | ShortArrayList | —— | —— |
| boolean | —— | BooleanOpenHashSet | —— |
推荐永远使用 OpenHash 系列(默认实现),它比旧的 RBTree/Champ 更快且内存更省。
1.4 最佳实践代码示例
1.4.1 基本替换(最常见)
// 差:大量装箱 + 高内存
Map<Integer, Long> map = new HashMap<>();
// 好:零装箱 + 极致性能
Int2LongOpenHashMap map = new Int2LongOpenHashMap();
// 常用构造方式
Int2LongOpenHashMap map = new Int2LongOpenHashMap(1_000_000); // 预估容量
Int2LongOpenHashMap map = new Int2LongOpenHashMap(1_000_000, 0.8f); // 指定负载因子
1.4.2 推荐初始化方式(避免频繁扩容)
// 最佳:预估容量 + 高负载因子(FastUtil 默认 0.8~0.9,比 JDK 0.75 高)
int expectedSize = 5_000_000;
Int2ObjectOpenHashMap<User> userMap = new Int2ObjectOpenHashMap<>(expectedSize, 0.9f);
// 如果你能接受极少数 rehash,负载因子甚至可以调到 0.95f
Int2IntOpenHashMap counter = new Int2IntOpenHashMap(100_000, 0.95f);
1.4.3 高频操作性能对比 & 推荐写法
Int2LongOpenHashMap map = new Int2LongOpenHashMap();
// 1. get 默认值(避免 containsKey + get 两次查找)
long value = map.getOrDefault(userId, 0L); // 推荐
long value = map.containsKey(id) ? map.get(id) : 0L; // 慢 2 倍!
// 2. 计数器模式(比 compute 快 3~5 倍)
map.addTo(userId, 1L); // 原子 + 极快
// 等价于 map.put(userId, map.getOrDefault(userId, 0L) + 1);
// 3. 自增 1 的最快写法
map.addTo(key, 1L);
// 4. 批量插入(FastUtil 独有 API,比 putAll 快 30%)
int[] keys = ...;
long[] values = ...;
map.putAll(IntArrays.forceCopy(keys), LongArrays.forceCopy(values), keys.length);
1.4.4 List 使用技巧
// 动态数组(比 ArrayList<Integer> 快 3~5 倍)
IntArrayList list = new IntArrayList();
list.add(1);
list.add(2);
// 快速转成原始数组(零拷贝!)
int[] array = list.elements(); // 注意:不要再往 list 里 add!
int[] safeArray = list.toIntArray(); // 推荐:防御性拷贝
// 从已有数组创建(零拷贝)
int[] raw = newint[]{1,2,3,4};
IntArrayList list = IntArrayList.wrap(raw); // 直接包装,不复制
1.4.5 Set 使用技巧
IntOpenHashSet set = new IntOpenHashSet(1_000_000, 0.9f);
set.add(123);
if (set.add(123)) { /* 第一次插入 */ }
// 快速转原始数组
int[] array = set.toArray(newint[set.size()]);
1.4.6 与 Java Stream 配合(推荐方式)
Int2LongOpenHashMap map = ...;
// FastUtil 自带原始流,比装箱流快 5~10 倍
long sum = map.int2LongEntrySet().fastForEach(entry -> total += entry.getLongValue());
// 或者并行原始流
map.int2LongEntrySet().parallelStream()
.forEach(entry -> updateSomeGlobalCounter(entry));
1.4.7 序列化注意事项
// FastUtil 默认实现了 Serializable,但建议显式指定版本
private static final long serialVersionUID = 1L;
// 大 Map 序列化建议使用 FastUtil 自带的二进制格式(比 JDK 快 5~10 倍)
ByteBufferOutput out = ...;
Int2LongBinaryOpenHashMap.write(out, map); // 极快!
1.4.8 String-Object / String-String 替换(最常见场景)
// 差:JDK 通用,性能一般
Map<String, Object> map = new HashMap<>();
Map<String, String> config = new HashMap<>();
// 好:FastUtil Object 优化,内存/速度提升明显
Object2ObjectOpenHashMap<String, Object> objMap = new Object2ObjectOpenHashMap<>();
Object2ObjectOpenHashMap<String, String> strMap = new Object2ObjectOpenHashMap<>();
// 常用构造:预估容量 + 负载因子(避免 rehash)
int expectedSize = 500_000; // 如配置项或缓存
Object2ObjectOpenHashMap<String, Object> objMap = new Object2ObjectOpenHashMap<>(expectedSize, 0.9f);
Object2ObjectOpenHashMap<String, String> strMap = new Object2ObjectOpenHashMap<>(expectedSize, 0.9f);
// 启用引用相等(推荐:String 场景下加速 20%~30%,但需确保无 null)
objMap.referenceEquality(); // 或 strMap.referenceEquality();
1.4.9 高频操作性能对比 & 推荐写法(Object 版)
Object2ObjectOpenHashMap<String, Object> map = new Object2ObjectOpenHashMap<>();
// 1. get 默认值(单次查找,避免 containsKey + get)
Object value = map.getOrDefault("userKey", null); // 推荐,零额外开销
// 2. 合并操作(Object 版 computeIfAbsent,比 JDK 快 2x)
map.computeIfAbsent("key", k -> new Object()); // 如懒加载 JSON 对象
// 3. 计数器模式(String key + int value,用混合类型更优)
Object2IntOpenHashMap<String> counter = new Object2IntOpenHashMap<>();
counter.addTo("item", 1); // 原子自增,比纯 Object 快 3~5x
// 4. 批量插入(FastUtil 独有,适用于 CSV/JSON 加载)
String[] keys = {"k1", "k2"};
Object[] values = {new Object(), "val2"};
map.putAll(keys, values, keys.length); // 比 putAll 快 25%~40%
1.4.10 List/Set 使用技巧(String 版)
// 动态 String 列表(比 ArrayList<String> 快 2~4x,内存省 30%)
ObjectArrayList<String> list = new ObjectArrayList<>();
list.add("item1");
list.add("item2");
// 快速转数组(零拷贝)
String[] array = list.toStringArray(); // 防御性拷贝,推荐
// 从数组创建(零拷贝包装)
String[] raw = {"a", "b", "c"};
ObjectArrayList<String> list = ObjectArrayList.wrap(raw);
// Set 去重 String(高效哈希)
ObjectOpenHashSet<String> set = new ObjectOpenHashSet<>(1_000_000, 0.9f);
set.add("unique");
if (set.add("duplicate")) { /* 插入成功 */ }
String[] uniqueArray = set.toStringArray();
1.4.11 与 Java Stream 配合(Object 流优化)
Object2ObjectOpenHashMap<String, String> map = ...;
// FastUtil 原始迭代器流(比 JDK stream 快 3~7x,无装箱)
long count = map.object2ObjectEntrySet()
.fastForEach(entry -> total += entry.getKey().length()); // e.g., 统计键长度
// 并行处理(大 String 集合)
map.object2ObjectEntrySet().parallelStream()
.forEach(entry -> process(entry.getStringKey(), entry.getStringValue()));
1.4.12 序列化注意事项(Object 版)
// Object Map 序列化:用 FastUtil 二进制(比 JDK 快 4~8x)
ByteBufferOutput out = ...;
Object2ObjectOpenHashMap.writeObject2Object(out, map); // 专为 Object2Object
// 反序列化
Object2ObjectOpenHashMap<String, Object> loaded = Object2ObjectOpenHashMap.readObject2Object(in);
1.5 生产环境避坑清单
| 坑点 | 正确做法 |
|---|---|
| 使用 new HashMap<Integer,…> | 改用 new Int2XxxOpenHashMap() |
| map.get(key) 返回包装类 | 使用原始方法 map.getOrDefault(intKey, 0L) |
| List 使用 ArrayList | 改用 IntArrayList |
| for (Integer i : list) | 用 for (int i : list) 或 IntIterator |
| 序列化超大 Map 超时 | 改用 FastUtil 二进制序列化 API |
| 并发修改导致异常 | 使用 Int2LongOpenHashMap + 分段锁或外部锁 |
| 使用 new HashMap<String, Object> | 改用 new Object2ObjectOpenHashMap<String, Object>() + referenceEquality() |
| String key + 原始 value 仍用 Object | 优先 Object2IntOpenHashMap(混合优化) |
| 迭代 Object 集合用 for-each(隐含 equals() 调用) | 用 fastIterator() 或 referenceEquality() 加速 |
| 大 String Set 内存爆炸 | 用 ObjectOpenHashSet + 预估容量 |
| null key/value 处理 | FastUtil 默认不支持 null key;用 defaultReturnValue() 设置默认值 |
| 与 Lombok/Spring Boot 集成冲突 | 显式导入 it.unimi.dsi.fastutil.objects.* |
结论:一条替换原则
- 只要键或值是
原始类型,且预计size > 10万,就必须使用FastUtil - 对于
String/Object:预计size > 5万,或频繁get/put时,用Object2ObjectOpenHashMap替换HashMap,提升 20%+ 性能。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)