Java 集合
本文是在看过一些视频以及文章总结出的一些个人理解
Java集合常见面试题总结(上) | JavaGuide(Java面试+学习指南)
Collection 接口 (父接口)
# List
ArrayList
:Object[]
数组Vector
:Object[]
数组LinkedList
: 双向链表(JDK1.6 之前为循环链表,JDK1.7 取消了循环)
# Set
HashSet
(无序,唯一): 基于HashMap
实现的,底层采用HashMap
来保存元素LinkedHashSet
:LinkedHashSet
是HashSet
的子类,并且其内部是通过LinkedHashMap
来实现的。有点类似于我们之前说的LinkedHashMap
其内部是基于HashMap
实现一样,不过还是有一点点区别的TreeSet
(有序,唯一): 红黑树(自平衡的排序二叉树)
# Queue
PriorityQueue
:Object[]
数组来实现二叉堆ArrayQueue
:Object[]
数组 + 双指针
再来看看 Map
接口下面的集合。
# Map
HashMap
: JDK1.8 之前HashMap
由数组+链表组成的,数组是HashMap
的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间LinkedHashMap
:LinkedHashMap
继承自HashMap
,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap
在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。详细可以查看:《LinkedHashMap 源码详细分析(JDK1.8)》open in new windowHashtable
: 数组+链表组成的,数组是Hashtable
的主体,链表则是主要为了解决哈希冲突而存在的TreeMap
: 红黑树(自平衡的排序二叉树)
Iterator 迭代器
所有实现了Iteratable
接口的类都可以通过iterator()
方法获取迭代器
注意:重新获取iterator
即可重置迭代器;
增强 for 循环
- 可以对 数组 和 集合 使用;
- 底层使用的仍然是
iterator
; - 大写 I 可以快速生成代码(Idea)。
List 接口 可重复-有顺序
ArrayList
-
线程不安全
-
ArrayList 维护了一个 Object 类型的数组 elementData –
transient Object[] elementData
// transient 表示该属性不会被序列化 -
两种构造方式(构造时数组已经初始化):
- 无参构造:
ArrayList
,则初始化elementData
容量为0
,第一次添加时,则扩容至默认容量10
,如需再次扩容,则扩容为当前的1.5
倍(1+1/2);Vector(无参情况下) 扩容倍数是2,线程安全是因为每个方法头上添加了 synchronized - 指定
initialCapacity
大小的构造器:初始elementData
容量为指定大小,如需扩容,则直接扩容elementData
为当前的1.5倍
- 无参构造:
-
每次添加元素时,都会触发一次扩容检查,容量不满足 size+1 就扩容
-
源码解读如下
Vector
- 线程安全
- 如果无参,默认10,满后,
2倍
扩容;如果指定大小,满后则每次直接2倍
扩容(优先使用自定义增量capacityIncrement) - 有参构造可以指定扩容大小
Vector(int initialCapacity, int capacityIncrement)
- 源码解读如下
====================================================
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1); // 扩容检查
elementData[elementCount++] = e;
return true;
}
====================================================
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity); // 真正扩容方法
}
====================================================
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//扩容的关键算法
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0) // 扩容后仍不满足最小capacity要求
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0) // 超过最大容量
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
====================================================
LinkedList
- 底层维护了一个
双向链表
- 可以添加任意元素
- 其中有两个属性
first
和last
分别指向首节点和尾结点 - 每个节点(Node对象),里面又维护了
prev
,next
,item
三个属性,其中通过prev
指向前一个,通过next
指向后一个节点,最终实现双向链表 - LinkedList 增删快,查找慢
- 源码解读如下(尾插头删)
-----------------------------
public boolean add(E e) {
linkLast(e);
return true;
}
-----------------------------
void linkLast(E e) {
final Node<E> l = last; // 旧尾结点
final Node<E> newNode = new Node<>(l, e, null); // 创建新节点
last = newNode; // 新节点上位尾结点
if (l == null)
first = newNode; // 第一个节点 first->a last->a
else
l.next = newNode; // 新节点是从尾部连接的!新节点赋值->旧尾结点.next
size++;
modCount++;
}
-----------------------------
public E remove() {
return removeFirst(); //注意是第一个
}
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
-----------------------------
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E