集合源码学习笔记
参考博客: Java集合详解(非常详细!!!)_阿里官方架构师的博客-CSDN博客_java 集合
1.集合框架
顶层接口Iterable、Collection:
List、Queue、Set接口都实现了Collection接口,Collection接口实现了Iterable接口。
Iterable接口中只有iterator()一个接口方法,Iterator也是一个接口,其主要有如下两个方法hasNext()和next()方法。也就是说,实现了Iterable接口的方法,就能使用迭代器了。
Collection 则包含了集合类常用的方法:add()、remove()、size()、isEmpty()、contains()、toArray()等常用方法。
public interface Collection<E> extends Iterable<E> {
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
boolean add(E e);
boolean remove(Object o);
boolean containsAll(Collection<?> c);
boolean removeAll(Collection<?> c);
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
boolean retainAll(Collection<?> c);
void clear();
int hashCode();
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, 0);
}
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
default Stream<E> parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
}

2.List
(1) List接口
List表示一串有序的集合,和Collection接口含义不同的是List突出有序的含义。
public interface List<E> extends Collection<E> {
<T> T[] toArray(T[] a);
boolean addAll(Collection<? extends E> c);
boolean addAll(int index, Collection<? extends E> c);
default void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
final ListIterator<E> li = this.listIterator();
while (li.hasNext()) {
li.set(operator.apply(li.next()));
}
}
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
boolean equals(Object o);
E get(int index);
E set(int index, E element);
void add(int index, E element);
int indexOf(Object o);
int lastIndexOf(Object o);
ListIterator<E> listIterator();
List<E> subList(int fromIndex, int toIndex);
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, Spliterator.ORDERED);
}
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, 0);
}
}
List比Collection多了添加方法 add 和 addAll ,查找方法get,indexOf,set等方法,并且支持index下标操作。
可以看出Collection和List最大的区别是Collection是无序的,不支持索引操作,而List是有序的。Collection没有顺序的概念。所以 List 可以进行排序,支持sort方法。
(2) ArrayList
① 组成与构造方法
组成:
//空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//构造方法使用的的空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//真正存放元素的数组
transient Object[] elementData;
private int size;
transient Object[] elementData
,该elementData是真正存放元素的容器,可见ArrayList是基于数组实现的。
构造方法:
//自定义大小构造
public ArrayList(int initialCapacity) {
//initialCapacity为自定义的初始大小
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {//大小为0,创建空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
//默认大小构造,也是空的
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
可以看到ArrayList中有两个静态的空数组,其实他们没有什么功能上的区别,只是对状态的一种划分:
在java8中(上面代码):
- 无参构造时,elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
- 有参构造时,如果给定初始容量为0,elementData = EMPTY_ELEMENTDATA。
在java7中,只有 EMPTY_ELEMENTDATA :
- 无参构造时,elementData = EMPTY_ELEMENTDATA;
- 有参构造时,会新建一个空数组,赋给elementData。
java8,对新建数组进行了性能上的优化。
② 扩容
通过上面的构造方法,是否产生了疑问?每次创建新的ArrayList,其实都指向了两个默认的空数组,那实际使用的过程中,每个ArrayList为何又不同了呢?看下面代码:
添加:
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
扩容:
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//DEFAULT_CAPACITY是10
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
由添加、扩容两步可以看出,每次添加元素时,都会先对容量进行判断。calculateCapacity
用来判断该ArrayList是否是默认的,如果是默认的会给他开辟10个空间。
之后再进行判断,minCapacity - elementData.length > 0
,说明所需容量已经大于已有容量,就会进行扩容grow()。
private void grow(int minCapacity) {
//之前的容量
int oldCapacity = elementData.length;
//新的容量 = 旧的容量+旧的容量/2 也就是1.5倍扩容
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果说新的容量还不够,将容量直接改为需求容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果说新容量比默认的最大值要大了
//int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 复制
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
//minCapacity < 0 说明容量大小已经超出int的范围
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//继续扩容到 MAX_ARRAY_SIZE 或 Integer.MAX_VALUE
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
每次创建新的ArrayList,其实都指向了两个默认的空数组,那实际使用的过程中,每个ArrayList为何又不同了呢?
最开始的问题也解决了,其实扩容的过程,就是找到一个新的合适的容量,然后进行拷贝(新建了一个数组)的过程。
举个例子,这里写了一个自定义的ArrayList,添加了一个可以获取ElementData的方法:
public Object[] getElementData(){
return this.elementData;
}
测试:
public class Main{
public static void main(String[] args){
ArrayList<Integer> list1 = new ArrayList<>();
ArrayList<Integer> list2 = new ArrayList<>();
System.out.println(System.identityHashCode(list1.getElementData()));
System.out.println(System.identityHashCode(list2.getElementData()));
list1.add(1);
System.out.println(System.identityHashCode(list1.getElementData()));
list2.add(1);
System.out.println(System.identityHashCode(list2.getElementData()));
}
}
/*
1956725890
1956725890
1735600054
21685669
*/
可以看出list1和list2,初始化后数组地址是一样的,都指向 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
添加元素后,扩容,替换成了新的数组。
③ 详解MAX_ARRAY_SIZE
为什么MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
要减去8?为什么有最大值了,还能扩容到Integer.MAX_VALUE
?
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
Java 8 ArrayList hugeCapacity 函数与 MAX_ARRAY_SIZE-阿里云开发者社区 (aliyun.com)
简单总结上述链接的内容:
首先,有些虚拟机会在数组头部保存数组的长度,而8就是保存长度所需空间。(第一个问题)
但是,并不是所有虚拟机都需要存数组长度,所以理论上ArrayList的最大值仍然是 Integer.MAX_VALUE
。所以,如果长度非要超过MAX_ARRAY_SIZE
,那就摆烂了,直接开到最大吧。(第二个问题)
④ 数组拷贝
那么,回到之前扩容的过程,Math.max(DEFAULT_CAPACITY, minCapacity)
取最大值,为什么minCapacity
会大于10呢?
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//DEFAULT_CAPACITY是10
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
这就要说到addAll(),将另一个集合中的元素全部add到当前集合中。
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
//获取加入集合的大小
int numNew = a.length;
//回到之前的判断过程,这次minCapacity的值是size + numNew,可能会大于默认的10(开头的问题)
ensureCapacityInternal(size + numNew);
//拷贝集合
System.arraycopy(a, 0, elementData, size, numNew);
//改变size
size += numNew;
return numNew != 0;
}
数组拷贝一般有四种方法:
-
for循环
-
System.arraycopy()
public final class System { ... public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length); ... }
-
Arrays.copyOf()
public static int[] copyOf(int[] original, int newLength) { int[] copy = new int[newLength]; //这里底层还是 System.arraycopy System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; }
-
Object.clone()
public class Object { ... protected native Object clone() throws CloneNotSupportedException; ... }
for循环不提,clone()是Object类的方法,arraycopy是System类的方法。都是 native方法实现的拷贝方法。 Java 是无法自己分配空间的,由底层的C实现。
Arrays.copyOf()
底层其实也是System.arraycopy()
。
⑤ transient
transient Object[] elementData;
可以看到,Object是用transient修饰的,那么transient是什么呢?在这里又有什么作用呢?
Java中transient关键字的详细总结_老鼠只爱大米的博客-CSDN博客_java transient
首先,transient的作用是该属性不参与序列化。如果一个类实现了Serializable接口,那么除了静态变量、方法,和transient关键字修饰的变量(transient不能修饰方法),其他都会被序列化。
同样,ArrayList也实现了Serializable接口,但这里的transient Object[] elementData;
不是为了防止序列化,而是手动序列化,保证安全,看下面代码:
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
/**
* Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
* deserialize it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
int capacity = calculateCapacity(elementData, size);
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
只看 writeObject,先写入size,再循环写入每个elementData,手动的把elementData序列化了。重点是下面这段代码:
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
在很多地方都能看到modCount的身影:
//每次添加元素都要确认容量,所以说,每次添加元素modCount++
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//删除元素modCount++
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
所以modCount记录的是List的修改次数,写入完成后,if (modCount != expectedModCount)
,说明写入前后数据不一致,抛出异常,序列化失败,Java集合都是用这种方法保证序列化安全的。
(3) LinkedList
LinkedList是一种链表结构。
LinkedList出了List接口,还实现了Queue接口,所以LinkedList功能更多。
① 组成与构造方法
LinkedList由 size、头节点,尾节点组成。
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
节点Node:
private static class Node<E> {
E item;//当前值
Node<E> next;//下个节点
Node<E> prev;//上个节点
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
构造方法:
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
② 头插和尾插
/**
* Links e as first element.
*/
private void linkFirst(E e) {
//获取头节点
final Node<E> f = first;
//新建头节点:前置节点为null,当前节点为头插的e,后置节点为原头节点f
final Node<E> newNode = new Node<>(null, e, f);
//覆盖
first = newNode;
//如果f == null,说明原List没有节点,尾节点为空,赋值
if (f == null)
last = newNode;
//其他情况下,因为之前的头节点前置节点是null,赋值
else
f.prev = newNode;
size++;
modCount++;
}
/**
* Links e as last element.
*/
//尾插同理
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;
else
l.next = newNode;
size++;
modCount++;
}
③ 添加、查询和修改
添加,调用 linkLast 方法:
public boolean add(E e) {
linkLast(e);
return true;
}
查询:
查询分为两部分,检验index、实际查询:
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
先看检验的过程:
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
实际查询:
Node<E> node(int index) {
//这里和size/2比较,可以找到index距离头还是尾更近,之后遍历即可
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
删除:
public E remove(int index) {
//检验index
checkElementIndex(index);
//node():找到删除node位置,同查询
return unlink(node(index));
}
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
(4) Vector和Stack
List接口主要实现类有ArrayLIst,LinkedList,Vector,Stack,后两者很少使用。
Vector,Stack都是使用 synchronized 修饰的,线程安全。
① Vector
和ArrayList一样,Vector也是List接口的一个实现类。
组成:
//存放元素的数组
protected Object[] elementData;
//有效元素数量,小于等于数组长度
protected int elementCount;
//容量增加量,和扩容相关,默认是0
protected int capacityIncrement;
扩容:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//扩容:如果没设置capacityIncrement,直接两倍扩容
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
移除:
public synchronized E remove(int index) {
modCount++;
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
int numMoved = elementCount - index - 1;
if (numMoved > 0)
//复制数组,假设数组移除了中间某元素,后边有效值前移1位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//引用null ,gc会处理
elementData[--elementCount] = null; // Let gc do its work
return oldValue;
}
② Stack
先进后出的数据结构。
Stack继承于Vector,其也是List接口的实现类。之前提到过Vector是线程安全的,因为其方法都是synchronized修饰的,故此处Stack从父类Vector继承而来的操作也是线程安全的。
Stack中很多方法都是基于Vector实现的。
public
class Stack<E> extends Vector<E> {
public Stack() {
}
//入栈,使用的是Vector的addElement方法。
public E push(E item) {
addElement(item);
return item;
}
//出栈,找到数组最后一个元素,移除并返回。
public synchronized E pop() {
E obj;
int len = size();
obj = peek();
removeElementAt(len - 1);
return obj;
}
public synchronized E peek() {
int len = size();
if (len == 0)
throw new EmptyStackException();
return elementAt(len - 1);
}
public boolean empty() {
return size() == 0;
}
public synchronized int search(Object o) {
int i = lastIndexOf(o);
if (i >= 0) {
return size() - i;
}
return -1;
}
private static final long serialVersionUID = 1224463164541339165L;
}
3.Queue
先进先出的数据结构,单向队列。
public interface Queue<E> extends Collection<E> {
//集合中插入元素
boolean add(E e);
//队列中插入元素
boolean offer(E e);
//移除元素,当集合为空,抛出异常
E remove();
//移除队列头部元素并返回,如果为空,返回null
E poll();
//查询集合第一个元素,如果为空,抛出异常
E element();
//查询队列中第一个元素,如果为空,返回null
E peek();
}
(1) Deque接口
双端队列。
public interface Deque<E> extends Queue<E> {
//deque的操作方法
void addFirst(E e);
void addLast(E e);
boolean offerFirst(E e);
boolean offerLast(E e);
E removeFirst();
E removeLast();
E pollFirst();
E pollLast();
E getFirst();
E getLast();
E peekFirst();
E peekLast();
boolean removeFirstOccurrence(Object o);
boolean removeLastOccurrence(Object o);
// *** Queue methods ***
boolean add(E e);
boolean offer(E e);
E remove();
E poll();
E element();
E peek();
// 省略一堆stack接口方法和collection接口方法
}
同Queue,多了很多Last、First操作,可以在队列两端进行操作。
Java中关于Queue的实现主要用的是双端队列。
Queue的实现有PriorityQueue,Deque的实现类有ArrayDeque和LinkedList,两者一个是基于数组的实现,一个是基于链表的实现。
(2) PriorityQueue
优先级队列。
①组成与构造方法
组成:
//默认容量大小,数组大小
private static final int DEFAULT_INITIAL_CAPACITY = 11;
//存放元素的数组
transient Object[] queue;
//队列中存放了多少元素
private int size = 0;
//自定义的比较规则,有该规则时优先使用,否则使用元素实现的Comparable接口方法。
private final Comparator<? super E> comparator;
//队列修改次数,每次存取都算一次修改
transient int modCount = 0;
构造方法:
public PriorityQueue() {
//无参构造,默认容量11
this(DEFAULT_INITIAL_CAPACITY, null);
}
public PriorityQueue(int initialCapacity) {
//自定义容量
this(initialCapacity, null);
}
public PriorityQueue(Comparator<? super E> comparator) {
//默认容量11,自定义比较规则
this(DEFAULT_INITIAL_CAPACITY, comparator);
}
public PriorityQueue(int initialCapacity,
Comparator<? super E> comparator) {
if (initialCapacity < 1)
throw new IllegalArgumentException();
//根据容量,创建数组
this.queue = new Object[initialCapacity];
//确定比较规则
this.comparator = comparator;
}
重点: 一个存放元素的数组,和一个定义比较规则的比较器。
② 添加
add、offer方法:
public boolean add(E e) {
return offer(e);
}
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
//扩容
if (i >= queue.length)
grow(i + 1);
size = i + 1;
if (i == 0)
//添加的第一个元素,直接放到0位置
queue[0] = e;
else
siftUp(i, e);
return true;
}
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
//比较器为空,使用默认规则
siftUpComparable(k, x);
}
默认比较规则siftUpComparable
:
private void siftUpComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>) x;
while (k > 0) {
//(k-1)/2可以找到该节点的父节点
int parent = (k - 1) >>> 1;
//父节点
Object e = queue[parent];
//当传入的新节点大于父节点则不做处理,否则二者交换
if (key.compareTo((E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = key;
}
可以看出,默认规则使用堆排序保证第一个元素是最小的。
③ 取出
peek
//因为堆顶元素最小,直接返回queue[0]即可
public E peek() {
return (size == 0) ? null : (E) queue[0];
}
poll
public E poll() {
if (size == 0)
return null;
//--size为最后一个元素的下标
int s = --size;
modCount++;
E result = (E) queue[0];
E x = (E) queue[s];
queue[s] = null;
//如果数组没被取空,则要重新确定堆顶元素
if (s != 0)
siftDown(0, x);
return result;
}
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
//比较器为空,使用默认规则
siftDownComparable(k, x);
}
private void siftDownComparable(int k, E x) {//传入索引0,和最后一个元素
Comparable<? super E> key = (Comparable<? super E>)x;
int half = size >>> 1; // loop while a non-leaf
while (k < half) {
int child = (k << 1) + 1; // assume left child is least
Object c = queue[child];
int right = child + 1;
if (right < size &&
//c和right是parent的两个子节点,找出小的那个成为新的c。
((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
c = queue[child = right];
if (key.compareTo((E) c) <= 0)
break;
//小的变成了新的父节点
queue[k] = c;
k = child;
}
queue[k] = key;
}
(3) ArrayDeque
在Java中Deque的实现有LinkedList和ArrayDeque,正如它两的名字就标志了它们的不同,LinkedList是基于双向链表实现的,而ArrayDeque是基于数组实现的。
① 组成与构造方法
组成:
//具体存放元素的数组,数组大小一定是2的幂次方
transient Object[] elements;
//队列头索引
transient int head;
//队列尾索引
transient int tail;
//默认的最小初始化容量,即传入的容量小于8容量为8,而默认容量是16
private static final int MIN_INITIAL_CAPACITY = 8;
构造方法:
public ArrayDeque() {
elements = new Object[16];
}
public ArrayDeque(int numElements) {
allocateElements(numElements);
}
public ArrayDeque(Collection<? extends E> c) {
allocateElements(c.size());
addAll(c);
}
private void allocateElements(int numElements) {
elements = new Object[calculateSize(numElements)];
}
private static int calculateSize(int numElements) {
int initialCapacity = MIN_INITIAL_CAPACITY;
// Find the best power of two to hold elements.
// Tests "<=" because arrays aren't kept full.
if (numElements >= initialCapacity) {
initialCapacity = numElements;
initialCapacity |= (initialCapacity >>> 1);
initialCapacity |= (initialCapacity >>> 2);
initialCapacity |= (initialCapacity >>> 4);
initialCapacity |= (initialCapacity >>> 8);
initialCapacity |= (initialCapacity >>> 16);
initialCapacity++;
if (initialCapacity < 0) // Too many elements, must back off
initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements
}
return initialCapacity;
}
可以看出:
- 无参构造时,大小为16
- 有参构造时,若参数小于MIN_INITIAL_CAPACITY(8),大小为8;若参数大于MIN_INITIAL_CAPACITY,保证大小为2的幂,即保证每一位都是1,再加1。
此处elements数组的长度永远是2的幂次方,和HashMap类似,即保证长度的二进制全部由1组成。然后再加1,则变成了100…,故一定是2的幂次方。
那么,如何理解initialCapacity |= (initialCapacity >>> 1);
?
虽然通过计算可以看出,上述过程确实将所有位变成了1,但如何理解这种用法呢:
initialCapacity |= (initialCapacity >>> 1)
表示如果你是1,把你的后面一位也置为1。
initialCapacity |= (initialCapacity >>> 2)
表示如果你是1,把你的后面第二位也置为1。
initialCapacity |= (initialCapacity >>> 4)
表示如果你是1,把你的后面第四位也置为1。
…
这次按照上面的思路来看:
1000 0000 0000 0000
>>> 1 : 1100 0000 0000 0000
>>> 2 : 1111 0000 0000 0000
>>> 4 : 1111 1111 0000 0000
...
所以initialCapacity |= (initialCapacity >>> 16)
刚好能把32位都置为1。
② 添加
public boolean add(E e) {
//add直接添加到Last
addLast(e);
return true;
}
public void addLast(E e) {
if (e == null)
throw new NullPointerException();
elements[tail] = e;
if ( (tail = (tail + 1) & (elements.length - 1)) == head)
doubleCapacity();
}
public void addFirst(E e) {
if (e == null)
throw new NullPointerException();
//addFirst添加到
elements[head = (head - 1) & (elements.length - 1)] = e;
if (head == tail)
doubleCapacity();
}
//offer方法,调用add方法
public boolean offer(E e) {
return offerLast(e);
}
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
public boolean offerLast(E e) {
addLast(e);
return true;
}
ArrayDeque是一个首尾相连的循环数组,tail表示尾,head表示头。
tail = (tail + 1) & (elements.length - 1)
,如果 tail + 1 小于 elements.length - 1,那么结果为 tail + 1,因为任意一个数和大于他的全是1的数做&,结果是他本身;如果 tail + 1 大于 elements.length - 1,也就表示当前尾节点超出数组大小,需要从头开始循环了,1000&0111 = 0
,从0开始。
此时,如果head == tail
说明容器满了,需要扩容,调用doubleCapacity()
。
private void doubleCapacity() {
assert head == tail;
//保存头节点(尾节点)位置
int p = head;
//获取旧的数组长度
int n = elements.length;
int r = n - p; // number of elements to the right of p
//二倍扩容
int newCapacity = n << 1;
if (newCapacity < 0)
throw new IllegalStateException("Sorry, deque too big");
Object[] a = new Object[newCapacity];
//拷贝头节点之后的内容
System.arraycopy(elements, p, a, 0, r);
//拷贝头节点之前的内容
System.arraycopy(elements, 0, a, r, p);
elements = a;
head = 0;
tail = n;
}
所以,ArrayDeque是二倍扩容。
③ 查询
get、peek:
public E getFirst() {
@SuppressWarnings("unchecked")
E result = (E) elements[head];
if (result == null)
throw new NoSuchElementException();
return result;
}
public E getLast() {
@SuppressWarnings("unchecked")
E result = (E) elements[(tail - 1) & (elements.length - 1)];
if (result == null)
throw new NoSuchElementException();
return result;
}
public E peek() {
return peekFirst();
}
public E peekFirst() {
// elements[head] is null if deque empty
return (E) elements[head];
}
public E peekLast() {
return (E) elements[(tail - 1) & (elements.length - 1)];
}
get、peek用法基本一致,不过查不到值时,get异常,peek返回null。
poll:
public E poll() {
return pollFirst();
}
public E pollFirst() {
int h = head;
@SuppressWarnings("unchecked")
E result = (E) elements[h];
// Element is null if deque empty
if (result == null)
return null;
elements[h] = null; // Must null out slot
head = (h + 1) & (elements.length - 1);
return result;
}
public E pollLast() {
int t = (tail - 1) & (elements.length - 1);
@SuppressWarnings("unchecked")
E result = (E) elements[t];
if (result == null)
return null;
elements[t] = null;
tail = t;
return result;
}
poll查询到目标值之后,还要将当前位置数据置为null,然后移动指针位置。
4.Set
(1) Set接口
Set接口和Colletion基本一致。
package java.util;
public interface Set<E> extends Collection<E> {
// Query Operations
int size();
boolean isEmpty();
Object[] toArray();
<T> T[] toArray(T[] a);
// Modification Operations
boolean add(E e);
boolean remove(Object o);
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean retainAll(Collection<?> c);
boolean removeAll(Collection<?> c);
void clear();
boolean equals(Object o);
int hashCode();
//此处和Collection接口有区别
Spliterator<E> spliterator() {
return Spliterators.spliterator(this, Spliterator.DISTINCT);
}
}
(2) HashSet
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
static final long serialVersionUID = -5024744406713321676L;
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<>();
}
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
public Iterator<E> iterator() {
return map.keySet().iterator();
}
public int size() {
return map.size();
}
public boolean isEmpty() {
return map.isEmpty();
}
public boolean contains(Object o) {
return map.containsKey(o);
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
public void clear() {
map.clear();
}
}
可以看出,HashSet就是一个HashMap。
通过map.put(e, PRESENT)==null
,让value等于一个固定的PRESENT,通过HashMap保证了数据不会重复。
(3) LinkedHashSet
LinkedHashSet.java:
public LinkedHashSet() {
super(16, .75f, true);
}
HashSet.java:
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
可以看到 LinkedHashSet 是HashSet 的子类,使用LinkedHashMap实现的。
(4) HashSet和LinkedHashSet区别
LinkedHashSet是HashSet的子类。
HashSet 使用 HashMap 实现,LinkedHashSet 使用 LinkedHashMap 实现。
LinkedHashset需要维护元素的插入顺序,所以性能要略低于HashSet,但在迭代访问Set里的全部元素是将有很好的性能。当要操作大量的数据或是需要遍历集合所有的数据时使用LinkedHashSet会比HashSet要好一些。
(5) SortedSet接口及TreeSet
TreeSet 实现了 SortedSet,TreeSet 使用 TreeMap 实现。
public TreeSet() {
this(new TreeMap<E,Object>());
}
5.Map
(1) Map结构
public interface Map<K,V> {
// Query Operations
int size();
boolean isEmpty();
boolean containsKey(Object key);
boolean containsValue(Object value);
V get(Object key);
// Modification Operations
V put(K key, V value);
V remove(Object key);
// Bulk Operations
void putAll(Map<? extends K, ? extends V> m);
void clear();
Set<K> keySet();
Collection<V> values();
Set<Map.Entry<K, V>> entrySet();
interface Entry<K,V> {
K getKey();
V getValue();
V setValue(V value);
boolean equals(Object o);
int hashCode();
public static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K,V>> comparingByKey() {
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> c1.getKey().compareTo(c2.getKey());
}
public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> comparingByValue() {
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> c1.getValue().compareTo(c2.getValue());
}
public static <K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super K> cmp) {
Objects.requireNonNull(cmp);
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> cmp.compare(c1.getKey(), c2.getKey());
}
public static <K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> cmp) {
Objects.requireNonNull(cmp);
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> cmp.compare(c1.getValue(), c2.getValue());
}
}
// Comparison and hashing
boolean equals(Object o);
int hashCode();
default V getOrDefault(Object key, V defaultValue) {
V v;
return (((v = get(key)) != null) || containsKey(key))
? v
: defaultValue;
}
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
Objects.requireNonNull(function);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
// ise thrown from function is not a cme.
v = function.apply(k, v);
try {
entry.setValue(v);
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
}
}
default V putIfAbsent(K key, V value) {
V v = get(key);
if (v == null) {
v = put(key, value);
}
return v;
}
default boolean remove(Object key, Object value) {
Object curValue = get(key);
if (!Objects.equals(curValue, value) ||
(curValue == null && !containsKey(key))) {
return false;
}
remove(key);
return true;
}
default boolean replace(K key, V oldValue, V newValue) {
Object curValue = get(key);
if (!Objects.equals(curValue, oldValue) ||
(curValue == null && !containsKey(key))) {
return false;
}
put(key, newValue);
return true;
}
default V replace(K key, V value) {
V curValue;
if (((curValue = get(key)) != null) || containsKey(key)) {
curValue = put(key, value);
}
return curValue;
}
default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v;
if ((v = get(key)) == null) {
V newValue;
if ((newValue = mappingFunction.apply(key)) != null) {
put(key, newValue);
return newValue;
}
}
return v;
}
default V computeIfPresent(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue;
if ((oldValue = get(key)) != null) {
V newValue = remappingFunction.apply(key, oldValue);
if (newValue != null) {
put(key, newValue);
return newValue;
} else {
remove(key);
return null;
}
} else {
return null;
}
}
default V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue = get(key);
V newValue = remappingFunction.apply(key, oldValue);
if (newValue == null) {
// delete mapping
if (oldValue != null || containsKey(key)) {
// something to remove
remove(key);
return null;
} else {
// nothing to do. Leave things as they were.
return null;
}
} else {
// add or replace old mapping
put(key, newValue);
return newValue;
}
}
default V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
Objects.requireNonNull(value);
V oldValue = get(key);
V newValue = (oldValue == null) ? value :
remappingFunction.apply(oldValue, value);
if(newValue == null) {
remove(key);
} else {
put(key, newValue);
}
return newValue;
}
}
Map接口是一个顶层接口,由一堆Map自身接口方法和一个Entry接口组成,Entry接口定义了主要是关于Key-Value自身的一些操作,Map接口定义的是一些属性和关于属性查找修改的一些接口方法。
(2) HashMap
① 组成和构造方法
Map接口中有一个Entry接口,在HashMap中对其进行了实现,Entry的实现是HashMap存放的数据的类型。
其中Entry在HashMap的实现是Node,Node是一个单链表的结构,TreeNode是其子类,是一个红黑树的类型。
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
HashMap中node的存储方式:
transient Node<K,V>[] table;
说明了该容器中是一个又一个node组成,且hashMap中存放的node的形式既可以是Node也可以是TreeNode。
详细看HashMap的组成:
//是hashMap的最小容量16,容量就是数组的大小也就是变量,transient Node<K,V>[] table。
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
//最大数量,该数组最大值为2^30一次方。
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认的加载因子,如果构造的时候不传则为0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//转化成树的阈值(8)。即链表长度达到8,链表转化为红黑树。
static final int TREEIFY_THRESHOLD = 8;
//当一个反树化的阈值,当这个node长度减少到该值就会从树转化成链表
static final int UNTREEIFY_THRESHOLD = 6;
//满足节点变成树的另一个条件,就是存放node的数组长度要达到64
static final int MIN_TREEIFY_CAPACITY = 64;
//具体存放数据的数组
transient Node<K,V>[] table;
//entrySet,一个存放k-v缓冲区
transient Set<Map.Entry<K,V>> entrySet;
//size是指hashMap中存放了多少个键值对
transient int size;
//对map的修改次数
transient int modCount;
//当前容量
int threshold;
//加载因子
final float loadFactor;
构造方法:
//只有容量,initialCapacity
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
//获取添加map的大小
int s = m.size();
if (s > 0) {
if (table == null) {
//根据当前大小,确定一个容量
float ft = ((float)s / loadFactor) + 1.0F;
int t = ((ft < (float)MAXIMUM_CAPACITY) ?
(int)ft : MAXIMUM_CAPACITY);
if (t > threshold)
//tableSizeFor保证容量是2的幂
threshold = tableSizeFor(t);
}
else if (s > threshold)
//table不为空,且大小超过容量,扩容
resize();
//容器准备完成,逐个填入数据
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
K key = e.getKey();
V value = e.getValue();
putVal(hash(key), key, value, false, evict);
}
}
}
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0) // 容量不能为负数
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
//当容量大于最大值就取最大值
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
//tableSizeFor保证容量是2的幂
this.threshold = tableSizeFor(initialCapacity);
}
//保证容量是2的幂,类似方法我们在ArrayDeque也遇到过,目的同样是让数组首尾相连,2倍扩容时便于重哈希
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
② 添加
put、putVal
//平时我们常用的put方法
public V put(K key, V value) {
//实际时调用了putVal
return putVal(hash(key), key, value, false, true);
}
/* 参数解释:
** (1)hash-当前key的hash值。
** (2)key、value-键值对。
** (3)onlyIfAbsent-默认为false,即在key值相同的时候,用新的value值替换原始值。当使用putIfAbsent时,
** onlyIfAbsent的值为true,不进行替换。
** (4)evict-map初始化的时候是false,其他情况为true
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//当hash到的位置,该位置为null的时候,存放一个新node放入
//p赋值成了table该位置的node值
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//该位置第一个就是查找到的值,将p赋给e
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//如果是红黑树,调用红黑树的putTreeVal方法
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//是链表,遍历,注意e = p.next这个一直将下一节点赋值给e,直到尾部。
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//当链表长度大于等于7,插入第8位,树化
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
③ 查询
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
//先判断表不为空
if ((tab = table) != null && (n = tab.length) > 0 &&
//这一行是找到要查询的Key在table中的位置,table是存放HashMap中每一个Node的数组。
(first = tab[(n - 1) & hash]) != null) {
//Node可能是一个链表或者树,先判断根节点是否是要查询的key,就是根节点,方便后续遍历Node写法并且
//对于只有根节点的Node直接判断
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
//有子节点
if ((e = first.next) != null) {
//红黑树查找
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
//链表查找
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
//遍历链表,当链表后续为null则推出循环
while ((e = e.next) != null);
}
}
return null;
}
(3) HashTable
和HashMap不同,HashTable的实现方式完全不同,这点从二者的类继承关系就可以看出,HashTable和HashMap虽然都实现了Map接口,但是HashTable继承了DIctionary抽象类,而HashMap继承了AbstractMap抽象类。
① Dictionary接口
public abstract
class Dictionary<K,V> {
public Dictionary() {
}
public abstract int size();
public abstract boolean isEmpty();
public abstract Enumeration<K> keys();
public abstract Enumeration<V> elements();
public abstract V get(Object key);
public abstract V put(K key, V value);
public abstract V remove(Object key);
}
HashTabel是不允许Key为null的。
② 组成和构造方法
HashTable
private transient Entry<?,?>[] table;
private transient int count;
private int threshold;
private float loadFactor;
private transient int modCount = 0;
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
Entry
private static class Entry<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Entry<K,V> next;
...
}
Entry是一个单链表,和HashMap中的Node结构相同,但是HashTable中没有TreeNode。
构造方法:
public Hashtable() {
this(11, 0.75f);
}
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
public Hashtable(Map<? extends K, ? extends V> t) {
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}
public Hashtable(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load: "+loadFactor);
if (initialCapacity==0)
initialCapacity = 1;
this.loadFactor = loadFactor;
table = new Entry<?,?>[initialCapacity];
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
② 添加
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
//在数组中的位置 0x7fffffff 是31位二进制1
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
//如果遍历链表找到了则替换旧值并返回
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
先hash求索引,遍历该索引Entry链表,如果找到hash值和key都和put的key一样的时候就替换旧值,否则使用addEntry方法添加新值进入table,因为添加新元素就涉及到修改元素大小,还可能需要扩容等,具体看下边的addEntry方法可知。
private void addEntry(int hash, K key, V value, int index) {
Entry<?,?> tab[] = table;
//如果扩容需要重新计算hash,所以index和table都会被修改
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index];
//插入新元素
tab[index] = new Entry<>(hash, key, value, e);
count++;
modCount++;
}
③ 查询
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}
return null;
}
可以看到HashTable中,主要操作都是使用synchronized修饰的,线程安全,但是效率不高。
(4) LinkedHashMap
超详细LinkedHashMap解析_求offer的菜鸡的博客-CSDN博客_linkedhashmap
LinkedHashMap继承自HashMap,操作都是建立在HashMap的基础上。不同的是,LinkedHashMap维护了一个Entry的双向链表,保证了插入的Entry中的顺序。这也是Linked的含义。结构图如下:

加入插入顺序为key1,key2,key3,key4,那么就会维护一个红线所示的双向链表。
//继承自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);
}
}
还记得HashMap中的三个空方法吗?在LinkedHashMap中他们有了具体的内容。
HashMap.java
// Callbacks to allow LinkedHashMap post-actions
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }
LinkedHashMap.java
//在节点删除后,维护链表,传入删除的节点
void afterNodeRemoval(Node<K,V> e) { // unlink
//p指向待删除元素,b执行前驱,a执行后驱
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
//这里执行双向链表删除p节点操作,很简单。
p.before = p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a == null)
tail = b;
else
a.before = b;
}
//在节点被访问后根据accessOrder判断是否需要调整链表顺序
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
//如果accessOrder为false,什么都不做
if (accessOrder && (last = tail) != e) {
//p指向待删除元素,b执行前驱,a执行后驱
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
//这里执行双向链表删除操作
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
//这里执行将p放到尾部
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
//保证并发读安全。
++modCount;
}
}
void afterNodeInsertion(boolean evict) {
LinkedHashMap.Entry<K,V> first;
//removeEldestEntry(first)默认返回false,所以afterNodeInsertion这个方法其实并不会执行
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
简单来说,LinkedHashMap就是在常规HashMap的基础上,添加了头尾指针,维护了key的插入顺序。具体是用afterNodeAccess、afterNodeInsertion、afterNodeRemoval等方法在常规的put、remove过程中,对链表进行维护。
(5) TreeMap
TreeMap继承了NavigableMap接口,NavigableMap接口继承了SortedMap接口。
TreeMap通过红黑树实现, 红黑树结构支持排序,默认情况下通过Key值的自然顺序进行排序。
直接看TreeMap的Entry,可以看出使用红黑树存储kv。
static final class Entry<K,V> implements Map.Entry<K,V> {
//key,val是存储的原始数据
K key;
V value;
//定义了节点的左孩子
Entry<K,V> left;
//定义了节点的右孩子
Entry<K,V> right;
//通过该节点可以反过来往上找到自己的父亲
Entry<K,V> parent;
//默认情况下为黑色节点,可调整
boolean color = BLACK;
...
}
(6) ConcurrentHashMap
ConcurrentHashMap实现原理及源码分析_快乐小石头的博客-CSDN博客_concurrenthashmap原理
HashMap不是线程安全的,HashTable线程安全,但每次操作会锁住整个结构,效率很低。
ConcurrentHashMap采用分段式锁,每次只锁一部分Entry。在1.7中分为16个segment,Entry分布在segment中,所以支持16的并发度;1.8后,对每个table数组加锁,一个Entry一个锁。
整体操作和HashMap一致,这里重点看看枷锁的方法:
putVal:
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
//如果此处没有元素
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//通过CAS的方式添加
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
//如果检测到某个节点的hash值是MOVED,表示正在扩容,参与扩容。
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
//不是上述情况,锁住头节点,开始插入
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {//fh>=0说明是链表,红黑树的fh等于-2
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
//key相同,替换掉value
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
//创建新Node,插入
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
//红黑树插入
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
//直接使用红黑树的putTreeVal插入
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
//当同一个节点的元素大于等于TREEIFY_THRESHOLD(8),转为红黑树
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);//计数,扩容!
return null;
}
在addCount方法中会进行判断,如果容量不够,调用transfer方法扩容:
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
int n = tab.length, stride;
// 每核处理的量小于16,则强制赋值16
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE; // subdivide range
if (nextTab == null) { // initiating
try {
@SuppressWarnings("unchecked")
//构建一个nextTable对象,其容量为原来容量的两倍
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
nextTab = nt;
} catch (Throwable ex) { // try to cope with OOME
sizeCtl = Integer.MAX_VALUE;
return;
}
nextTable = nextTab;
transferIndex = n;
}
int nextn = nextTab.length;
// 连接点指针,用于标志位(fwd的hash值为-1,fwd.nextTable=nextTab)
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
// 当advance == true时,表明该节点已经处理过了
boolean advance = true;
boolean finishing = false; // to ensure sweep before committing nextTab
for (int i = 0, bound = 0;;) {
Node<K,V> f; int fh;
// 控制 --i ,遍历原hash表中的节点
while (advance) {
int nextIndex, nextBound;
if (--i >= bound || finishing)
advance = false;
else if ((nextIndex = transferIndex) <= 0) {
i = -1;
advance = false;
}
// 用CAS计算得到的transferIndex
else if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ?
nextIndex - stride : 0))) {
bound = nextBound;
i = nextIndex - 1;
advance = false;
}
}
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
// 已经完成所有节点复制了
if (finishing) {
nextTable = null;
table = nextTab; // table 指向nextTable
sizeCtl = (n << 1) - (n >>> 1); // sizeCtl阈值为原来的1.5倍
return; // 跳出死循环,
}
// CAS 更扩容阈值,在这里面sizectl值减一,说明新加入一个线程参与到扩容操作
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
return;
finishing = advance = true;
i = n; // recheck before commit
}
}
// 遍历的节点为null,则放入到ForwardingNode 指针节点
else if ((f = tabAt(tab, i)) == null)
advance = casTabAt(tab, i, null, fwd);
// f.hash == -1 表示遍历到了ForwardingNode节点,意味着该节点已经处理过了
// 这里是控制并发扩容的核心
else if ((fh = f.hash) == MOVED)
advance = true; // already processed
else {
// 节点加锁
synchronized (f) {
// 节点复制工作
if (tabAt(tab, i) == f) {
Node<K,V> ln, hn;
// fh >= 0 ,表示为链表节点
if (fh >= 0) {
// 构造两个链表 一个是原链表 另一个是原链表的反序排列
int runBit = fh & n;
Node<K,V> lastRun = f;
for (Node<K,V> p = f.next; p != null; p = p.next) {
int b = p.hash & n;
if (b != runBit) {
runBit = b;
lastRun = p;
}
}
if (runBit == 0) {
ln = lastRun;
hn = null;
}
else {
hn = lastRun;
ln = null;
}
for (Node<K,V> p = f; p != lastRun; p = p.next) {
int ph = p.hash; K pk = p.key; V pv = p.val;
if ((ph & n) == 0)
ln = new Node<K,V>(ph, pk, pv, ln);
else
hn = new Node<K,V>(ph, pk, pv, hn);
}
// 在nextTable i 位置处插上链表
setTabAt(nextTab, i, ln);
// 在nextTable i + n 位置处插上链表
setTabAt(nextTab, i + n, hn);
// 在table i 位置处插上ForwardingNode 表示该节点已经处理过了
setTabAt(tab, i, fwd);
// advance = true 可以执行--i动作,遍历节点
advance = true;
}
// 如果是TreeBin,则按照红黑树进行处理,处理逻辑与上面一致
else if (f instanceof TreeBin) {
TreeBin<K,V> t = (TreeBin<K,V>)f;
TreeNode<K,V> lo = null, loTail = null;
TreeNode<K,V> hi = null, hiTail = null;
int lc = 0, hc = 0;
for (Node<K,V> e = t.first; e != null; e = e.next) {
int h = e.hash;
TreeNode<K,V> p = new TreeNode<K,V>
(h, e.key, e.val, null, null);
if ((h & n) == 0) {
if ((p.prev = loTail) == null)
lo = p;
else
loTail.next = p;
loTail = p;
++lc;
}
else {
if ((p.prev = hiTail) == null)
hi = p;
else
hiTail.next = p;
hiTail = p;
++hc;
}
}
// 扩容后树节点个数若<=6,将树转链表
ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
(hc != 0) ? new TreeBin<K,V>(lo) : t;
hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
(lc != 0) ? new TreeBin<K,V>(hi) : t;
setTabAt(nextTab, i, ln);
setTabAt(nextTab, i + n, hn);
setTabAt(tab, i, fwd);
advance = true;
}
}
}
}
}
}
get:
get方法就很简单了,计算hashCode,如果是头节点直接返回,如过不是遍历链表或查询红黑树即可。
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
int h = spread(key.hashCode());
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
6.补充
(1) AbstractXxx
AbstractXxx是一个抽象类,它是接口的一个骨架实现,最小化实现了此接口提供的抽象函数。
在Collection框架以及Map框架中基本都遵循了这一规定(为了图片简介,并没有画出),骨架实现在接口与实现类之间构建了一层抽象,复用一些比较通用的函数以及方便扩展,例如Map接口拥有骨架实现AbstractMap,Collection接口拥有骨架实现AbstractCollection,List接口拥有骨架实现AbstractList、Set接口拥有骨架实现AbstractSet等。
值得注意的是Queue接口并没有AbstractQueue。
7.总结
问:Java有哪些集合,谈谈你的理解?
首先,集合可以分为两类,一类是Collection接口下的集合,另一类是Map接口下的集合。
先从Collection说起:
(1) Collection整体架构
最上层接口为iterable接口,它里面只有一个接口方法iterator迭代器。也就是说,实现了iterable接口的类都可以使用迭代器。
在下一层Collection接口实现了iterable接口,在Collection里定义了很多集合常用方法,比如add()、remove()、size()、isEmpty()。
在他之下就是我们常见的List、Queue、Set接口了。
这里还有一个抽象类AbstractCollection,用于实现一些通用的方法。在集合中,经常能看到这种抽象类,用于骨架接口和他的实现类之间,比如说AbstractList、AbstractSet、AbstractMap。
(2) List
List接口下有ArrayList、LinkedList、Vector、Stack四个实现类。
① ArrayList
ArrayList中有一个Object数组用于储ArrayList中的元素,也说明ArrayList是基于数组实现的。
这里有一个值得注意的地方,数组是用transient修饰的,他表示不参与序列化。当集合需要序列化时,会遍历集合中的所有元素,逐个进行序列化,序列化结束后,会有一个modCount变量,用来记录集合修改次数。如果序列化前后modCount一致,才继续下一步。Java的集合都是用这种方式保证序列化线程安全的。
ArrayList每次添加元素时,都会调用一个确定容量是否够用的方法ensureCapacityInternal(),用新加元素+已有元素和容量进行比较,如果容量不足,会进行扩容。
ArrayList使用1.5倍扩容,也就是oldCapacity + (oldCapacity >> 1)
,扩容后还不够,直接开新的大小。
容量最大为ArrayList中的一个默认值,它的值是Integer.MAX_VALUE-8。因为在很多JVM中,会在数组的前8个容量中存储数组长度,所以,这就是他的最大值。但是,当添加元素已经大于最大值,小于Integer时,还是会给ArrayList开足够的容量,因为有的JVM不需要存长度。
② LinkedList
与ArrayList不同,LinkedList中没有数组,而是有一个头节点、一个尾节点。还有一个包含值、前置节点,后置节点的Node,所以LinkedList是用链表实现的。
所以他的增删改查就是针对链表的一系列操作,也不会有扩容的问题。
值得注意的是,LinkedList在进行查询操作时,会用index和size>>1比较,判断目标离哪头更近,于是从哪头遍历。
③ Vector和Stack
Vector和Stack都用synchronized 修饰,线程安全,但性能较低,所以现在很少是用。
Vector类似ArrayList,是用数组实现的,两倍扩容。
Stack是Vector的子类,添加了push、pop这些栈操作。
(3) Queue
Queue有一个实现类,优先级队列PriorityQueue。还有一个接口Deque,Deque的实现类是ArrayDeque。
① PriorityQueue
优先级队列默认容量是11,他也是用数组实现的。最大的特点是包含了一个比较器,每次添加元素,或取出元素后,都会进行一次排序。
默认的比较器,使用的是堆排序,保证了堆顶一直是最小元素。
② ArrayDeque
就是一个双向队列,用数组实现,同时保存了头和尾的位置。
ArrayDeque的容量必须是2的幂,每次添加可以用索引位置和容量-1做&操作,如果索引小于容量-1,结果就是索引,如果大于,结果就回到0。当head==taill,扩容。ArrayDeque就是用这种方式实现了循环数组。
所以,在扩容时,也一定要保证容量必须是2的幂。通过容量和容量右移1位、2位、4位一直到16位分别做与操作实现。
(4) Set
Set接口基本与Collection接口一致,有两个实现类HashSet和LinkedHashSet,一个接口SortedSet,他的实现类是TreeSet。Set的接口中类底层基本都是Map中的实现类。
HashSet是用HashMap实现的,他的value固定为一个空的PRESENT类。
LinkedHashSet是用LinkedHashMap实现的。
TreeSet实现了SortedSet接口,使用 TreeMap 实现。
(5) Map
Map接口下有HashMap、HashTabe、LinkedHashMap、TreeMap、ConcurrentHashMap几个比较常用的实现类。
Map接口是一个顶层接口,除了自身接口方法外,还有一个Entry接口组成,Entry接口定义了关于Key-Value自身的一些操作。
① HashMap
在java7及之前,就是一个数组,每个位置保存链表头,进行存储。
每个hashmap允许一键为null,多个值为null。
每个链表头存的是一个hash值,首先用该对象的hashcode得出一个hash值,然后对数组长度取余,找到他所在的位置,如果是空的,创建链表,头插法。
java8之后,进行了优化,最大不同是使用了红黑树,链表长度大于8就转化为红黑树。同时改成了尾插法。
扩容机制:
当容量达到0.75进行扩容,进行重哈希,2倍扩容。就是从111到1111到11111扩容,与hash值进行&操作时,大多数位置是不变的。
② HashTable
HashTable只使用了链表,没有使用红黑树。且方法用synchronized修饰,线程安全,但效率很低,很少使用。
③ LinkedHashMap
LinkedHashMap就是在常规HashMap的基础上,添加了头尾指针,维护了key的插入顺序。在添加、修改操作后,会调用方法,对链表进行维护。
这些方法,在HashMap中也存在,但是都是空方法,用于扩展。在LinkedHashMap中进行了使用。
④ TreeMap
通过红黑树实现,是数据自然排序。
⑤ ConcurrentHashMap
java7及之前,使用默认16个segment,每个segment下有多个链表,与HashMap类似,只是在每个segment上加锁,所以最大并发量16。
java8以后取消了segment,直接给链表头加锁。并且采用了CAS+synchronized方式加锁。
存:
求出hash值,没有使用CAS的方式创建链表,如果有就synchronized锁住进行尾插。
如果在扩容,就加入一起扩容。
取:
类似HashMap,直接遍历取值即可。因为涉及的共享变量用volatile修饰,所以取到的值一定是新的。