快速结论(先看这一段)
- 想要随机访问/数组式存储:
ArrayList(默认选择)。 - 想做队列/栈(高性能):
ArrayDeque(优于LinkedList作队列/栈)。 - 想做链式插入/删除(特别是中间频繁操作):
LinkedList(但实际很少是最佳选择)。 - 想做优先级操作(Top-K、Dijkstra):
PriorityQueue(堆)。 - 想做键值查找:
HashMap(常用)、TreeMap(需排序)、LinkedHashMap(按插入/访问顺序,方便实现 LRU)。 - 并发场景用
ConcurrentHashMap、CopyOnWriteArrayList、阻塞队列等。
面试速查表(简短)
- 随机访问快 →
ArrayList - 栈 / 队列 →
ArrayDeque(非并发) - 双端队列需并发 →
LinkedBlockingDeque或并发队列实现 - 优先级 / Top-K →
PriorityQueue(堆) - 无序键值 →
HashMap(预设容量以减少扩容) - 保持插入/访问顺序 →
LinkedHashMap(LRU) - 有序键 / 范围查询 →
TreeMap(红黑树) - 并发 Map →
ConcurrentHashMap(非阻塞弱一致迭代) - 读多写少的线程安全列表 →
CopyOnWriteArrayList
ArrayList(动态数组)
是什么 / 实现
- 基于动态数组(
Object[] elementData)实现。默认初始容量 10(空构造返回空数组,在首次add时扩容到 10)。扩容策略:newCapacity = oldCapacity + (oldCapacity >> 1),即约1.5x摆动增长(摊销 O(1) 插入)。
常用操作复杂度
get(i):O(1)add(e)(尾插,摊销):O(1)add(index, e)、remove(index):O(n)(需要移动元素)contains、indexOf:O(n)- 迭代(顺序访问):极快(连续内存,CPU 缓存友好)
重要内部细节 & 易错点
- 扩容会
System.arraycopy,在大数组场景下可能触发 GC/复制开销。可以用ensureCapacity预分配减少扩容次数。 - 使用自定义对象作为元素时,不要忘了
equals/hashCode的实现会影响contains等操作。 - 不是线程安全的;并发修改时迭代会抛
ConcurrentModificationException(fail-fast)。 trimToSize()可以将内部数组裁剪到当前 size,节省内存(但可能影响后续扩容性能)。
典型场景 & 优点
- 随机访问频繁、尾部插入多、内存/缓存效率重要 →
ArrayList最优。
示例
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
int v = list.get(1); // O(1)
list.add(1, 99); // 在中间插入,O(n)
实战建议
- 若知道元素个数,
new ArrayList<>(expectedSize)预设容量。 - 需要线程安全时用
Collections.synchronizedList(简单)或CopyOnWriteArrayList(读多写少)。 - 面试问点:扩容因子、为什么 get 快(连续内存,缓存命中)。
LinkedList(双向链表,同时实现 List 和 Deque)
是什么 / 实现
- 双向链表,节点保存
item, prev, next。实现了List、Deque接口,因此既可当链表也可当队列/栈使用。
常用操作复杂度
addFirst/addLast、removeFirst/removeLast:O(1)get(index)、add(index, e):O(n)(定位节点需要从头/尾遍历)contains:O(n)
重要细节 & 易错点
- 节点对象开销较大(每个节点有 3 引用),内存占用比
ArrayList高。 Iterator的remove()在链表中通常是 O(1)(因为 iterator 保存当前节点引用)。- 作为队列/双端队列功能完备,但通常
ArrayDeque更快(内存更少、局部性更好)。 - 不建议频繁按索引访问或在随机位置插入大量元素(除非需要通过迭代器在当前位置插入/删除)。
典型场景
- 需要频繁在两端插入/删除,或需要在迭代过程中基于 iterator 做
remove/add时使用。
示例
LinkedList<Integer> l = new LinkedList<>();
l.addFirst(1);
l.addLast(2);
int head = l.removeFirst();
for (Iterator<Integer> it = l.iterator(); it.hasNext();) {
if (it.next() == 2) it.remove(); // 安全且 O(1)
}
实战建议
- 大多数“队列/栈”场景优先考虑
ArrayDeque。 - 需要保存插入顺序并频繁从中间用 iterator 修改时,可以考虑
LinkedList。
ArrayDeque(强烈推荐用于栈/队列)
是什么 / 实现
- 基于循环数组(环形缓冲区)实现的双端队列。头尾索引移动而不是整体移动元素。不允许
null元素(null用作返回值标识)。
常用操作复杂度
offer,poll,peek,push,pop:O(1) 摊销- 扩容(当满)会进行数组复制(通常以 2 倍扩容或类似策略),但很少发生
重要细节 & 易错点
- 不支持
null(add(null)抛NullPointerException)。 - 相较于
LinkedList更高效(更低内存、局部性更好)。 - 作为栈请用
push/pop;不要用老旧的Stack(Stack是同步的且性能差)。
典型场景
- BFS 队列、DFS 非递归栈、需要双端操作的场景。
示例
ArrayDeque<Integer> dq = new ArrayDeque<>();
dq.offer(1); // 队尾入队
int x = dq.poll(); // 队头出队
dq.push(5); // 当栈使用
int top = dq.pop();
实战建议
- 一般把
ArrayDeque作为默认的队列/栈实现。 - 非线程安全,若并发需要阻塞队列或并发队列(见后面)。
PriorityQueue(堆 —— 优先队列)
是什么 / 实现
- 基于数组二叉堆实现(最小堆,默认按自然顺序)。可以传入
Comparator指定顺序。底层为Object[],插入/删除按堆性质上浮/下沉。
常用操作复杂度
offer/add:O(log n)poll:O(log n)peek:O(1)remove(Object)、contains(Object):O(n)(需线性查找)
重要细节 & 易错点
- 迭代器遍历不是有序的(如果你需要按优先级访问,应不断
poll())。 - 不允许
null。 Comparator/元素的Comparable必须一致(否则会抛异常)。PriorityQueue不是线程安全的。
典型场景
- Top-K(保留 K 个最大/最小元素)、Dijkstra、事件调度(按时间排序)、合并 K 个已排序流等。
示例(Top-K)
int k = 3;
PriorityQueue<Integer> pq = new PriorityQueue<>(); // 小顶堆
for (int v : nums) {
pq.offer(v);
if (pq.size() > k) pq.poll(); // 保留 k 个最大
}
面试/实战提示
- 要实现大顶堆:
new PriorityQueue<>(Comparator.reverseOrder())。 - 想在 O(n) 时间内取 top-k(k 小于 n)可使用小顶堆;对全部排序则 O(n log n)。
HashMap / HashSet(哈希表)
是什么 / 实现
HashMap基于哈希表(Java 8 起为数组 + 链表/红黑树混合结构实现)。键通过hashCode()映射到桶(bucket),在桶内查找equals()。当单个桶中链表长度超过阈值(默认 8),链表会转为红黑树以避免 O(n) 退化。HashSet底层使用HashMap的 key 存储值。
常用操作复杂度
get/put/remove:平均 O(1),最坏 O(log n)(链表转树后)或最坏 O(n)(早期极端哈希冲突)。- 迭代成本 O(n)。
重要细节 & 易错点
- hashCode / equals:自定义类型作 key 时必须正确覆写
hashCode()与equals(),否则查找失败。 - 初始容量和 loadFactor(默认 0.75)决定何时扩容:
threshold = capacity * loadFactor。扩容会重新分配数组并 rehash,代价大。可通过new HashMap<>(expectedCapacity)预设。 nullkey 是允许的(仅一个nullkey),nullvalue 也允许。- 并发修改(多线程)不是线程安全的;并发使用请用
ConcurrentHashMap(注意ConcurrentHashMap不允许nullkey/value)。 - Java 8 以后
putIfAbsent/computeIfAbsent/merge等原子方法很适合并发逻辑(但本身不是线程安全,需要注意)。
常见 API
map.putIfAbsent(k, v);
map.computeIfAbsent(k, key -> new ArrayList<>());
map.merge(k, 1, Integer::sum);
int val = map.getOrDefault(k, 0);
示例(计数)
Map<Integer, Integer> freq = new HashMap<>();
for (int x : nums) {
freq.put(x, freq.getOrDefault(x, 0) + 1);
}
实战建议
- 预估元素数量并用
new HashMap<>(capacity)初始化来减少扩容。 - 面试常问:负载因子、扩容成本、null 键行为、hashCode/equals 的要求、为什么 HashMap.get 是 O(1)。
LinkedHashMap(保持插入或访问顺序)
是什么 / 实现
LinkedHashMap在HashMap基础上维护一条双向链表来记录条目的插入顺序(或最近访问顺序,当accessOrder=true时)。- 典型用途:实现 LRU 缓存(通过重写
removeEldestEntry)。
常见用法(LRU)
class LRUCache<K,V> extends LinkedHashMap<K,V> {
private final int capacity;
LRUCache(int cap) {
super(cap, 0.75f, true); // accessOrder=true
this.capacity = cap;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return size() > capacity;
}
}
重要细节
accessOrder=true时每次get会把 entry 移到链表尾(最近使用)。- 插入顺序和访问顺序可选。
- 仍然继承自
HashMap的复杂度特性(平均 O(1))。
实战建议
- 需要有序迭代(插入/访问顺序)或实现简单缓存用
LinkedHashMap非常方便。
TreeMap / TreeSet(基于红黑树的有序结构)
是什么 / 实现
- 基于红黑树(自平衡二叉搜索树)实现,键按自然顺序或提供的
Comparator排序。TreeMap实现NavigableMap,提供诸如floorKey,ceilingKey,subMap等范围查询方法。
常用操作复杂度
get,put,remove:O(log n)
重要细节 & 易错点
- 不能使用
null键(自然顺序比较器会 NPE);若使用自定义 comparator,需要保证比较器一致且稳定。 - 适用于需要按顺序检索或范围查询的场景(比如:按时间窗口查找、排行榜区间查询)。
示例
TreeMap<Integer, String> tm = new TreeMap<>();
tm.put(5, "a");
Map.Entry<Integer, String> e = tm.floorEntry(4); // <= 4 的最大键
SortedMap<Integer, String> sub = tm.subMap(1, true, 6, false);
实战建议
- 当你需要排序的键或需要前驱/后继查找时用
TreeMap。 - 面试常问:红黑树的性质、为什么操作是 O(log n)。
并发/线程安全集合(常见选择)
ConcurrentHashMap
- Java 8 以后实现基于 CAS + 链表/树 + synchronized(在部分情况下)来保证并发安全。提供高并发读写性能。不允许
nullkey/value。 - 迭代器是弱一致(weakly consistent):可见一部分后续修改,但不会抛
ConcurrentModificationException。
CopyOnWriteArrayList
- 对写操作做“写时复制”,适合读多写少场景(如事件监听器集合)。迭代器是快照,读不会被写影响。写操作代价昂贵(复制整个数组)。
阻塞队列
ArrayBlockingQueue(有界、基于数组,适合生产者消费者)LinkedBlockingQueue(可选界限,节点基于链表)SynchronousQueue(无缓冲区,直接交接)DelayQueue、PriorityBlockingQueue等特殊队列
非阻塞队列
ConcurrentLinkedQueue:基于无锁算法的线程安全队列(高并发下优选)。
实战建议
- 并发 Map 首选
ConcurrentHashMap;并发队列选LinkedBlockingQueue或ConcurrentLinkedQueue依据需求(阻塞 vs 非阻塞)。
其他零碎但重要的点(面试常问)
- Stack / Vector:
Stack继承Vector(同步,老旧),建议用ArrayDeque代替Stack。 - Collections.unmodifiableXXX:返回不可变视图(非深拷贝),底层集合变更会影响视图。
- Arrays vs Collections:
Arrays.asList返回固定大小的视图(不支持 add/remove),经常踩坑。 - Iterator 的 fail-fast 机制:大多数集合在结构性修改时检测并抛
ConcurrentModificationException,并发集合或CopyOnWriteArrayList不抛。 - 内存/性能:链表节点对象开销大(多指针),数组结构更节省、缓存友好。大数据量下考虑 primitive 专用集合(fastutil/Trove)提升性能。
- null 在不同集合中的处理:
HashMap/ArrayList/LinkedList可含 null;ArrayDeque/PriorityQueue/ConcurrentHashMap不允许 null。