集合源码学习笔记

集合源码学习笔记

参考博客: 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);
    }
}
img

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修饰,所以取到的值一定是新的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值