文章目录
- 6.Java集合类使用总结
- 6.1概览
- 6.1.1集合接口类特性
- 6.1.2List接口和Set接口的区别
- 6.1.3简要介绍
- (1)List接口
- (2)Set接口
- (3)Map接口
- 6.2Collection接口
- 6.3List接口
- 6.3.1ArrayList
- 6.3.2LinkedList—不常用
- 6.3.3区分ArrayList与LinkedList
- 6.4Set接口
- 6.4.1HashSet
- 6.4.2LinkHashSet
- 6.4.3TreeSet
- 6.5Map接口
- 6.5.1HashMap
- 6.5.2LinkedHashMap
- 6.5.3HashTable
- 6.5.4TreeMap
- 6.5.5ConcurrentHashMap
- 6.6Collections工具类
- 6.6.1排序方法
- 6.6.2查找替换方法
- 6.6.3同步控制
- 6.6.4不可变集合
- 6.7常见面试题
- 6.7.1比较ArrayList与LinkedList的异同?
- 6.7.2比较HashSet、LinkHashSet、TreeSet的异同?
- 6.7.3如何选用集合?
- 6.7.4ArrayList插入和删除元素的时间复杂度?
- (1)插入
- (2)删除
- 6.7.5LinkedList 插入和删除元素的时间复杂度?
- 6.7.6比较HashMap和HashTable的异同?
- 6.7.7比较HashMap和HashSet的异同?
- 6.7.8比较HashMap和TreeMap的异同?
- (1)TreeMap自定义排序比较器
6.Java集合类使用总结
6.1概览
Java 集合, 也叫作容器,主要是由两大接口派生而来:一个是 Collection
接口,主要用于存放单一元素;另一个是 Map
接口,主要用于存放键值对。对于Collection
接口,下面又有两个个主要的子接口:List
、Set
6.1.1集合接口类特性
- Collection接口:存储无序,不唯一的对象
- List接口:存储有序,不唯一的对象
- Set接口:存储无序,唯一的对象
- Map接口:存储 键值(key-value) 对象
6.1.2List接口和Set接口的区别
- 存储元素不同:
- List接口存储不唯一,有序的对象
- Set接口存储唯一,不唯一的对象
- 查找/删除和插入效率不同:
- Set 查找效率低,删除和插入效率高,插入和删除不会引起元素位置改变。
- List查找元素效率高,插入删除效率低,因为会引起其他元素位置改变
6.1.3简要介绍
(1)List接口
ArrayList
:Object[]
数组。。LinkedList
:双向链表(JDK1.6 之前为循环链表,JDK1.7 取消了循环)
(2)Set接口
-
HashSet
(无序,唯一): 基于HashMap
实现的,底层采用HashMap
来保存元素。 -
LinkedHashSet
:LinkedHashSet
是HashSet
的子类,并且其内部是通过LinkedHashMap
来实现的。 -
TreeSet
(有序,唯一): 红黑树(自平衡的排序二叉树)
(3)Map接口
HashMap
:JDK1.8 之前HashMap
由数组+链表组成的,数组是HashMap
的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。- LinkedHashMap
:
LinkedHashMap继承自
HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,
LinkedHashMap在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。详细可以查看:[LinkedHashMap 源码分析]()
- Hashtable
:数组+链表组成的,数组是
Hashtable的主体,链表则是主要为了解决哈希冲突而存在的。
- TreeMap`:红黑树(自平衡的排序二叉树)
6.2Collection接口
Collection 接口是层次结构中的根接口。构成 Collection 的单位称为元素。Collection 接口通常不能直接使用,但该接口提供了添加元素、删除元素、管理数据的方法。由于 List 接口与 Set 接口都继承了 Collection 接口,因此这些方法对 List 集合与 Set 集合是通用的。
- 常用方法:
方法 | 描述 |
---|---|
add(E e) | 将指定的对象添加到该集合中 |
remove(Object o) | 将指定的对象从该集合中移除 |
isEmpty() | 返回 boolean 值,用于判断当前集合是否为空 |
iterator() | 返回在此 Collection 的元素上进行迭代的迭代器。用于遍历集合中的对象 |
size() | 返回 int 型值,获取该集合中元素的个数 |
- 遍历集合
6.3List接口
6.3.1ArrayList
- 存储对象:
ArrayList
中只能存储引用类型数据的对象。对于基本类型数据,需要使用其对应的包装类 - 底层数据结构:底层使用
Object[]
存储,适用于频繁的查找工作,线程不安全 - null值问题:可以添加null值
- 线程安全问题:不安全
- 数组大小:
ArrayList
创建时不需要指定大小,会根据实际存储的元素动态地扩容或缩容,ArrayList
允许使用泛型来确保类型安全
6.3.2LinkedList—不常用
- 存储对象:存储对象:LinkedList 可以存储任意类型的对象,包括基本数据类型和引用数据类型
- 底层数据结构:LinkedList 的底层数据结构是双向链表,通过一个 Node 内部类实现的这种链表结构
- null值问题:LinkedList 允许存储 null 值作为元素
- 线程安全问题:不安全
6.3.3区分ArrayList与LinkedList
ArrayList | LinkedList | |
---|---|---|
线程安全 | 不安全 | 不安全 |
底层数据结构 | Object数组 | 双向链表(JDK1.6 之前为循环链表,JDK1.7 取消了循环。注意双向链表和双向循环链表的区别) |
插入和删除是否受元素位置的影响 | 采用数组存储,受影响(O(1)或者O(n)) | 链表存储,指定位置插入删除、首尾(O(1))、O(n) |
是否支持快速随机访问 | 实现了 RandomAccess 接口,通过元素的序号快速获取元素对象(对应于get(int index) 方法) | 不支持 |
内存空间占用 | 空间浪费主要体现在在 list 列表的结尾会预留一定的容量空间 | LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(前驱和后驱) |
6.4Set接口
Set 接口常用的实现类有 HashSet 、LinkedHashSet、TreeSet。
6.4.1HashSet
- 存储对象:HashSet 存储对象采用哈希表的方式,它不允许重复元素,即集合中不会包含相同的元素。当向 HashSet 中添加元素时,会根据元素的 hashCode() 方法和 equals() 方法来判断元素是否重复。
- 底层数据结构:HashSet 的底层数据结构是基于 HashMap 实现的,实际上 HashSet 的元素就是作为 HashMap 的 key 存储的。
- null 值问题:HashSet 允许存储一个 null 元素。
- 线程安全问题:不安全
6.4.2LinkHashSet
- 存储对象:LinkedHashSet 也存储唯一的对象,不允许重复元素。当向 LinkedHashSet 中添加元素时,会根据元素的 hashCode() 方法和 equals() 方法来判断元素是否重复。
- 底层数据结构:LinkedHashSet 的底层数据结构是基于 LinkedHashMap 实现的,实际上 LinkedHashSet 内部维护了一个 LinkedHashMap 对象。LinkedHashMap 通过维护一个双向链表来保持元素的插入顺序,因此 LinkedHashSet 可以保持元素的插入顺序。
- null 值问题:LinkedHashSet 允许存储一个 null 元素。与 HashSet 类似,null 值在计算哈希值时会被映射到哈希表中的一个位置,并且 LinkedHashSet 会保证集合中只有一个 null 元素。
- 线程安全问题:不安全
6.4.3TreeSet
- 存储对象:TreeSet 存储唯一的对象,不允许重复元素。当向 TreeSet 中添加元素时,会根据元素的比较规则(通过实现 Comparable 接口或者提供 Comparator)来判断元素是否重复。
- 底层数据结构:TreeSet 的底层数据结构是基于红黑树实现的。红黑树是一种自平衡的二叉查找树,可以保持元素的有序性
- null 值问题:TreeSet 不允许存储 null 元素,如果向 TreeSet 中添加 null 元素,会抛出 NullPointerException 异常。
- 线程安全问题:TreeSet 不是线程安全的
6.5Map接口
Map接口常见的实现类有HashMap、LinkedHashMap、HashTable、TreeMap、ConcurrentHashMap
6.5.1HashMap
- 存储对象:HashMap 存储键值对(key-value pair),其中 key 是唯一的,不允许重复。
- 底层数据结构:HashMap 的底层数据结构是一个数组和链表/红黑树的组合,即数组加链表(或红黑树)解决冲突。当多个键的哈希值相同时,它们会存储在同一个桶中,并以链表(JDK7)或者红黑树(JDK8)的形式进行存储,以提高查找、插入和删除的效率。
- null 值问题:HashMap 允许一个键为 null 的键值对
- 线程安全:HashMap 不是线程安全的
6.5.2LinkedHashMap
- 存储对象:LinkedHashMap 也是基于键值对的存储结构,可以存储键值对
- 底层数据结构:LinkedHashMap 的底层数据结构是基于哈希表和双向链表实现的
- null 值问题:与 HashMap 类似,可以存储一个键为 null 的键值对。
- 线程安全:LinkedHashMap 不是线程安全的
6.5.3HashTable
- 存储对象:HashTable 也是基于键值对的存储结构,可以存储键值对
- 底层数据结构:HashTable 的底层数据结构是基于哈希表实现的,类似于 HashMap。当发生哈希冲突时,HashTable 使用开放寻址法来解决,即在冲突位置的下一个可用位置插入元素。
- null 值问题:HashTable 不允许存储 null 键和 null 值。如果尝试将 null 键或 null 值放入 HashTable 中,将会抛出 NullPointerException 异常。
- 线程安全:HashTable 是线程安全的,所有的方法都是同步的。
6.5.4TreeMap
- 存储对象:TreeMap 也是基于键值对的存储结构,可以存储键值对,并且会根据键的自然排序或自定义排序进行排序。根据键的比较结果,TreeMap 中的键值对会按照升序排列。
- 底层数据结构:TreeMap 的底层数据结构是红黑树(Red-Black Tree)。红黑树是一种自平衡的二叉搜索树,通过保持树的平衡性,可以在 O(logN) 的时间复杂度下进行插入、删除和查找操作。
- null 值问题:TreeMap 不允许键为 null,因为需要根据键的比较进行排序
- 线程安全:TreeMap 不是线程安全的
6.5.5ConcurrentHashMap
- 存储对象:ConcurrentHashMap 也是基于键值对的存储结构,可以存储键值对
- 底层数据结构:ConcurrentHashMap 的底层数据结构是分段锁(Segmented Array),即将整个 Map 分成多个小的 Segment,在每个 Segment 上都可以单独加锁,从而实现更细粒度的并发控制。这样可以在大部分操作上实现并发访问,提高了并发性能。
- null 值问题:ConcurrentHashMap 允许键和值为 null
- 线程安全:ConcurrentHashMap 是线程安全的
6.6Collections工具类
Java 提供了一个操作 List、Set 和 Map 等集合的工具类:Collections,该工具类提供了大量方法对集合进行排序、查询和修改等操作,还提供了将集合对象置为不可变、对集合对象实现同步控制等方法。
这个类不需要创建对象,内部提供的都是静态方法。
6.6.1排序方法
方法 | 描述 |
---|---|
static void reverse(List<?> list) | 反转列表中元素的顺序。 |
static void shuffle(List<?> list) | 对List集合元素进行随机排序。 |
static void sort(List list) | 根据元素的自然顺序 对指定列表按升序进行排序 。 |
static void sort(List list, Comparator<? super T> c) | 根据指定比较器产生的顺序对指定列表进行排序。 |
static void swap(List<?> list, int i, int j) | 在指定List的指定位置i,j处交换元素。 |
static void rotate(List<?> list, int distance) | 当distance为正数时,将List集合的后distance个元素“整体”移到前面; 当distance为负数时,将list集合的前distance个元素“整体”移到后边。该方法不会改变集合的长度。 |
6.6.2查找替换方法
方法 | 描述 |
---|---|
static int binarySearch(List<? extends Comparable<? super T>>list, T key) | 使用二分搜索法搜索指定列表,以获得指定对象在List集合中的索引。 |
static Object max(Collection coll) | 根据元素的自然顺序,返回给定collection 的最大元素。 |
static Object max(Collection coll,Comparator comp) | 根据指定比较器产生的顺序,返回给定 collection 的最大元素。 |
static Object min(Collection coll) | 根据元素的自然顺序,返回给定collection 的最小元素。 |
static Object min(Collection coll,Comparator comp) | 根据指定比较器产生的顺序,返回给定 collection 的最小元素。 |
static void fill(List<? super T> list, T obj) | 使用指定元素替换指定列表中的所有元素。 |
static int frequency(Collection<?> c, Object o) | 返回指定 collection 中等于指定对象的出现次数。 |
static int indexOfSubList(List<?> source, List<?> target) | 返回指定源列表中第一次出现指定目标列表的起始位置;如果没有出现这样的列表,则返回 -1。 |
static int lastIndexOfSubList(List<?> source, List<?> target) | 返回指定源列表中最后一次出现指定目标列表的起始位置;如果没有出现这样的列表,则返回 -1。 |
static boolean replaceAll(List list, T oldVal, T newVal) | 使用一个新值替换List对象的所有旧值oldVal |
6.6.3同步控制
方法 | 描述 |
---|---|
static Collection synchronizedCollection(Collection c) | 返回指定 collection 支持的同步(线程安全的)collection。 |
static List synchronizedList(List list) | 返回指定列表支持的同步(线程安全的)列表。 |
static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) | 返回由指定映射支持的同步(线程安全的)映射。 |
static Set synchronizedSet(Set s) | 返回指定 set 支持的同步(线程安全的)set。 |
6.6.4不可变集合
方法 | 描述 |
---|---|
emptyXxx() | 返回一个空的、不可变的集合对象。 |
singletonXxx() | 返回一个只包含指定对象(只有一个或一个元素)的不可变的集合对象。 |
unmodifiableXxx() | 返回指定集合对象的不可变视图。 |
6.7常见面试题
6.7.1比较ArrayList与LinkedList的异同?
- 相同:两者都继承了list接口,存储的元素有序、不唯一,可存储null,线程不安全
- 不同
- 存储的元素类型不同:ArrayList只能存储引用类型的数据(基本类型数据需要用到包装类);LinkedList则可以存储基本类型、引用类型数据
- 底层数据结构不同:ArrrayList底层是动态数组;LinkedList底层是双向链表
- 查找/插入删除效率不同:ArrayList查找效率更高,LinkedList插入删除效率更高
- ArrrayList支持快速访问,LinkedList不支持
6.7.2比较HashSet、LinkHashSet、TreeSet的异同?
- 相同:
HashSet
、LinkedHashSet
和TreeSet
都是Set
接口的实现类,都能保证元素唯一,并且都不是线程安全的 - 不同
- 底层数据结构不同:
HashSet
的底层数据结构是哈希表(基于HashMap
实现)。LinkedHashSet
的底层数据结构是链表和哈希表,元素的插入和取出顺序满足 FIFO。TreeSet
底层数据结构是红黑树,元素是有序的,排序的方式有自然排序和定制排序。
- 应用不同:
HashSet
用于不需要保证元素插入和取出顺序的场景LinkedHashSet
用于保证元素的插入和取出顺序满足 FIFO 的场景TreeSet
用于支持对元素自定义排序规则的场景。
- 底层数据结构不同:
6.7.3如何选用集合?
-
我们需要根据键值获取到元素值时就选用
Map
接口下的集合- 需要排序时选择
TreeMap
- 不需要排序时就选择
HashMap
- 需要保证线程安全就选用
ConcurrentHashMap
。
- 需要排序时选择
-
我们只需要存放元素值时,就选择实现
Collection
接口的集合- 需要保证元素唯一时选择实现
Set
接口的集合比如TreeSet
或HashSet
- 不需要就选择实现
List
接口的比如ArrayList
或LinkedList
,然后再根据实现这些接口的集合的特点来选用
- 需要保证元素唯一时选择实现
6.7.4ArrayList插入和删除元素的时间复杂度?
(1)插入
- 头部插入:由于需要将所有元素都依次向后移动一个位置,因此时间复杂度是 O(n)。
- 尾部插入:当
ArrayList
的容量未达到极限时,往列表末尾插入元素的时间复杂度是 O(1),因为它只需要在数组末尾添加一个元素即可;当容量已达到极限并且需要扩容时,则需要执行一次 O(n) 的操作将原数组复制到新的更大的数组中,然后再执行 O(1) 的操作添加元素。 - 指定位置插入:需要将目标位置之后的所有元素都向后移动一个位置,然后再把新元素放入指定位置。这个过程需要移动平均 n/2 个元素,因此时间复杂度为 O(n)
(2)删除
- 头部删除:由于需要将所有元素依次向前移动一个位置,因此时间复杂度是 O(n)。
- 尾部删除:当删除的元素位于列表末尾时,时间复杂度为 O(1)。
- 指定位置删除:需要将目标元素之后的所有元素向前移动一个位置以填补被删除的空白位置,因此需要移动平均 n/2 个元素,时间复杂度为 O(n)。
6.7.5LinkedList 插入和删除元素的时间复杂度?
- 头部插入/删除:只需要修改头结点的指针即可完成插入/删除操作,因此时间复杂度为 O(1)。
- 尾部插入/删除:只需要修改尾结点的指针即可完成插入/删除操作,因此时间复杂度为 O(1)。
- 指定位置插入/删除:需要先移动到指定位置,再修改指定节点的指针完成插入/删除,因此需要移动平均 n/2 个元素,时间复杂度为 O(n)
6.7.6比较HashMap和HashTable的异同?
- 相同:两者均实现了Map接口,用于存储key-value键值对的数据
- 不同:
- 底层数据结构不同:HashMap底层为(数组+链表或者数组+红黑树)、HashTable底层为哈希表(哈希数组)+链表,使用寻址开放法解决哈希冲突
- null值问题不同:HashMap支持null键(一个)和null值(多个);HashTable不支持null键和null值
- 线程安全问题不同:HashMap不安全,HashTable安全
- 初始化机制和扩容机制不同:
- HashMap的初始化容量为16,若容量超出阈值(数组容量16*负载因子0.75=12)的话,则扩容为原来的2倍
- HashTable的初始化容量为11,之后每次扩容,容量变为原来的2n+1
- HashMap若指定容量初始值,HashMap会将其扩充为最近的2的幂次方大小(HashMap中的tableSizeFor()方法保证)
- HashTable若指定容量初始值,直接使用该指定大小
6.7.7比较HashMap和HashSet的异同?
ps:
HashSet
底层就是基于HashMap
实现的。(HashSet
的源码非常非常少,因为除了clone()
、writeObject()
、readObject()
是HashSet
自己不得不实现之外,其他方法都是直接调用HashMap
中的方法。
HashMap | HashSet | |
---|---|---|
1. 实现的接口及存储的对象不同 | 实现了 Map 接口,存储键值对 | 实现 Set 接口,仅存储对象 |
2.添加方法不同 | 调用 put() 向 map 中添加元素 | 调用 add() 方法向 Set 中添加元素 |
3.比较对象是否相同方法不同 | HashMap 使用键(Key)计算 hashcode | HashSet 使用成员对象来计算 hashcode 值,对于两个对象来说 hashcode 可能相同 ,所以equals() 方法用来判断对象的相等性 |
6.7.8比较HashMap和TreeMap的异同?
- 相同:两者都实现Map接口,存储key-value,线程不安全
- 不同:
- 底层数据结构不同:TreeMap底层是一棵自平衡的多路查找树(红黑树)
- null值问题不同:TreeMap不能存储null键的数据,因为需要排序
- 排序问题不同:TreeMap强大的功能点在于可以排序
(1)TreeMap自定义排序比较器
public class Person {
private Integer age;
public Person(Integer age) {
this.age = age;
}
public Integer getAge() {
return age;
}
public static void main(String[] args) {
TreeMap<Person, String> treeMap = new TreeMap<>(new Comparator<Person>() {
@Override
public int compare(Person person1, Person person2) {
int num = person1.getAge() - person2.getAge();
return Integer.compare(num, 0);
}
});
treeMap.put(new Person(3), "person1");
treeMap.put(new Person(18), "person2");
treeMap.put(new Person(35), "person3");
treeMap.put(new Person(16), "person4");
treeMap.entrySet().stream().forEach(personStringEntry -> {
System.out.println(personStringEntry.getValue());
});
}
}
输出:TreeMap
中的元素已经是按照 Person
的 age 字段的升序来排列了
person1
person4
person2
person3
我们是通过传入匿名内部类的方式实现的,你可以将代码替换成 Lambda 表达式实现的方式:
TreeMap<Person, String> treeMap = new TreeMap<>((person1, person2) -> {
int num = person1.getAge() - person2.getAge();
return Integer.compare(num, 0);
});
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/e2e735f4e3f04c838f93352dff481d63.jpeg#pic_center)