FastUtil:为原始类型提升性能的集合框架

张开发
2026/6/21 16:21:19 15 分钟阅读
FastUtil:为原始类型提升性能的集合框架
文章目录一、简介二、使用1、包2、统一命名规则3、与jdk替换对比表1List 互相对应最常用2Set 互相对应去重、ID 集合3原生类型 → 原生类型 Map性能提升4原生类型 → 对象 Map存业务对象5对象 → 原生类型 Map统计对象次数6对象 → 对象 Map比 JDK 更快7有序 Map 对应TreeMap8保持插入顺序 Map4、使用1原始类型 List以 int 为例2原始类型 Map以 int-long 为例3引用类型 Map以 String-String 为例4超大集合64 位索引5Set使用6与 Java Stream 配合推荐方式7序列化注意事项5、工具类三、避坑1、map的key不存在返回02、不用forEach遍历3、非线程安全4、List/Set的remove是删除索引5、toArray () 返回包装类型数组导致内存浪费6、map最好设置初始容量与负载因子7、FastUtil 集合不能直接序列化Redis、Dubbo 会报错8、FastUtil 禁止 key/value 为 null一、简介开源地址https://github.com/vigna/fastutilFastUtil 核心是为原始类型int/long/double 等和引用类型提供类型特化的集合实现彻底规避 JDK 集合的装箱 / 拆箱开销实现速度 2–10 倍提升、内存占用降低 40%–70%广泛用于大数据、高并发、高频计算场景。扩展 Java 集合框架提供类型特定的 Map/Set/List/Queue三大核心能力原始类型集合无装箱 / 拆箱内存更省、速度更快超大集合支持64 位索引的 BigArray/BigList突破2³¹容量限制高性能 I/O二进制 / 文本文件快速读写适配海量数据持久化。只要键或值是原始类型且预计size 10万就必须使用 FastUtil。对于 String/Object预计size 5万或频繁 get/put 时用 Object2ObjectOpenHashMap 替换 HashMap提升 20% 性能。二、使用1、包dependencygroupIdit.unimi.dsi/groupIdartifactIdfastutil/artifactIdversion8.5.18/version/dependency2、统一命名规则FastUtil 类名遵循统一规则集合类[类型][集合类型]如 IntArrayList、LongOpenHashSetMap 类[键类型]2[值类型][实现类型]如 Int2IntOpenHashMap、Long2ObjectAVLTreeMap。3、与jdk替换对比表1List 互相对应最常用JDK 集合FastUtil 等价类说明ArrayListIntegerIntArrayListint 原生列表ArrayListLongLongArrayListlong 原生列表ArrayListDoubleDoubleArrayListdouble 原生列表ArrayListFloatFloatArrayListfloat 原生列表ArrayListByteByteArrayListbyte 原生列表ArrayListShortShortArrayListshort 原生列表ArrayListBooleanBooleanArrayListboolean 原生列表ArrayListObjectObjectArrayList对象类型比 JDK 更快2Set 互相对应去重、ID 集合JDK 集合FastUtil 等价类性能/内存优势HashSetIntegerIntOpenHashSet内存减少 60%HashSetLongLongOpenHashSet最常用存用户IDHashSetDoubleDoubleOpenHashSet原生 doubleHashSetObjectObjectOpenHashSet对象版本比 JDK 快LinkedHashSetIntLinkedOpenHashSet保持插入顺序3原生类型 → 原生类型 Map性能提升JDK 集合FastUtil 等价类典型用途HashMapInteger, IntegerInt2IntOpenHashMap计数、统计HashMapLong, LongLong2LongOpenHashMap用户ID → 计数HashMapInteger, LongInt2LongOpenHashMap次数统计HashMapLong, IntegerLong2IntOpenHashMap在线人数、状态HashMapDouble, DoubleDouble2DoubleOpenHashMap指标计算4原生类型 → 对象 Map存业务对象JDK 集合FastUtil 等价类HashMapInteger, UserInt2ObjectOpenHashMapUserHashMapLong, OrderLong2ObjectOpenHashMapOrderHashMapInteger, StringInt2ObjectOpenHashMapStringHashMapLong, ObjectLong2ObjectOpenHashMapObject5对象 → 原生类型 Map统计对象次数JDK 集合FastUtil 等价类HashMapUser, IntegerObject2IntOpenHashMapUserHashMapString, LongObject2LongOpenHashMapStringHashMapOrder, LongObject2LongOpenHashMapOrder6对象 → 对象 Map比 JDK 更快JDK 集合FastUtil 等价类HashMapObject, ObjectObject2ObjectOpenHashMapHashMapString, StringObject2ObjectOpenHashMapString,String7有序 Map 对应TreeMapJDKFastUtilTreeMapInteger, IntegerInt2IntAVLTreeMapTreeMapLong, LongLong2LongAVLTreeMapTreeMapLong, ObjectLong2ObjectAVLTreeMapAVLTree 比 JDK TreeMap 更快、内存更小。8保持插入顺序 MapJDKFastUtilLinkedHashMapInt2LongLinkedOpenHashMapLinkedHashMapLong2LongLinkedOpenHashMap4、使用1原始类型 List以 int 为例// 1. 初始化支持初始容量、负载因子IntArrayListintListnewIntArrayList(100);// 初始容量 100// 2. 增删改查无装箱/拆箱intList.add(10);// 直接存 int无需 Integer.valueOf(10)intList.add(20);intList.set(1,25);// 替换索引 1 的值intList.getInt(1);// 获取值返回 int无拆箱intList.removeInt(0);// 删除指定索引的值// 3. 快速遍历优先用类型迭代器避免 for-each 装箱longsum0;IntIteratoriteratorintList.iterator();while(iterator.hasNext()){sumiterator.nextInt();// 直接获取 int}// 4. 批量操作int[]arraynewint[]{1,2,3};intList.addAll(IntList.of(array));// 批量添加数组int[]resultintList.toIntArray();// 转原始数组// 快速转成原始数组零拷贝int[]arraylist.elements();// 注意不要再往 list 里 addint[]safeArraylist.toIntArray();// 推荐防御性拷贝// 从已有数组创建零拷贝int[]rawnewint[]{1,2,3,4};IntArrayListlistIntArrayList.wrap(raw);// 直接包装不复制2原始类型 Map以 int-long 为例// 1. 初始化推荐 OpenHash 实现速度/内存最优Int2LongOpenHashMapmapnewInt2LongOpenHashMap();// 或指定初始容量与负载因子负载因子 0.75 为平衡选择Int2LongOpenHashMapmapWithCapnewInt2LongOpenHashMap(500,0.75f);// 2. 增删改查无装箱/拆箱map.put(1,100L);// 直接存 int key/long valuemap.put(2,200L);longvaluemap.get(1);// 获取 long无拆箱map.remove(2);// 删除 key// 3. 默认返回值key 不存在时返回指定值默认返回0map.defaultReturnValue(-1L);map.get(3);// 返回 -1L而非 null// 4. 遍历优先用 fastEntrySet减少对象创建Int2LongMap.FastEntrySetentrySetmap.int2LongEntrySet();for(Int2LongMap.Entryentry:entrySet){intkeyentry.getIntKey();longvalentry.getLongValue();}// 5、自增操作// 计数器模式比 compute 快 3~5 倍加一// 等价于 map.put(userId, map.getOrDefault(userId, 0L) 1);map.addTo(1,1L);3引用类型 Map以 String-String 为例// 差JDK 通用性能一般MapString,ObjectmapnewHashMap();MapString,StringconfignewHashMap();// 好FastUtil Object 优化内存/速度提升明显Object2ObjectOpenHashMapString,ObjectobjMapnewObject2ObjectOpenHashMap();Object2ObjectOpenHashMapString,StringstrMapnewObject2ObjectOpenHashMap();// 常用构造预估容量 负载因子避免 rehashintexpectedSize500_000;// 如配置项或缓存Object2ObjectOpenHashMapString,ObjectobjMapnewObject2ObjectOpenHashMap(expectedSize,0.9f);Object2ObjectOpenHashMapString,StringstrMapnewObject2ObjectOpenHashMap(expectedSize,0.9f);// 启用引用相等推荐String 场景下加速 20%~30%但需确保无 nullobjMap.referenceEquality();// 或 strMap.referenceEquality();// 引用相等 比较而非 equals()类似 IdentityHashMapObject2ObjectOpenHashMapString,StringrefMapnewObject2ObjectOpenHashMap();refMap.put(a,A);// 若用 interned 字符串引用相等可提升性能Stringkeya.intern();refMap.get(key);// 快速命中4超大集合64 位索引// 存储超过 2^31 个元素的集合IntBigArrayBigListbigListnewIntBigArrayBigList();// 64 位索引添加元素bigList.add(10);bigList.add(20);// 64 位索引获取intvalbigList.get(0L);// 大小返回 long 类型longsizebigList.size64();5Set使用IntOpenHashSetsetnewIntOpenHashSet(1_000_000,0.9f);set.add(123);if(set.add(123)){/* 第一次插入 */}// 快速转原始数组int[]arrayset.toArray(newint[set.size()]);6与 Java Stream 配合推荐方式Int2LongOpenHashMapmap...;// FastUtil 自带原始流比装箱流快 5~10 倍longsummap.int2LongEntrySet().fastForEach(entry-totalentry.getLongValue());// 或者并行原始流map.int2LongEntrySet().parallelStream().forEach(entry-updateSomeGlobalCounter(entry));7序列化注意事项// FastUtil 默认实现了 Serializable但建议显式指定版本privatestaticfinallongserialVersionUID1L;// 大 Map 序列化建议使用 FastUtil 自带的二进制格式比 JDK 快 5~10 倍ByteBufferOutputout...;Int2LongBinaryOpenHashMap.write(out,map);// 极快5、工具类publicfinalclassFastUtilKit{// 1. 创建 Map自动设置默认值publicstaticLong2LongOpenHashMapnewLong2LongMap(){Long2LongOpenHashMapmapnewLong2LongOpenHashMap();map.defaultReturnValue(-1L);returnmap;}// 2. 带容量的 Map推荐publicstaticLong2LongOpenHashMapnewLong2LongMap(intsize){Long2LongOpenHashMapmapnewLong2LongOpenHashMap(size);map.defaultReturnValue(-1L);returnmap;}// 3. 安全获取值publicstaticlongget(Long2LongMapmap,longkey){returnmap.containsKey(key)?map.get(key):-1L;}// 4. 安全遍历 MappublicstaticvoidforEach(Long2LongMapmap,LongLongConsumerconsumer){for(Long2LongMap.Entryentry:map.long2LongEntrySet()){consumer.accept(entry.getLongKey(),entry.getLongValue());}}// 5. 安全遍历 SetpublicstaticvoidforEach(LongSetset,LongConsumerconsumer){LongIteratoritset.iterator();while(it.hasNext()){consumer.accept(it.nextLong());}}}三、避坑1、map的key不存在返回0JDK 的 Map 取不到值返回 null但 FastUtil 原始类型 Map 没有 null默认返回 0。// 错误Long2LongMapmapnewLong2LongOpenHashMap();longvalmap.get(999L);// key 不存在返回 0if(val0){// 你以为是不存在其实可能业务值真的是 0}// 方案 1先判断 containsKeyif(map.containsKey(999L)){longvalmap.get(999L);}// 方案 2设置默认返回值map.defaultReturnValue(-1L);// 不存在返回 -1longvalmap.get(999L);// -1// 方案 3使用 getOrDefaultlongvalmap.getOrDefault(999L,-1L);2、不用forEach遍历FastUtil 如果你用普通的 forEach、entrySet性能会掉回 JDK 水平甚至更差。// 错误会把 int 包装成 Integerfor(Map.EntryLong,Longentry:map.entrySet()){}// Map 最快遍历 性能最大化、无装箱Long2LongOpenHashMapmapnewLong2LongOpenHashMap();for(Long2LongMap.Entryentry:map.long2LongEntrySet()){longkeyentry.getLongKey();longvalueentry.getLongValue();}// Set 最快遍历LongSetsetnewLongOpenHashSet();LongIteratoriteratorset.iterator();while(iterator.hasNext()){longvaliterator.nextLong();// 无装箱}3、非线程安全FastUtil 集合全部非线程安全并发会导致脏数据 / 死循环// 方案 1加 synchronizedsynchronized(map){map.put(k,v);}// 方案 2使用 FastUtil 同步包装Long2LongMapsyncMapLong2LongMaps.synchronize(map);// 自定义分段锁// 1. 定义分段数建议 CPU核数×2privatestaticfinalintSEGMENT_NUM16;// 2. 每个段一个独立 FastUtil Map 锁privatefinalLong2LongMap[]segmentsnewLong2LongMap[SEGMENT_NUM];privatefinalObject[]locksnewObject[SEGMENT_NUM];// 初始化publicConcurrentLong2LongMap(){for(inti0;iSEGMENT_NUM;i){segments[i]newLong2LongOpenHashMap();locks[i]newObject();}}// 分段路由key哈希取模privateintsegmentIdx(longkey){return(int)(key(SEGMENT_NUM-1));}// 线程安全 putpublicvoidput(longkey,longvalue){intidxsegmentIdx(key);synchronized(locks[idx]){// 只锁一个段segments[idx].put(key,value);}}// 线程安全 getpubliclongget(longkey){intidxsegmentIdx(key);synchronized(locks[idx]){returnsegments[idx].get(key);}}4、List/Set的remove是删除索引IntListlistnewIntArrayList();list.add(10);list.add(20);list.add(30);list.remove(20);// 错误// 你以为删除值 20实际是删除索引 20list.removeInt(intindex);// 删除索引list.rem(intvalue);// 删除值推荐5、toArray () 返回包装类型数组导致内存浪费Integer[]arrlist.toArray();// 错误包装类型浪费内存int[]arrlist.toIntArray();// 原生数组性能最优6、map最好设置初始容量与负载因子FastUtil 默认容量很小16批量插入必须指定容量否则不断扩容、复制数组。// 预估 100 万数据Long2LongOpenHashMapmapnewLong2LongOpenHashMap(1_000_000);// 推荐固定0.75f 默认也是0.75newLong2LongOpenHashMap(100000,0.75f);7、FastUtil 集合不能直接序列化Redis、Dubbo 会报错FastUtil 集合 没有实现 Java 序列化标准存 Redis、Dubbo 传输会失败。// 方案 1转成原生数组再存储long[]keysmap.keySet().toLongArray();long[]valuesmap.values().toLongArray();// 方案 2使用 BinIO 二进制序列化BinIO.storeObject(map,file);8、FastUtil 禁止 key/value 为 nullJDK HashMap 允许存 null但 FastUtil 大部分集合不允许 null。存 null 直接抛NullPointerException。可以考虑用defaultReturnValue(-1)代替 null用特殊值表示空-1、-2、Long.MIN_VALUE

更多文章