集合
List
ArrayList
ArrayList
是基于动态数组(也叫做可变数组)实现的。其底层实现可以总结如下:
- 数组存储:
ArrayList
底层使用一个数组来存储元素。初始时,数组有一个默认容量,通常是10。 - 动态扩容:当添加新元素导致数组容量不足时,
ArrayList
会自动扩容。扩容的过程是创建一个新的数组,其容量通常是旧数组容量的1.5倍或2倍,然后将旧数组的元素复制到新数组中。 - 快速访问:由于底层是数组,
ArrayList
支持通过索引快速访问元素,时间复杂度为O(1)。 - 插入和删除:在中间插入或删除元素需要移动后续元素,时间复杂度为O(n),其中n是数组的大小。
LinkedList
LinkedList
是基于双向链表实现的。其底层实现可以总结如下:
- 双向链表结构:
LinkedList
使用节点(Node)来存储元素,每个节点包含一个数据域(data)和两个指针(前驱指针prev和后继指针next),分别指向前一个节点和后一个节点。 - 动态存储:与数组不同,链表不需要预先分配固定大小的内存。每添加一个元素,就创建一个新的节点并链接到链表中。
- 插入和删除:在链表的任意位置插入或删除元素的时间复杂度为O(1),因为只需要调整相邻节点的指针。然而,定位到某个位置的时间复杂度为O(n),因为需要从头开始遍历链表。
- 访问元素:访问链表中的元素通常需要从头或尾开始遍历,时间复杂度为O(n)。
对比
- 访问速度:
ArrayList
由于使用数组存储,可以通过索引快速访问元素,而LinkedList
需要遍历链表,访问速度较慢。 - 插入和删除速度:
LinkedList
在插入和删除操作方面更高效,尤其是在中间位置插入和删除,而ArrayList
则需要移动大量元素。 - 适用场景:
ArrayList
适用于频繁访问元素的场景,而LinkedList
适用于频繁插入和删除元素的场景。
Set
List
接口表示一个有序的集合,允许包含重复的元素。Set
接口表示一个无序不包含重复元素的集合。
HashSet
HashSet
是基于哈希表(HashMap)实现的集合类,它不允许存储重复元素,并且没有特定的顺序。
底层实现
- 哈希表(HashMap):
HashSet
底层使用HashMap
来存储元素。每个元素作为HashMap
的键,HashMap
的值是一个固定的常量对象。 - 哈希函数:
HashSet
使用元素的hashCode()
方法来计算元素的哈希值,并通过哈希值决定元素在表中的位置。 - 处理哈希冲突:
HashMap
采用链地址法(链表或红黑树)来处理哈希冲突。当哈希冲突较少时,冲突的元素存储在链表中;当冲突较多时,链表转换为红黑树以提高查询效率。 - 加载因子和扩容:当哈希表的元素数量超过阈值(容量 * 加载因子)时,哈希表会扩容并重新哈希已有的元素,新的容量通常是旧容量的两倍。
LinkedHashSet
LinkedHashSet
是 HashSet
的子类,除了哈希表,还维护了一个双向链表以保证元素的插入顺序。
底层实现
- 哈希表(HashMap):与
HashSet
类似,LinkedHashSet
也使用HashMap
来存储元素。 - 双向链表:
LinkedHashSet
通过在HashMap
的基础上,维护一个双向链表来记录元素的插入顺序。每个节点不仅包含哈希表中的键值对,还包含指向前一个和后一个节点的指针。 - 保持插入顺序:通过双向链表,
LinkedHashSet
可以保证遍历时按照元素的插入顺序进行。
HashSet和LinkedHashSet的工作原理
使用HashSet、LinkedHashSet存储自定义类的实例时,自定义类需要重写equals()和hashCode()方法。重写equals()确保Set集合的无序性,重写hashCode()用于生成对象的哈希码,哈希码确定对象在哈希表中的位置。如果两个对象通过equals()比较相等,则它们的hashCode也必须相等。
- 哈希表:
HashSet
和LinkedHashSet
使用哈希表来存储元素。每个元素通过hashCode
方法生成一个哈希码,根据哈希码决定元素的存储位置。 - 哈希冲突:如果两个对象的哈希码相同,则会发生哈希冲突。此时,
HashSet
和LinkedHashSet
会进一步使用equals
方法来比较对象,判断是否真的是相同对象(即使哈希码相同,不一定是相同对象)。 - 唯一性:通过
hashCode
和equals
方法,HashSet
和LinkedHashSet
能够保证集合中不包含重复的元素。
TreeSet
TreeSet
是基于红黑树(Red-Black Tree)实现的集合类,元素是有序的,默认按自然顺序排序,也可以使用指定的比较器(Comparator)进行排序。
底层实现
- 红黑树(TreeMap):
TreeSet
底层使用TreeMap
来存储元素。每个元素作为TreeMap
的键,TreeMap
的值是一个固定的常量对象。 - 排序:红黑树是一种自平衡的二叉搜索树,通过在插入和删除时进行旋转和重新着色来保持树的平衡,从而保证了
TreeSet
中元素的有序性。 - 搜索、插入、删除:在红黑树中,这些操作的时间复杂度均为 O(log n)。
注意
使用TreeSet存储自定义类的实例时,需要自定义类需要实现Comparable接口的compareTo方法,或者在定义TreeSet时,给TreeSet构造器传递一个实现了Comparator接口的实例。这样才能确保 TreeSet
能够正确地对元素进行排序。
总结
- HashSet:
- 基于哈希表实现。
- 快速查找、插入和删除(平均时间复杂度 O(1))。
- 元素无序。
- LinkedHashSet:
- 基于哈希表和双向链表实现。
- 保证插入顺序。
- 快速查找、插入和删除(平均时间复杂度 O(1)),但有额外的内存开销。
- TreeSet:
- 基于红黑树实现。
- 元素有序。
- 查找、插入和删除的时间复杂度为 O(log n)。
Map
HashMap
HashMap
是基于哈希表(hash table)实现的,它提供了高效的查找、插入和删除操作。
底层实现
-
哈希表:
HashMap
使用一个数组(桶)来存储键值对,每个桶对应一个链表或红黑树(链地址法处理哈希冲突)。 -
哈希函数:键的
hashCode
方法用于计算哈希值,决定元素在哈希表中的位置。 -
哈希冲突处理
:当两个键的哈希值相同(哈希冲突)时,元素存储在同一个桶的链表或红黑树中。
- 链表:当冲突较少时,使用链表存储冲突的元素。
- 红黑树:当冲突较多时,链表转换为红黑树,以提高查询效率。
特性
- 时间复杂度:查找、插入和删除操作的平均时间复杂度为 O(1)。
- 无序存储:
HashMap
中的元素没有特定的顺序。 - 允许 null:可以存储一个
null
键和多个null
值。 - 非线程安全:多个线程同时访问和修改
HashMap
可能导致数据不一致。
LinkedHashMap
LinkedHashMap
是 HashMap
的子类,具有 HashMap
的所有特性,并且维护了插入顺序或访问顺序。
底层实现
- 哈希表:与
HashMap
相同,使用数组和链表或红黑树处理哈希冲突。 - 双向链表:维护元素的插入顺序或访问顺序。每个元素还包含前后指针,形成一个双向链表。
特性
- 有序存储:按照插入顺序或访问顺序存储元素。
- 时间复杂度:与
HashMap
相同,查找、插入和删除操作的平均时间复杂度为 O(1)。 - 允许 null:可以存储一个
null
键和多个null
值。 - 非线程安全:多个线程同时访问和修改
LinkedHashMap
可能导致数据不一致。
TreeMap
TreeMap
是基于红黑树(Red-Black Tree)实现的,键值对按照键的自然顺序或指定的比较器顺序进行排序。
底层实现
- 红黑树:一种自平衡二叉搜索树,通过节点的旋转和重新着色来保持平衡。
- 排序:默认情况下,键按其自然顺序排序。如果提供了
Comparator
,则按Comparator
的顺序排序。
特性
- 有序存储:键值对按照键的顺序存储和迭代。
- 时间复杂度:查找、插入和删除操作的时间复杂度为 O(log n)。
- 不允许 null:
TreeMap
不允许null
键,但允许null
值。 - 非线程安全:多个线程同时访问和修改
TreeMap
可能导致数据不一致。
Hashtable
Hashtable
是基于哈希表实现的,与 HashMap
类似,但它是线程安全的,适用于多线程环境。
底层实现
- 哈希表:使用数组和链表处理哈希冲突。
- 同步:所有方法都使用
synchronized
关键字进行同步,确保线程安全。
特性
- 线程安全:所有方法都是同步的,适用于多线程环境。
- 不允许 null:不允许
null
键和null
值。 - 时间复杂度:查找、插入和删除操作的平均时间复杂度为 O(1)。
- 无序存储:元素没有特定的顺序。
Properties
Properties
是 Hashtable
的子类,专门用于处理属性文件(键和值都是字符串)。
底层实现
- 哈希表:与
Hashtable
相同,使用数组和链表处理哈希冲突。 - 字符串键值对:键和值都必须是字符串(
String
)。
特性
- 专用于属性文件:键和值都必须是字符串,常用于读取和保存配置文件。
- 线程安全:继承了
Hashtable
的同步特性,适用于多线程环境。 - 不允许 null:不允许
null
键和null
值。 - 无序存储:元素没有特定的顺序。
- 额外功能:提供
load
和store
方法,用于从输入流读取属性文件或将属性保存到输出流。
比较与选择
- HashMap:适用于对顺序不敏感且需要高效查找和更新操作的场景。
- LinkedHashMap:适用于需要维护插入顺序或访问顺序的场景。
- TreeMap:适用于需要有序存储和快速范围查询的场景。
- Hashtable:适用于需要线程安全的多线程环境,但不允许
null
键和值。 - Properties:适用于读取和保存配置文件,键和值都必须是字符串。
泛型
泛型也称参数类型,起到标识和约束作用。通过泛型处理不同类型对象的类、接口和方法时,无需显式地进行类型转换。
泛型类
public class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
使用泛型类
Box<String> stringBox = new Box<>();
stringBox.setValue("Hello");
String value = stringBox.getValue();
泛型方法
public <E> E method(E e) {
return e;
}
public <E> void voidMethod(E e) {
}
需要注意的是:
创建自定义泛型类的对象,没有指明泛型参数类型时,按照Object处理,但不等价于Object
泛型不能是基本类型,只能是引用类型
泛型不能用于异常
泛型可以有多个,逗号隔开。如Map<K, V>
泛型通配符
<?>
被称为无界通配符,表示任意类型。它常用于方法参数中,表示该方法可以接受任何类型的参数,而无需关心具体的类型是什么。
当你只需要读取数据,而不需要修改数据时,可以使用无界通配符。例如,打印集合中的所有元素。
通配符 | 描述 |
---|---|
? | 表示任何类型 |
? extends T | 表示类型是T或T的子类型 |
? super T | 表示类型是T或T的超类型 |