Apache Commons Collections 配置参数详解
详解 commons-collections4 4.6.0 的 Maven 依赖参数、多值 Map 与分段锁 Map 构造参数、不可变装饰器、各 Map 与 Bag 实现复杂度对比、JVM 参数与 3.x 反序列化安全边界,均配实测回显。
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)实测通过。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS(x86_64)
- JDK:OpenJDK 17.0.20.1(javac 17.0.20.1)
- Maven:3.9.16(/opt/apache-maven-3.9.16)
- 目标库:commons-collections4-4.6.0.jar(bin 包解压自官方发行目录)
- 工作目录:/root/collections-lab(示例代码目录 examples)
- 获取 jar:https://downloads.apache.org/commons/collections/binaries/commons-collections4-4.6.0-bin.tar.gz(sha512 校验值 b54c6173 开头、d3aa5 结尾,实测校验 OK)
本篇 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 工厂方法 + expectedSizes Maps.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 实现复杂度与实测对比
选实现就是选复杂度与功能约束的组合:
| 实现 | put | get/containsKey | 有序性 | 特殊能力/约束 |
|---|---|---|---|---|
| HashMap(JDK) | O(1) 均摊 | O(1) | 无 | 基准实现 |
| LinkedMap | O(1) | O(1) | 插入序 | 额外双向链表,支持 firstKey/nextKey 有序遍历 |
| DualHashBidiMap | O(1) | O(1) | 无 | 正反两张 HashMap,双向查找,值不重复 |
| DualTreeBidiMap | O(log n) | O(log n) | 键序 | 正反两张 TreeMap,inverseBidiMap 同步排序 |
| PatriciaTrie | O(k) k=键长 | O(k) | 键字典序 | 前缀检索 prefixMap,键固定 String |
| HashBag/HashMultiSet | O(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 坐标同步更换)。
八、参考链接
- 官方 Javadoc(各构造器参数含义):https://commons.apache.org/proper/commons-collections/javadocs/api-4.6.0/
- 4.6.0 RELEASE-NOTES(MultiSet 补齐与 Bag 迁移说明):https://downloads.apache.org/commons/collections/RELEASE-NOTES.txt
- 官方发行包目录:https://downloads.apache.org/commons/collections/
- Maven 中央仓库版本列表:https://mvnrepository.com/artifact/org.apache.commons/commons-collections4
- 历史版本归档(3.2.2 下载依据):https://archive.apache.org/dist/commons/collections/binaries/