Java集合框架常用类
JDK8的集合框架主要分为两大体系——Collection接口和Map接口。其中Collection接口是所有单列集合的父接口,包含List、Set两个主要子接口;而Map接口是双列集合的父接口,存储的是键值对(key-value)。两者的核心区别的是:Collection存储单个元素,Map存储映射关系,这一点一定要先分清。
一、Collection接口:单列集合的“顶层规范”
Collection接口定义了单列集合的通用操作,比如添加元素(add)、删除元素(remove)、判断是否包含(contains)、遍历(iterator)等,它本身是一个抽象接口,不能直接实例化,必须通过它的子接口(List、Set)的实现类来使用。
这里重点说一下JDK8给Collection新增的一个实用特性:forEach方法,结合Lambda表达式,能极大简化集合的遍历操作,比如:
Collection<String> collection = new ArrayList<>();
collection.add("Java");
collection.add("JDK8");
// JDK8新增forEach遍历
collection.forEach(str -> System.out.println(str));接下来,咱们分别详解List和Set这两个核心子接口,以及它们的常用实现类。
1. List接口:有序、可重复的单列集合
List接口的核心特点:有序(元素插入顺序与遍历顺序一致)、可重复(允许存储相同元素)、支持索引访问(就像数组一样,可以通过下标get、set元素)。实现类有3个:ArrayList、LinkedList、Vector,其中Vector在JDK8中已基本被淘汰,重点关注前两个。
(1)ArrayList:基于数组实现,查询快、增删慢
ArrayList是List接口最常用的实现类,底层基于动态数组(数组容量可自动扩容)实现,核心优势是“查询效率高”——因为数组支持通过索引直接访问元素,时间复杂度是O(1);但增删效率较低,尤其是在集合中间位置增删元素时,需要移动后续所有元素,时间复杂度是O(n)。
JDK8中ArrayList的关键优化:
初始容量默认是10,扩容机制为“每次扩容至原来的1.5倍”(JDK7及之前是1.5倍,JDK8保持一致,但优化了扩容时的数组复制效率)。
支持Lambda遍历、Stream流操作(后续会结合Map一起说)。
新增了removeIf方法,可根据条件批量删除元素,比如删除所有长度小于3的字符串:
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("abc");
list.add("ab");
// 批量删除长度<3的元素
list.removeIf(str -> str.length() < 3);适用场景:查询操作频繁、增删操作较少的场景,比如展示列表数据、存储固定顺序的数据集。
(2)LinkedList:基于双向链表实现,增删快、查询慢
LinkedList底层基于双向链表实现,每个元素都包含前驱节点和后继节点的引用,核心优势是“增删效率高”——在链表中间位置增删元素时,只需修改节点的引用,无需移动元素,时间复杂度是O(1);但查询效率低,需要从链表头或尾遍历到目标元素,时间复杂度是O(n)。
补充:LinkedList还实现了Deque接口,支持双端队列操作(比如addFirst、addLast、removeFirst、removeLast),可以当作队列、栈来使用,灵活性比ArrayList高。
适用场景:增删操作频繁、查询操作较少的场景,比如消息队列、栈操作、频繁插入删除的列表。
(3)Vector:线程安全,但效率低(不推荐使用)
Vector和ArrayList类似,底层也是基于数组实现,但它的所有方法都加了synchronized锁,是线程安全的。但正因为加了锁,它的执行效率比ArrayList低很多,在JDK8中,更推荐使用“ArrayList + 手动加锁”或“CopyOnWriteArrayList”(线程安全的List实现),而非Vector。
2. Set接口:无序、不可重复的单列集合
Set接口的核心特点:无序(元素插入顺序与遍历顺序不一定一致)、不可重复(不允许存储相同元素,底层通过equals和hashCode方法判断元素是否相同)、不支持索引访问(不能通过下标get元素)。常用实现类有3个:HashSet、LinkedHashSet、TreeSet。
(1)HashSet:基于哈希表实现,效率最高,无序
HashSet是Set接口最常用的实现类,底层基于HashMap(后面会讲)实现,核心优势是“增删改查效率都很高”,时间复杂度接近O(1)。它的“无序”是指元素不会按照插入顺序存储,而是根据元素的hashCode值分配存储位置。
关键注意点:
存储的元素必须重写equals和hashCode方法,否则无法保证“不可重复”——因为HashSet判断元素是否相同,先通过hashCode判断哈希值,再通过equals判断内容。
允许存储null值,但只能存储一个null(因为不可重复)。
适用场景:不需要保证元素顺序,只需要去重、高效操作的场景,比如存储用户ID、标签等。
(2)LinkedHashSet:有序、不可重复,效率略低于HashSet
LinkedHashSet是HashSet的子类,底层基于“哈希表 + 双向链表”实现,它继承了HashSet的高效性,同时新增了“有序”特性——这里的有序是指“元素插入顺序与遍历顺序一致”,相当于“去重的ArrayList”。
适用场景:需要去重,且需要保证元素插入顺序的场景,比如存储历史记录、有序去重的列表。
(3)TreeSet:有序、不可重复,支持排序
TreeSet底层基于红黑树实现,核心特点是“有序”——但这里的有序不是插入顺序,而是“自然排序”(比如字符串按字典序、数字按升序),也可以通过Comparator接口自定义排序规则。它的增删改查效率是O(log n),比HashSet略低。
示例:自定义排序(按字符串长度降序)
TreeSet<String> treeSet = new TreeSet<>((s1, s2) -> s2.length() - s1.length());
treeSet.add("apple");
treeSet.add("banana");
treeSet.add("orange");
// 遍历结果:banana、orange、apple(按长度降序)适用场景:需要排序、去重的场景,比如排行榜、有序数据集。
二、Map接口:双列集合的“映射容器”
Map接口和Collection接口是完全独立的体系,它存储的是“键值对(key-value)”映射关系,核心特点:key不可重复(唯一)、value可重复,一个key对应一个value,通过key可以快速获取value。JDK8中Map的常用实现类有3个:HashMap、LinkedHashMap、TreeMap,还有一个线程安全的ConcurrentHashMap(替代了JDK7的Hashtable)。
1. HashMap:基于哈希表实现,效率最高,无序
HashMap是Map接口最常用的实现类,底层基于“数组 + 链表/红黑树”实现(JDK8优化点:当链表长度超过8时,会自动转为红黑树,提升查询效率),核心优势是“增删改查效率高”,时间复杂度接近O(1)。
JDK8中HashMap的关键优化(重点):
底层结构:数组(哈希桶) + 链表(解决哈希冲突) + 红黑树(链表过长时优化)。
初始容量默认16,扩容机制为“每次扩容至原来的2倍”,负载因子默认0.75(当元素数量达到容量×负载因子时,触发扩容)。
支持Lambda遍历,新增了forEach、compute、merge等实用方法,简化操作:
HashMap<String, Integer> map = new HashMap<>();
map.put("Java", 8);
map.put("Python", 3);
// Lambda遍历
map.forEach((key, value) -> System.out.println(key + ":" + value));
// compute方法:修改指定key的value
map.compute("Java", (k, v) -> v + 2); // Java的值变为10
// merge方法:合并key,不存在则新增,存在则合并
map.merge("Python", 2, Integer::sum); // Python的值变为5关键注意点:key必须重写equals和hashCode方法,否则无法保证key的唯一性;允许key为null、value为null(key只能有一个null)。
适用场景:不需要保证key的顺序,只需要高效的键值对操作,比如存储用户信息(key为用户ID,value为用户对象)、配置参数等。
2. LinkedHashMap:有序、高效,保留插入顺序
LinkedHashMap是HashMap的子类,底层基于“哈希表 + 双向链表”实现,继承了HashMap的高效性,同时新增了“有序”特性——保留key的插入顺序(或访问顺序,可通过构造方法设置),相当于“有序的HashMap”。
LinkedHashMap中多了一个双向链表,他的核心用处,主要体现在3个方面,与哈希表分工协作、互不影响:
1. 维护有序性:这是最核心用途。双向链表专门记录键值对的“插入顺序”或“访问顺序”(由accessOrder控制),比如accessOrder=true时,访问某个key后,该键值对会被移动到链表尾部,确保遍历时有固定顺序,而哈希表仅负责快速查询。
2. 高效遍历:遍历LinkedHashMap时,会直接沿着双向链表的顺序(从头到尾或从尾到头)执行,无需遍历哈希表的所有桶位,避免哈希冲突带来的遍历效率损耗,同时支持正序、倒序两种遍历方式。
3. 支持高效的顺序相关操作:比如实现LRU缓存时,当缓存满了,可直接删除双向链表头部的“最久未访问”元素(仅修改链表节点引用,时间复杂度O(1));新增、访问元素时,也能快速调整链表节点位置,无需移动大量数据。
关键提醒:双向链表仅负责“顺序维护和遍历”,不影响查询效率——get(key)时依然通过哈希表定位,和元素在链表中的位置无关,保证查询效率接近O(1),不会因为链表顺序而变慢。
示例:设置按访问顺序排序(最近访问的key排在最后)
// accessOrder=true:按访问顺序排序,默认false(按插入顺序)
LinkedHashMap<String, Integer> linkedMap = new LinkedHashMap<>(16, 0.75f, true);
linkedMap.put("a", 1);
linkedMap.put("b", 2);
linkedMap.put("c", 3);
linkedMap.get("a"); // 访问a
// 遍历结果:b、c、a(a最近访问,排在最后)适用场景:需要保留键值对插入顺序或访问顺序的场景,比如缓存、LRU缓存(最近最少使用)的简单实现。
3. TreeMap:有序、支持排序,基于红黑树
TreeMap底层基于红黑树实现,核心特点是“有序”——key按自然排序或自定义排序(通过Comparator接口),和TreeSet类似,它的增删改查效率是O(log n),比HashMap略低。
关键注意点:key必须实现Comparable接口(自然排序),或在构造TreeMap时指定Comparator,否则会抛出ClassCastException异常;不允许key为null(value可以为null)。
适用场景:需要对key进行排序的键值对场景,比如按日期排序的日志映射、按ID排序的用户映射。
4. ConcurrentHashMap:线程安全,高效(替代Hashtable)
Hashtable是JDK早期的线程安全Map实现,所有方法都加了synchronized锁,效率很低;而ConcurrentHashMap是JDK8中推荐的线程安全Map,底层采用“分段锁”(JDK8优化为CAS + synchronized),只对当前操作的哈希桶加锁,不会锁住整个Map,效率比Hashtable高很多,同时保证线程安全。
适用场景:多线程环境下的键值对操作,比如多线程统计数据、并发缓存等。
三、总结:List、Set、Map及实现类如何选择
为了方便大家快速选择,整理了核心对比表,一目了然: