Apache Commons Collections 配置参数详解

详解 commons-collections4 4.6.0 的 Maven 依赖参数、多值 Map 与分段锁 Map 构造参数、不可变装饰器、各 Map 与 Bag 实现复杂度对比、JVM 参数与 3.x 反序列化安全边界,均配实测回显。

Apache Commons CollectionsApache Commons Collections2026/10/13

Apache Commons Collections 配置参数详解

目标:把 commons-collections4 4.6.0 所有会影响行为与性能的参数讲透——Maven 依赖参数、集合实现构造参数(容量/桶数)、不可变与固定大小装饰器、各实现复杂度选型、JVM 运行参数与安全边界。已在 Ubuntu 22.04.5 LTS(x86_64、OpenJDK 17.0.20.1、Maven 3.9.16、commons-collections4 4.6.0)实测通过。

一、环境信息

本篇 jar 获取只需两条命令(独立可执行):

curl -sSOL https://downloads.apache.org/commons/collections/binaries/commons-collections4-4.6.0-bin.tar.gz
tar xzf commons-collections4-4.6.0-bin.tar.gz commons-collections4-4.6.0/commons-collections4-4.6.0.jar

实测校验(解压后 jar 就位):

commons-collections4-4.6.0/commons-collections4-4.6.0.jar

Commons Collections 是 JDK 内置集合之外的集合扩展库,参数分三层:依赖层(Maven 坐标与 scope)、实现层(构造器容量参数)、运行层(JVM 参数),下面逐层展开。

💡 技术拓展:集合扩展库的「参数化」思路各不相同——

库参数化风格典型参数
JDK 内置 java.util构造器容量 + 负载因子HashMap(initialCapacity, loadFactor)
Apache Commons Collections沿用 JDK 风格 + 结构特有参数ArrayListValuedHashMap(map容量, list容量)、StaticBucketMap(桶数)
Guava工厂方法 + expectedSizesMaps.newHashMapWithExpectedSize(n)
Eclipse Collections构造器容量为主UnifiedMap(capacity)

本文实测 commons-collections4 4.6.0:它最大程度沿用 JDK 构造器惯例,另给多值 Map、分段锁 Map 等自有结构补充了专用容量参数。

二、Maven 依赖参数详解

依赖参数决定「用哪个版本、什么范围、进不进产物」。标准依赖块如下(与《Ubuntu 22.04 安装 Apache Commons Collections 指南》中 5.1 的工程一致,可独立复用):

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.6.0</version>
</dependency>

逐项说明:

  • groupId=org.apache.commons:4.x 官方 groupId;注意 3.x 的坐标是 commons-collections:commons-collections,写错会拉到有漏洞历史的旧系列。
  • artifactId=commons-collections4:4.x 专用构件名,自带 collections4 包名前缀(org.apache.commons.collections4),与 3.x 物理隔离。
  • version=4.6.0:2026-08-10 发布的官方最新版,Maven 中央仓库 maven-metadata.xml 中 latest 与 release 均为 4.6.0(可选 4.0/4.1/4.2/4.3/4.4/4.5.0-M1/4.5.0-M2/4.5.0-M3/4.5.0/4.6.0)。
  • scope:缺省 compile,参与编译、测试与打包全流程。

scope 取值对比(如何选型):

scope编译期运行期打包进产物适用
compile(缺省)是是是业务代码直接使用集合类型
provided是否否目标环境已自带该 jar(如共享 lib 目录统一投放)
test是仅测试否只在单测里做集合工具断言

关键差异:本库是零传递依赖单 jar,compile 与 provided 是仅有的两种现实选择;判定标准是「最终运行环境的类路径上是否已有人放置这个 jar」。

验证依赖已正确解析(compile scope 与模块名一并展示):

mvn dependency:list | grep collections4

实测回显:

[INFO]    org.apache.commons:commons-collections4:jar:4.6.0:compile -- module org.apache.commons.collections4

依赖末尾的 module 信息来自 jar 内 META-INF/versions/9/module-info.class(多版本 jar 附带 JPMS 描述,4.6.0 实测存在),说明它同时兼容模块化与非模块化工程。

三、集合实现构造参数详解

实现层参数只存在于带内部数组的结构上。用 javap 列出 4.6.0 各实现的构造器签名,先看多值 Map 与分段锁 Map(J 指向解压出的主 jar):

javap -cp $J org.apache.commons.collections4.multimap.ArrayListValuedHashMap | grep ArrayListValuedHashMap
javap -cp $J org.apache.commons.collections4.multimap.HashSetValuedHashMap | grep HashSetValuedHashMap
javap -cp $J org.apache.commons.collections4.map.StaticBucketMap | grep "StaticBucketMap("

实测回显:

public org.apache.commons.collections4.multimap.ArrayListValuedHashMap()
public org.apache.commons.collections4.multimap.ArrayListValuedHashMap(int)
public org.apache.commons.collections4.multimap.ArrayListValuedHashMap(int, int)
public org.apache.commons.collections4.multimap.ArrayListValuedHashMap(java.util.Map<? extends K, ? extends V>)
public org.apache.commons.collections4.multimap.ArrayListValuedHashMap(org.apache.commons.collections4.MultiValuedMap<? extends K, ? extends V>)
public org.apache.commons.collections4.multimap.HashSetValuedHashMap()
public org.apache.commons.collections4.multimap.HashSetValuedHashMap(int)
public org.apache.commons.collections4.multimap.HashSetValuedHashMap(int, int)
public org.apache.commons.collections4.multimap.HashSetValuedHashMap(java.util.Map<? extends K, ? extends V>)
public org.apache.commons.collections4.multimap.HashSetValuedHashMap(java.util.Map<? extends K, ? extends V>)
public org.apache.commons.collections4.map.StaticBucketMap()
public org.apache.commons.collections4.map.StaticBucketMap(int)

参数含义与默认值:

  • ArrayListValuedHashMap(int initialMapCapacity):外层 HashMap 初始容量,默认 16;int,int 双参版本第二参 initialListCapacity 是每个键对应内层 ArrayList 的初始容量,默认 3。键少值多时调大第二参可减少内层扩容复制。
  • HashSetValuedHashMap(int initialMapCapacity, int initialSetCapacity):同理,第二参是每个键对应内层 HashSet 的初始容量(默认 2),值会去重时用它。
  • StaticBucketMap(int nbBuckets):把整张 Map 预先切分为固定个数的桶,每桶一把锁,默认 256 个桶;桶数决定并发度上限,之后增删元素不再扩容重排。

实测验证容量参数可用且行为正确(CapacityDemo.java,编译 -cp 同上节 $J):

import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
import org.apache.commons.collections4.multimap.HashSetValuedHashMap;
import org.apache.commons.collections4.map.StaticBucketMap;

public class CapacityDemo {
    public static void main(String[] args) {
        MultiValuedMap<String, String> m1 = new ArrayListValuedHashMap<>(64, 8);
        m1.put("k", "v1");
        System.out.println("ArrayListValuedHashMap(64, 8): get(k) = " + m1.get("k"));
        MultiValuedMap<String, String> m2 = new HashSetValuedHashMap<>(64, 8);
        m2.put("k", "v1");
        m2.put("k", "v1");
        System.out.println("HashSetValuedHashMap(64, 8) dedup: get(k) = " + m2.get("k"));
        StaticBucketMap<String, String> m3 = new StaticBucketMap<>(256);
        m3.put("k", "v");
        System.out.println("StaticBucketMap(256): get(k) = " + m3.get("k"));
    }
}
javac -nowarn -encoding UTF-8 -cp $J CapacityDemo.java
java -cp .:$J CapacityDemo

实测回显:

ArrayListValuedHashMap(64, 8): get(k) = [v1]
HashSetValuedHashMap(64, 8) dedup: get(k) = [v1]
StaticBucketMap(256): get(k) = v

三类输出均正确。注意 HashSetValuedHashMap 重复 put 同键同值两次后 get 只返回一个元素,这就是它的去重语义。另外两个常用结构没有容量参数,选型时不要白找:HashBag 只有 ()、(Collection)、(Iterable) 三个构造器(javap 实测确认),PatriciaTrie 只有 () 与 (Map) 且键类型固定为 String(泛型签名 PatriciaTrie<V> extends AbstractPatriciaTrie<String, V>,javap 实测确认)——前者按 HashMap 语义自动扩容,后者容量随键内容自然生长。

四、不可变与固定大小装饰器

除了构造容量,4.x 暴露配置意图的另一半方式是「装饰器」:把任意 Collection 包一层改变其行为约定。先看不可变视图的实测行为(ImmutableDemo.java):

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.collections4.ListUtils;

public class ImmutableDemo {
    public static void main(String[] args) {
        List<String> src = new ArrayList<>(Arrays.asList("a", "b"));
        List<String> ro = ListUtils.unmodifiableList(src);
        System.out.println("ro = " + ro);
        try {
            ro.add("c");
        } catch (UnsupportedOperationException e) {
            System.out.println("ro.add(c) -> UnsupportedOperationException");
        }
        src.add("c");
        System.out.println("after src.add(c): ro = " + ro);
        List<String> fixed = ListUtils.unmodifiableList(Arrays.asList("x", "y"));
        System.out.println("fixed = " + fixed);
    }
}
javac -nowarn -encoding UTF-8 -cp $J ImmutableDemo.java
java -cp .:$J ImmutableDemo

实测回显:

ro = [a, b]
ro.add(c) -> UnsupportedOperationException
after src.add(c): ro = [a, b, c]
fixed = [x, y]

逐项解读:unmodifiableList 是装饰器视图,拦截写入并抛 UnsupportedOperationException,但不复制数据;要真正不可变,需像 fixed 那样包一个本身不再变更的源(Arrays.asList 定长)或先复制再包。同族装饰器按结构对应:ListUtils/SetUtils/MapUtils/CollectionUtils 均有 unmodifiableXxx;MapUtils 还提供 fixedSizeMap/fixedSizeSortedMap(javap 实测存在),允许改值但禁止增删键,适合「键集合固定」的配置表场景。配套的空安全工具 SetUtils.emptySet() 与 MapUtils.emptyIfNull(null) 实测返回 size=0 的安全空集合,用于替换手写 null 判断。

五、各 Map/Bag 实现复杂度与实测对比

选实现就是选复杂度与功能约束的组合:

实现putget/containsKey有序性特殊能力/约束
HashMap(JDK)O(1) 均摊O(1)无基准实现
LinkedMapO(1)O(1)插入序额外双向链表,支持 firstKey/nextKey 有序遍历
DualHashBidiMapO(1)O(1)无正反两张 HashMap,双向查找,值不重复
DualTreeBidiMapO(log n)O(log n)键序正反两张 TreeMap,inverseBidiMap 同步排序
PatriciaTrieO(k) k=键长O(k)键字典序前缀检索 prefixMap,键固定 String
HashBag/HashMultiSetO(1) 均摊O(1)无计数语义 add(e,n)/getCount(e)

关键差异:需要双向查值选 Dual 系(性能敏感用 Hash、要有序用 Tree);需要前缀搜索只有 PatriciaTrie;计数统计用 Bag/MultiSet;既要插入序又要 MapIterator 写入用 LinkedMap。

实测数据(BenchDemo.java,N=200000 次 put + N 次 get,JVM 固定 -Xms256m -Xmx512m,毫秒):

import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections4.bag.HashBag;
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
import org.apache.commons.collections4.map.LinkedMap;
import org.apache.commons.collections4.trie.PatriciaTrie;

public class BenchDemo {
    static final int N = 200000;

    public static void main(String[] args) {
        Map<Integer, Integer> hm = new HashMap<>();
        Map<Integer, Integer> lm = new LinkedMap<>();
        Map<Integer, Integer> bm = new DualHashBidiMap<>();
        PatriciaTrie<Integer> trie = new PatriciaTrie<>();
        HashBag<Integer> bag = new HashBag<>();

        long t1 = System.nanoTime();
        for (int i = 0; i < N; i++) { hm.put(i, i); }
        for (int i = 0; i < N; i++) { hm.get(i); }
        long t2 = System.nanoTime();
        for (int i = 0; i < N; i++) { lm.put(i, i); }
        for (int i = 0; i < N; i++) { lm.get(i); }
        long t3 = System.nanoTime();
        for (int i = 0; i < N; i++) { bm.put(i, i); }
        for (int i = 0; i < N; i++) { bm.get(i); }
        long t4 = System.nanoTime();
        for (int i = 0; i < N; i++) { trie.put(String.valueOf(i), i); }
        for (int i = 0; i < N; i++) { trie.get(String.valueOf(i)); }
        long t5 = System.nanoTime();
        for (int i = 0; i < N; i++) { bag.add(i); }
        for (int i = 0; i < N; i++) { bag.getCount(i); }
        long t6 = System.nanoTime();

        System.out.println("N = " + N + " (put+get, each N times)");
        System.out.printf("HashMap            put+get: %6.1f ms%n", (t2 - t1) / 1e6);
        System.out.printf("LinkedMap          put+get: %6.1f ms%n", (t3 - t2) / 1e6);
        System.out.printf("DualHashBidiMap    put+get: %6.1f ms%n", (t4 - t3) / 1e6);
        System.out.printf("PatriciaTrie       put+get: %6.1f ms%n", (t5 - t4) / 1e6);
        System.out.printf("HashBag            add+getCount: %6.1f ms%n", (t6 - t5) / 1e6);
    }
}
javac -nowarn -encoding UTF-8 -cp $J BenchDemo.java
java -Xms256m -Xmx512m -cp .:$J BenchDemo

实测回显(连续两轮,消除偶发抖动):

N = 200000 (put+get, each N times)
HashMap            put+get:   38.6 ms
LinkedMap          put+get:   71.8 ms
DualHashBidiMap    put+get:   54.4 ms
PatriciaTrie       put+get:  157.4 ms
HashBag            add+getCount:   30.1 ms

N = 200000 (put+get, each N times)
HashMap            put+get:   38.4 ms
LinkedMap          put+get:   75.6 ms
DualHashBidiMap    put+get:   55.6 ms
PatriciaTrie       put+get:  149.0 ms
HashBag            add+getCount:   20.5 ms

解读:HashMap 是基准;LinkedMap 比它慢约一倍,来自维护插入序的双向链表,换来的是 O(1) 的有序遍历;DualHashBidiMap 用双倍内存两张表换双向 O(1),只比 HashMap 慢四成;PatriciaTrie 慢在逐字符比较与树节点,但它的价值在 prefixMap 前缀查询这一其他结构做不到的能力;HashBag 内部就是装饰过的 HashMap,计数开销极低。微基准只测单线程顺序访问,结论用于量级判断而非精确排名。

六、JVM 运行参数建议

本库无常驻服务,JVM 参数集中在两处:编译期编码与运行期堆。编译含中文的源码必须显式给编码(无 LANG 环境下 javac 默认按 US-ASCII 读源文件,实测报错「error: unmappable character (0xE5) for encoding US-ASCII」),解法:

javac -encoding UTF-8 -cp $J BenchDemo.java

运行期堆参数按小服务规模固定 256-512MB,并用 PrintFlagsFinal 验证参数真实生效:

java -Xms256m -Xmx512m -XX:+PrintFlagsFinal -version | grep -E "InitialHeapSize|MaxHeapSize"

实测回显:

size_t InitialHeapSize                          = 268435456                                 {product} {command line}
size_t MaxHeapSize                              = 536870912                                 {product} {command line}

268435456=256MB、536870912=512MB,行尾 {command line} 标记说明值来自命令行而非默认推断,此即「改了确实生效」的证据。第五节全部基准均在此堆配置下运行。

七、安全相关参数与 3.x 漏洞史说明

参数之外的「安全配置」是版本与用法选择。3.x 与 4.x 的本质差异可用同一工具对比出来——对 InvokerTransformer 做 javap(3.2.2 jar 取自 archive.apache.org 归档):

javap -cp commons-collections-3.2.2/commons-collections-3.2.2.jar org.apache.commons.collections.functors.InvokerTransformer | head -2
javap -cp commons-collections4-4.6.0/commons-collections4-4.6.0.jar org.apache.commons.collections4.functors.InvokerTransformer | head -2

实测回显:

public class org.apache.commons.collections.functors.InvokerTransformer implements org.apache.commons.collections.Transformer,java.io.Serializable {
public class org.apache.commons.collections4.functors.InvokerTransformer<T, R> implements org.apache.commons.collections4.Transformer<T, R> {

解读:3.2.2 的 InvokerTransformer 实现 java.io.Serializable,配合 Java 原生反序列化入口曾被 ysoserial 的 CommonsCollections 系列利用链(CC1/CC3/CC5/CC6/CC7 等)构造任意命令执行,是 Java 反序列化漏洞史上最著名的组件案例;4.1 起官方删除了 functors 类的 Serializable(上面 4.6.0 的 javap 输出证实无 Serializable),利用链在该环节断掉。落地三条纪律:新项目只用 4.x 并保持升级;任何版本都不要对不可信来源的数据流调用 ObjectInputStream 反序列化;安全扫描报 commons-collections 3.x 命中即按包名迁移方案改造(org.apache.commons.collections 改 org.apache.commons.collections4,Maven 坐标同步更换)。

八、参考链接

  1. 官方 Javadoc(各构造器参数含义):https://commons.apache.org/proper/commons-collections/javadocs/api-4.6.0/
  2. 4.6.0 RELEASE-NOTES(MultiSet 补齐与 Bag 迁移说明):https://downloads.apache.org/commons/collections/RELEASE-NOTES.txt
  3. 官方发行包目录:https://downloads.apache.org/commons/collections/
  4. Maven 中央仓库版本列表:https://mvnrepository.com/artifact/org.apache.commons/commons-collections4
  5. 历史版本归档(3.2.2 下载依据):https://archive.apache.org/dist/commons/collections/binaries/