ArrayList 的 size() 时间复杂度为 O(1),因其直接返回内部维护的 size 字段,无需遍历或依赖底层数组长度,故耗时恒定。

ArrayList 的 size() 是 O(1)
因为 ArrayList 内部维护了一个 size 字段,每次增删都会更新它,调用 size() 只是直接返回这个整数。不需要遍历、不依赖底层数组长度(elementData.length),所以无论列表有多大,耗时恒定。
常见误用场景:有人在循环里反复调用 list.size() 判断边界,比如写成 for (int i = 0; i —— 这没问题,JVM 通常能内联优化,但语义上仍是安全的 O(1) 调用。
LinkedList 的 size() 也是 O(1)
从 Java 8 开始,LinkedList 已不再需要遍历链表计数。它同样持有独立的 size 成员变量,增删节点时同步更新。所以和 ArrayList 一样,size() 是常数时间操作。
注意:早期 JDK(如 Java 6)中部分实现可能未缓存 size,但所有现代主流 JDK(8+)均已保证 O(1)。如果你看到性能分析工具显示 LinkedList.size() 很慢,大概率是被误标,或实际调用的是其他方法(比如 get(i))。
立即学习“Java免费学习笔记(深入)”;
HashMap / HashSet 的 size() 同样是 O(1)
它们都继承自 AbstractMap 或 AbstractCollection,内部有明确的 size 计数器。插入、删除、清空时都会维护该值,不会去遍历桶数组或链表/红黑树结构。
例外情况:ConcurrentHashMap 的 size() 在高并发下可能触发估算逻辑(尤其在 Java 7 中),但 Java 8+ 已改用更精确的分段计数(baseCount + sum of counter cells),仍是 O(1) 平均复杂度;不过极端争用下可能有微小波动,一般业务无需担忧。
哪些集合的 size() 不是 O(1)?
标准库中几乎全部主流集合都实现了 O(1) 的 size()。真正要注意的是你自己写的包装类或流式集合:
- 基于
Stream构造的懒序列(如StreamSupport.stream(...)包装的自定义Spliterator),若未重写estimateSize()或getExactSizeIfKnown(),调用count()就是 O(n) - 某些第三方库中的惰性求值集合(如 jOOλ 的
Seq),size()可能触发全量计算 -
Collections.unmodifiableCollection(c)等装饰器会委托给底层集合,只要底层是 O(1),它就是 O(1);但如果底层是手写低效实现,那就另当别论
真实项目里最常踩的坑不是集合本身,而是把 size() 和 isEmpty() 混用——后者在多数集合中也 O(1),但语义更清晰、可读性更好,且部分集合(如 TreeSet)对 isEmpty() 有额外优化。











