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%+ 性能。
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐