Java集合框架基础知识点全面解析
目录
- 🚀前言
- 🌟定义与体系结构
- 🐧Collection
- 💯常用功能
- 💯遍历方式
- 💯List集合
- 💯Set集合
- 🐍Map
- 💯常用方法
- 💯遍历方式
- 💯实现类详解与底层原理
🚀前言
大家好!我是 EnigmaCoder。
本文介绍Java中的集合框架部分,从单列集合(Collection)和双列集合(Map)两大类进行解析。
🌟定义与体系结构
-
在
Java
中,集合(Collection
) 是一种用于存储、管理多个数据元素的数据结构,它可以动态地存储一组相关的对象,解决了数组长度固定、类型单一的局限性。Java
集合框架(Java Collection Framework, JCF
)提供了一套完整的集合接口、实现类和工具类,使得数据操作更加便捷和高效。 -
我们可以将集合大体分为两类,分别是单列集合(
Collection
)和双列集合(Map
)。
🐧Collection
Collection
代表单列集合,每个元素(数据)只包含一个值。
注意:虚边框的是接口,实边框的是实现类。
特点:
List
系列集合:添加的元素是有序、可重复、有索引。ArrayList、LinkedList
:有序、可重复、有索引。
Set
系列集合:添加的元素的无序、不重复、无索引。HashSet
:无序、不重复、无索引。LinkedHashSet
:有序、不重复、无索引。TreeSet
:按图大小默认升序排序、不重复、无索引。
💯常用功能
Collection
是单列集合的祖宗,它规定的方法(功能)是全部单列集合都会继承的。
方法名 | 说明 |
---|---|
public boolean add(E e) | 把给定的对象添加到当前集合中 |
public void clear( ) | 清空集合中所有的元素 |
public boolean remove(E e) | 把给定的对象在当前集合中删除 |
public boolean contains(Object obj) | 判断当前集合中是否包含给定的对象 |
public boolean isEmpty() | 判断当前集合是否为空 |
public int size() | 返回集合中元素的个数 |
public Object[ ] toArray() | 把集合中的元素,存储到数组中 |
💯遍历方式
- 迭代器遍历
迭代器是用来遍历集合的专用方式(数组没有迭代器),在java
中迭代器的代表是Iterator
。
Collection
集合获取迭代器的方法
方法名称 | 说明 |
---|---|
Iterator< E > iterator() | 返回集合中的迭代器对象,该迭代器对象默认指向当前集合的第一个元素(即索引为0的元素 ) |
- Iterator迭代器中的常用方法
方法名称 | 说明 |
---|---|
boolean hasNext() | 询问当前位置是否有元素存在,存在返回true ,不存在返回false |
E next() | 获取当前位置的元素,并同时将迭代器对象指向下一个元素处 |
示例:
public class Test {public static void main(String[] args) {Collection<String> list = new ArrayList<String>();list.add("hello");list.add("world");list.add("java");list.add("C++");Iterator<String> it = list.iterator();while (it.hasNext()){String ele = it.next();System.out.println(ele);}}
}
- 增强
for
循环
- 增强
for
可以用来遍历集合或数组。 - 增强
for
遍历集合,本质就是迭代器遍历集合的简化写法。
格式:
for (元素的数据类型 变量名 : 数组或者集合){}
示例:
public class Test {public static void main(String[] args) {Collection<String> list = new ArrayList<String>();list.add("hello");list.add("world");list.add("java");list.add("C++");for(String s:list){System.out.println(s);}String [] arr = {"hello","world","java","C++"};for(String s:arr){System.out.println(s);}}
}
Lambda
表达式
- 得益于JDK 8开始的新技术
Lambda
表达式,提供了一种更简单、更直接的方式来遍历集合。
需要使用Collection
的如下方法来完成:
方法名称 | 说明 |
---|---|
default void forEach(Consumer<? super T> action) | 结合Lambda遍历集合 |
示例:
public class Test {public static void main(String[] args) {Collection<String> list = new ArrayList<String>();list.add("hello");list.add("world");list.add("java");list.add("C++");list.forEach(new Consumer<String>() {@Overridepublic void accept(String s) {System.out.println(s);}});list.forEach(s -> System.out.println(s));list.forEach(System.out::println);}
}
- 三种遍历的区别
前置知识: 遍历集合的同时又存在增删集合元素的行为时可能出现业务异常,这种现象被称之为并发修改异常问题。
解决这一问题的方案:
- 如果集合支持索引,可以使用for循环遍历,每删除数据后做
i--;
或者可以倒着遍历。
// 正向遍历删除
for(int i=0; i<list.size(); i++){if(条件){list.remove(i);i--;}
}// 倒序遍历删除(更优)
for(int i=list.size()-1; i>=0; i--){if(条件){list.remove(i);}
}
- 可以使用迭代器遍历,并用迭代器提供的删除方法删除数据(例如:
it.remove();
)。
Iterator<String> it = list.iterator();
while(it.hasNext()){String item = it.next();if(条件){it.remove(); // 使用迭代器的remove方法}
}
注意:增强
for
循环/Lambda
遍历均不能解决并发修改异常问题,因此它们只适合做数据的遍历,不适合同时做增删操作,这就是三种遍历的区别。
💯List集合
List
系列集合特点:有序、可重复、有索引。
ArrayList
:有序、可重复、有索引。LinkedList
:有序、可重复、有索引。
ArrayList
与LinkedList
特点相同,但底层实现不同,适合的场景不同。
List
集合继承了Collection
的功能,同时也拥有自己的特有功能,这些功能都与索引有关。
方法名称 | 说明 |
---|---|
void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
E get(int index) | 返回指定索引处的元素 |
import java.util.ArrayList;
import java.util.List;public class ListExample {public static void main(String[] args) {// 创建List集合(使用ArrayList实现)List<String> fruits = new ArrayList<>();// 继承自Collection的功能:添加元素fruits.add("Apple");fruits.add("Banana");fruits.add("Cherry");// List特有功能:通过索引插入元素fruits.add(1, "Orange"); // 在索引1插入// List特有功能:通过索引获取元素String fruitAtIndex2 = fruits.get(2);System.out.println("索引2的元素: " + fruitAtIndex2);// List特有功能:通过索引修改元素fruits.set(0, "Mango");System.out.println("修改后的List: " + fruits);// 继承自Collection的功能:遍历元素for (String fruit : fruits) {System.out.println(fruit);}// List特有功能:通过索引删除元素fruits.remove(3);System.out.println("删除后的List: " + fruits);}
}
-
ArrayList
底层是基于数组存储数据的。特点:
- 查询速度快(注意:是根据索引查询数据块):查询数据通过地址值和索引定位,查询任意数据耗时相同。
- 增删数据效率低:可能需要把后面很多的数据进行前移。
-
LinkedList
底层是基于双链表存储数据的。特点:
- 链表中的数据是一个一个独立的结点组成的,结点在内存中是不连续的,每个结点包含数据值和下一个结点的地址。
- 查询慢,无论查询哪个数据都要从头开始找。
- 链表增删相对快。
LinkedList
新增了很多首尾操作的特定方法:
方法名称 | 说明 |
---|---|
public void addFirst(E e) | 在该列表开头插入指定的元素 |
public void addLast(E e) | 将指定的元素追加到该列表的末尾 |
public E getFirst() | 返回此列表中的第一个元素 |
public E getLast() | 返回此列表中的最后一个元素 |
public E removeFirst() | 从此列表中删除并返回第一个元素 |
public E removeLast() | 从此列表中删除并返回最后一个元素 |
LinkedList
可以用来实现栈和队列。
(1)实现栈(LIFO结构)
栈是一种后进先出(Last-In-First-Out)的数据结构,LinkedList
可以通过以下方式模拟栈操作:
- 压栈(Push):使用
addFirst()
方法在链表头部插入元素LinkedList<Integer> stack = new LinkedList<>(); stack.addFirst(1); // 栈顶元素变为1 stack.addFirst(2); // 栈顶元素变为2
- 弹栈(Pop):使用
removeFirst()
方法移除并返回头部元素int top = stack.removeFirst(); // 返回2并移除
- 查看栈顶(Peek):使用
getFirst()
方法 - 典型应用场景:函数调用栈、浏览器后退功能、表达式求值等
(2)实现队列(FIFO结构)
队列是一种先进先出(First-In-First-Out)的数据结构,可以通过以下方式实现:
- 入队(Enqueue):使用
addLast()
方法在尾部添加元素LinkedList<String> queue = new LinkedList<>(); queue.addLast("A"); // 队首 queue.addLast("B");
- 出队(Dequeue):使用
removeFirst()
方法从头部移除元素String first = queue.removeFirst(); // 返回"A"
- 查看队首元素:使用
getFirst()
方法 - 典型应用场景:消息队列、多线程任务调度、BFS算法实现等
(3)实现双端队列(Deque)
LinkedList
本身就实现了Deque
接口,因此可以直接作为双端队列使用:
Deque<Integer> deque = new LinkedList<>();
deque.addFirst(1); // 前端插入
deque.addLast(2); // 后端插入
int front = deque.removeFirst(); // 移除前端元素
int rear = deque.removeLast(); // 移除后端元素
(4) 性能考虑
- 相比
ArrayList
,LinkedList
在头部插入/删除操作上有O(1)的时间复杂度 - 但随机访问需要O(n)时间,因为需要遍历链表
- 在内存使用上,每个元素需要额外的空间存储前后节点引用
(5) 线程安全注意事项
标准LinkedList
实现不是线程安全的,多线程环境下建议使用:
List<String> syncList = Collections.synchronizedList(new LinkedList<>());
通过这些实现方式,LinkedList
可以灵活地满足不同场景下栈和队列的需求,特别是当需要频繁在数据结构两端进行操作时,其性能优势尤为明显。
💯Set集合
Set
集合特点:无序:添加数据的顺序和获取出的数据顺序不一致;不重复;无索引;
- HashSet:无序、不重复、无索引。(底层原理:数组+链表+红黑树)
- LinkedHashSet:有序、不重复、无索引。(底层原理:数组+双链表+红黑树)
- TreeSet:排序、不重复、无索引。(底层原理:红黑树)
注意:
Set
要用到的常用的方法,基本上就是Collection
提供的,自己几乎没有额外新增一些功能。JDK8开始,当链表长度超过8,且数组长度>=64时,HashSet
自动将链表转为红黑树。
HashSet
集合元素的去重操作
-
去重原理
HashSet
的去重机制主要通过以下方式实现:- 当向
HashSet
添加元素时,会先计算元素的hashCode
值 - 根据
hashCode
值确定元素的存储位置 - 如果该位置已有元素,则调用
equals()
方法比较 - 如果
equals()
返回true
,则视为重复元素,不会被添加
- 当向
-
如果希望
Set
集合认为2个内容一样的对象是重复的,必须重写对象的hashCode()
和equals()
方法。(自定义对象去重复)
class Student {private String id;private String name;// 构造方法等其他代码@Overridepublic int hashCode() {//保证了不同的学生对象,如果内容一样返回的哈希值一定是一样的。return Objects.hash(id, name);}//只要两个对象的内容一样结果一定是true@Overridepublic boolean equals(Object obj) {// 实现比较逻辑}
}// 使用示例
Set<Student> studentSet = new HashSet<>();
studentSet.add(new Student("001", "王五"));
studentSet.add(new Student("001", "王五")); // 不会重复添加
TreeSet
底层是基于红黑树(Red-Black Tree)实现的有序集合,它能够保证元素始终处于排序状态。红黑树是一种自平衡的二叉查找树,通过特定的着色规则和旋转操作来维护树的平衡,确保插入、删除和查找操作的时间复杂度都是O(log n)。
注意:
- 对于数值类型:如
Integer
、Double
等,TreeSet
默认按照数值本身的大小进行升序排序。例如:[3, 1, 2]
会被排序为[1, 2, 3]
。 - 对于字符串类型:默认按照字符串的首字符的Unicode编号升序排序。例如:
["banana", "apple", "cherry"]
会被排序为["apple", "banana", "cherry"]
。 - 对于自定义类型:如
Student
对象,TreeSet
默认是无法直接排序的,因为集合无法确定如何比较两个自定义对象的大小。
解决自定义类型排序的两种方案:
- 实现
Comparable
接口:- 让自定义类实现
Comparable
接口,并重写compareTo()
方法,定义对象的自然排序规则。 - 示例:
class Student implements Comparable<Student> {private String name;private int age;@Overridepublic int compareTo(Student other) {return this.age - other.age; // 按照年龄升序排序} }
- 让自定义类实现
this
是比较者,other
是被比较者。如果要左边大于右边,就返回正整数;如果要左边小于右边,就返回负整数;如果要两边相等,就返回0。
- 使用
Comparator
比较器:- 在创建
TreeSet
时传入一个自定义的Comparator
比较器对象,定义临时的排序规则。 - 示例:
TreeSet<Student> students = new TreeSet<>(new Comparator<Student>() {@Overridepublic int compare(Student s1, Student s2) {return s1.getName().compareTo(s2.getName()); // 按照姓名升序排序} });
- 在创建
🐍Map
Map
代表双列集合,每个元素包含两个值(键值对)。
Map
集合也叫做“键值对集合”,格式:key1=value1,key2=value2,key3=value3,...
Map
集合的所有键是不允许重复的,但值可以重复,键和值是一一对应的,每一个键只能找到自己对应的值。
注意:虚边框的是接口,实边框的是实现类。
特点:
Map
系列集合的特点都是由键决定的,值只是一个附属品,值是不做要求的。
HashMap
(键决定特点):无序、不重复、无索引。(用的最多)LinkedHashMap
(键决定特点):有序、不重复、无索引。TreeMap
(键决定特点):按照大小默认升序排序、不重复、无索引。
💯常用方法
Map
是双列集合的祖宗,它的功能是全部双列集合都可以继承来使用的。
方法名称 | 说明 |
---|---|
public V put(K key,v value) | 添加元素 |
public int size() | 获取集合的大小 |
public void clear() | 清空集合 |
public boolean isEmpty() | 判断集合是否为空,为空返回true,反之返回false |
public V get(Object key) | 根据键获取对应值 |
public V remove(Object key) | 根据键删除整个元素 |
public boolean containsKey(Object key) | 判断是否包含某个键 |
public boolean containsValue(Object value) | 判断是否包含某个值 |
public Set< K> keySet() | 获取全部键的集合 |
public Collection< V> values() | 获取Map集合的全部值 |
💯遍历方式
- 键找值
先获取Map
集合全部的键,再通过遍历键来找值。
Set<String>Keys = map.keySet();for(String key:keys){Integer value = map.get(key);System.out.println(key + "=" + value);
}
- 键值对
把Map
集合转换成Set
集合,里面的元素类型都是键值对类型(Map.Entry<String,Integer>
)。
Set<Map.Entry<String,Integer>>entries = map.entrySet();for(Map.Entry<String,Integer>entry : entries){String key = entry.getKey();Integer value = entry.getValue();System.out.println(key + "=" + value);
}
- Lambda
需要运用Map
的如下方法:
方法名称 | 说明 |
---|---|
default void forEach(BiConsumer<? super K , ? super V> action) | 结合Labdma遍历Map集合 |
map.forEach((k,v)->{System.out.println(key + "--->" + value);
});
💯实现类详解与底层原理
HashMap
集合的底层原理深入解析
HashMap
是Java集合框架中最常用的Map实现类,其底层采用哈希表(数组+链表/红黑树)结构实现。关键特性包括:
- 初始容量默认为16,负载因子0.75
- 当链表长度超过8且数组长度≥64时,链表会转为红黑树
- 通过
hash(key.hashCode())
计算索引位置
HashSet
与HashMap
的关系更为明确:
// HashSet的源码实现
public class HashSet<E> implements Set<E> {private transient HashMap<E,Object> map;// 所有值都关联同一个虚拟对象private static final Object PRESENT = new Object();public HashSet() {map = new HashMap<>(); // 底层实际使用HashMap存储}public boolean add(E e) {return map.put(e, PRESENT) == null;}
}
典型应用场景:
- 快速查找(时间复杂度O(1))
- 去重存储
- 缓存实现
LinkedHashMap
的底层实现机制
LinkedHashMap
继承自HashMap
,在哈希表的基础上增加了双向链表维护插入顺序或访问顺序:
// 典型节点结构
static class Entry<K,V> extends HashMap.Node<K,V> {Entry<K,V> before, after; // 双向链表指针Entry(int hash, K key, V value, Node<K,V> next) {super(hash, key, value, next);}
}
有序性实现原理:
- 默认按插入顺序排序(
accessOrder=false
) - 设置
accessOrder=true
时按访问顺序排序(适合实现LRU缓存) - 通过重写
newNode()
方法创建带链表指针的节点
LinkedHashSet
的实现方式:
public LinkedHashSet() {super(16, 0.75f, true); // 调用HashSet的特定构造器// 底层实际使用LinkedHashMap
}
TreeMap
的红黑树实现详解
TreeMap
基于红黑树(自平衡二叉查找树)实现,主要特点:
- 元素默认按key的自然顺序排序
- 可通过
Comparator
自定义排序规则 - 基本操作时间复杂度为
O(log n)
红黑树五大特性:
- 节点是红色或黑色
- 根节点是黑色
- 所有叶子节点(
NIL
)是黑色 - 红色节点的子节点必须是黑色
- 从任一节点到其每个叶子的路径包含相同数目的黑色节点
TreeSet
的底层实现:
public TreeSet() {this(new TreeMap<E,Object>()); // 使用TreeMap作为存储
}
排序示例:
TreeMap<String, Integer> map = new TreeMap<>((a,b) -> b.length()-a.length());
map.put("apple", 1);
map.put("banana", 2);
// 将按字符串长度降序排列