• [Java] 集合框架原理之一:基本结构与源码分析


    一、Collection

    Collection 接口定义了一些基本的方法:

    • int size(); 
    • boolean isEmpty(); 
    • boolean add(E e);
    • boolean addAll(Collection<? extend E> c);
    • boolean remove(Object o); 
    • boolean removeAll(Collection<?> c);
    • boolean removeIf(Predicate<? super E> filter) // 移除满足给定条件的元素
    • boolean retainAll(Collection<?> c); // 移除不在集合 c 中的所有元素
    • boolean contains(Object o);
    • boolean containsAll(Collection<?> c);
    • boolean equals(Object o);
    • int hashCode();
    • Iterator<E> iterator();
    • Object[] toArray();
    • T[] toArray(T[]);  //返回任意类型的数组。如果传入数组的长度小于元素长度,则创建一个长度等于元素数量的新数组,并返回;反之传入数组长度足够,则将元素拷贝到所传入的数组中,并返回。
    • void clear();
    • Spliterator<E> spliterator(); // 并行遍历元素,
    • Stream<E> stream(); // 将集合转换成流,
    • Stream<E> parallelStream(); 

    上面我们提到的 Spliterator 是一个并行遍历的迭代器(分成多份交给不同线程),而 Iterator 是一个顺序遍历迭代器,Spliterator 接口的基本方法如下:

    • boolean tryAdvance(Consumer<? super T> action); // 给定一个执行动作,若还有剩余元素返回 true
    • default void forEachRemaining(Consumer<? super T> action) // 循环调用了上述 tryAdvance 方法
    • Spliterator<T> trySplit(); // 拆分当前要遍历的元素,返回一个新的 Spliterator 迭代器
    • long estimateSize(); // 估算还有多少元素要遍历
    • int characteristics(); //返回当前对象有哪些特征值
    • default boolean hasCharacteristics(int characteristics) //是否具有当前特征值
    • default long getExactSizeIfKnown() // 当迭代器拥有 SIZED 特征时,返回剩余元素个数;否则返回-1
    • default Comparator<? super T> getComparator() //如果 Spliterator 的 list 是通过 Comparator 排序的,则返回Comparator; 如果 Spliterator 的 list 是自然排序的则返回 null

    AbstractCollection 抽象类实现了 Collection 接口的部分方法,但是这些方法是基于 Iterator() 方法实现的,而 Iterator() 是由具体的实现类进行实现的,下面我们来看一下 Collection 的实现体系。

    1 Queue 与 Deque

    Queue 接口也是继承自 Collection 接口,增加了一些“队列”的操作方法。以下是 Queue 接口中的方法:

    • boolean add(E e); // 将元素 e 放入队列,如果队列空间不足则抛出异常
    • boolean offer(E e); // 将元素 e 放入队列
    • E remove(); // 获取队首元素,并移除。与 poll() 的区别是,如果队列为空则抛异常
    • E poll(); // 获取队首元素,并移除。队列为空则返回 null
    • E element(); // 获取队首元素,但不移除。与 peek() 的区别是,如果队列为空则抛异常
    • E peek(); // 获取队首元素,但不移除。队列为空则返回 null

    Deque 接口继承自 Queue 接口,增加了一些“双端队列”和“栈”的操作方法,Deque 接口中相对 Queue 接口增加了如下方法:

    • 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); // 删除第一个 o 元素
    • boolean removeLastOccurrence(Object o); // 删除最后一个 o 元素
    • void push(E e); // 模拟栈的操作,将一个元素压入栈中
    • E pop(); // 模拟栈的操作,弹出栈顶元素

    1.1 PriorityQueue

    PriorityQueue 是一种特殊的队列,每次出队的权值都是队列中最小的,因此可以使用它来排序,但是它只保证出队的元素有序的,不保证使用迭代器遍历的元素是有序的。权值大小的评判可以由元素本身,也可以由传入比较器来判断。PriorityQueue 之所以每次都能取出最小的权值,是利用 transient Object[] queue 数组(默认长度11)实现了小根堆,最小(大)堆是一棵完全二叉树,且父节点要小于(大于)子节点,每个节点的左孩子位置为2*i,右孩子为2*i+1。

    我们来看一下向队列中插入 {5, 3, 4, 2, 1, 6} 这个序列时,堆的变化(以下操作过程是在数据结构可视化网址中进行的)

    插入元素 5 到数组的第一个位置,如下图

    插入元素 3 到数组第二个位置,然后与父节点(当前节点位置i,父节点位置i/2)比较大小,小于父节点则交换位置,如下图

     

    插入元素 4 到数组第三个位置,然后与父节点比较大小......如下图

    插入元素 2 到数组第四个位置,然后......与5交换.....与3交换......如下图

    插入元素 1 到数组第五个位置,然后......与3交换.....与2交换......如下图

    插入元素 6 到数组第六个位置......如下图

    1) 入队操作

        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)
                queue[0] = e;
            else
                // 插入元素的实际操作,i 表示插入的是第几个元素
                siftUp(i, e);
            return true;
        }    
        private void siftUp(int k, E x) {
            // 判断是否有比较器可用
            if (comparator != null)
                siftUpUsingComparator(k, x);
            else   
                siftUpComparable(k, x);
        }        
        // 使用默认方式来插入节点
        private void siftUpComparable(int k, E x) {
            Comparable<? super E> key = (Comparable<? super E>) x;
            while (k > 0) {
                // 找到父节点
                int parent = (k - 1) >>> 1;
                Object e = queue[parent];
                // 如果插入节点较大,则无需处理,否则与父节点交换位置
                if (key.compareTo((E) e) >= 0)
                    break;
                // 交换父子节点位置,继续判断插入节点位置是否合适
                queue[k] = e;
                k = parent;
            }
            // 确定好插入节点位置,并放入
            queue[k] = key;
        }
        // 使用比较器来插入节点,同上只是比较方式不同
        private void siftUpUsingComparator(int k, E x) {
            while (k > 0) {
                int parent = (k - 1) >>> 1;
                Object e = queue[parent];
                if (comparator.compare(x, (E) e) >= 0)
                    break;
                queue[k] = e;
                k = parent;
            }
            queue[k] = x;
        }
    View Code

    2) 出队操作

        // 这个方法在 AbstractQueue 中实现的
        public E remove() {
            E x = poll();
            if (x != null)
                return x;
            else
                throw new NoSuchElementException();
        }
        public E poll() {
            if (size == 0)
                return null;
            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) {
            Comparable<? super E> key = (Comparable<? super E>)x;
            // 非叶子结点元素的最大位置
            int half = size >>> 1;        // loop while a non-leaf
            // 如果不是叶子结点,继续循环调整
            while (k < half) {
                // 得到k位置节点的左孩子,假设左孩子比右孩子小
                int child = (k << 1) + 1; // assume left child is least
                // 获取左孩子的值
                Object c = queue[child];
                // 获取右孩子位置
                int right = child + 1;
                // 获取左右孩子中较小的一个
                if (right < size &&
                    ((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;
        }
        // 使用比较器的方式调整堆,同上只是比较方式不同
        private void siftDownUsingComparator(int k, E x) {
            int half = size >>> 1;
            while (k < half) {
                int child = (k << 1) + 1;
                Object c = queue[child];
                int right = child + 1;
                if (right < size &&
                    comparator.compare((E) c, (E) queue[right]) > 0)
                    c = queue[child = right];
                if (comparator.compare(x, (E) c) <= 0)
                    break;
                queue[k] = c;
                k = child;
            }
            queue[k] = x;
        }
    View Code

    1.2 ArrayDeque

    ArrayDeque 利用 transient Object[] elements 数组实现的双端队列,默认长度为16,最小长度8。当有元素进队或出队时,数组中的元素不会移动,发生变化的只是 head 和 tail 两个属性。进队时就根据 tail 所指示将其放到队尾,出队时就将 head 所指示的元素出队,当 tail 追上 head 时,将数组容量扩大一倍。

    1) 分配数组大小

        // 初始化数组空间
        private void allocateElements(int numElements) {
            int initialCapacity = MIN_INITIAL_CAPACITY;
            // 指定长度计算数组大小,始终为2的n次方
            // 当指定值为1-7时,数组大小为8
            // 当指定值为8-15时,数组大小为16
            // 当指定值为16-31时,数组大小为32
            if (numElements >= initialCapacity) {
                initialCapacity = numElements;
                initialCapacity |= (initialCapacity >>>  1);
                initialCapacity |= (initialCapacity >>>  2);
                initialCapacity |= (initialCapacity >>>  4);
                initialCapacity |= (initialCapacity >>>  8);
                initialCapacity |= (initialCapacity >>> 16);
                initialCapacity++;
                // 如果值太大溢出,需要缩小2倍
                if (initialCapacity < 0)   // Too many elements, must back off
                    initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements
            }
            elements = new Object[initialCapacity];
        }
        // 扩大数组空间
        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;
        }
    View Code

    2) 判断大小

        public int size() {
            // 若 elements.length=16, tail=2, head=1,则 tail - head = -11
            // 换算二进制   11111111111111111111111111110101
            // 进行与运算 & 00000000000000000000000000001111
            // 得实际长度 = 00000000000000000000000000000101 即得到长度为 5
            return (tail - head) & (elements.length - 1);
        }
    View Code

    3) 入队操作

        public void addFirst(E e) {
            if (e == null)
                throw new NullPointerException();
            elements[head = (head - 1) & (elements.length - 1)] = e;
            if (head == tail)
                doubleCapacity();
        }
        public void addLast(E e) {
            if (e == null)
                throw new NullPointerException();
            elements[tail] = e;
            if ( (tail = (tail + 1) & (elements.length - 1)) == head)
                doubleCapacity();
        }
    View Code

    4) 出队操作

        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;
        } 
    View Code

    2 List

    List 接口继承于 Collection 接口,是有序可重复集合,可以精确控制元素的插入、删除。以下是与 Collection 接口相比增加的方法:

    • void replaceAll(UnaryOperator<E> operator); // 根据 operator 进行替换,待研究
    • void sort(Comparator<? super E> c); // 根据 Comparator 指定规则进行排序
    • void add(int index, E e); // 精确的在 index 位置上插入元素 e,其后元素后移
    • boolean addAll(int, Collection<? extend E>); // 将集合插入到 index 位置,其后元素后移
    • E get(int index);
    • E set(int index, E e); // 用 e 替换在 index 位置上的原有元素,并将其返回
    • E remove(int index);
    • int indexOf(Object o); // 从前往后遍历查找
    • int lastIndexOf(Object o); // 从后往前遍历查找
    • ListIterator<E> listIterator();
    • ListIterator<E> listIterator(int index); // 从指定位置返回 ListIterator
    • List<E> subList(int fromIndex, int toIndex);

    AbstractList 抽象类实现了 List 接口的部分方法,这些方法都是基于 listIterator() 方法遍历实现的,如 indexOf() 和 lastIndexOf() 分别是向后和向前遍历比较;clear() 是遍历移除;equals() 是先判断是否相等,然后遍历比较。

    在 AbstractList 中还有两个内部类( listIterator() 方法就是利用其中一个内部类 ListItr 的实例实现的):

    1. Itr (implements Iterator<E>),有基本的迭代方法 hasNext()、next()、remove(),其中 next() 是利用 get() 实现,get() 需要被其子类实现才可用;remove() 是利用 AbstractList.this.remove() 方法,remove() 同样需要被子类实现才可用。
    2. ListItr (extends Itr implements ListIterator<E>),继承自 Itr,增加了向前迭代方法和 add()、set()方法,方式与 Itr 类似,其实现仍依赖于实现类。

    以上是 List 接口和 AbstractList 抽象类的内容,下面我们来看一下 List 的实现类

    2.1 ArrayList

    ArrayList 利用一个数组(transient Object[] elementData)实现的,初始化大小为 10。ArrayList 的部分方法如下:

    1) 添加元素

        public boolean add(E e) {
            // 根据所需大小计算
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }
        public void add(int index, E element) {
            // 判断 index 是否合法
            rangeCheckForAdd(index);
            // 根据所需大小计算
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            // 调整元素位置,将要插入的位置空出
            System.arraycopy(elementData, index, elementData, index + 1, size - index);
            elementData[index] = element;
            size++;
        }
        private void ensureCapacityInternal(int minCapacity) {
            // 如果数组为空,取默认大小和所需大小两者中较大的值
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
            }
            ensureExplicitCapacity(minCapacity);
        }
        private void ensureExplicitCapacity(int minCapacity) {
            // 修改次数+1
            modCount++;
            // 判断是否需要扩充数组
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
        private void grow(int minCapacity) {
            // overflow-conscious code
            int oldCapacity = elementData.length;
            // 将数组容量扩大1.5倍
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            // 若仍然不足,则按最需求容量
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            // 如果超出最大限制
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            // 扩充数组
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
        private static int hugeCapacity(int minCapacity) {
            // 小于0表示整型溢出
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
        }
    View Code

    2) 移除元素

        // 移除某个位置上的元素
        public E remove(int index) {
            // 检查删除位置是否合法
            rangeCheck(index);
            // 修改次数+1
            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;
        }
        // 移除某个元素
        public boolean remove(Object o) {
            if (o == null) {
                // 从头开始遍历查找
                for (int index = 0; index < size; index++)
                    if (elementData[index] == null) {
                        // 移除操作
                        fastRemove(index);
                        return true;
                    }
            } else {
                for (int index = 0; index < size; index++)
                    if (o.equals(elementData[index])) {
                        fastRemove(index);
                        return true;
                    }
            }
            return false;
        }
        private void fastRemove(int index) {
            modCount++;
            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
        }
    View Code

     在 ArrayList 中的几个内部类:

    1. Itr (implements Iterator<E>),Itr 类优化了 AbstractList.Itr 类,不再依赖于 get() 方法,而是直接操作 ArrayList 内部的数组来实现迭代功能。
    2. ListItr (extends Itr implements ListIterator<E>),ListItr 类优化了 AbstractList.ListItr 类,同样也不在依赖于 get() 方法,直接操作 ArrayList 内部的数组来实现。
    3. SubList (extends AbstractList<E> implements RandomAccess)
    4. static ArrayListSpliterator (implements Spliterator<E>)

    2.2 LinkedList

    LinkedList 利用一个双向链表实现的。LinkedList 除了实现 List 接口外,还实现了 Deque 接口,所以 LinkedList 也包含一些“队列”、“双端队列”和“栈”的方法,LinkedList 部分方法如下:

    1) 添加元素、进队、进栈

        // 元素入栈
        public void push(E e) {
            addFirst(e);
        }
        // 元素入队
        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;
        }
        // 添加元素
        public boolean add(E e) {
            linkLast(e);
            return true;
        }
        // 添加元素到头部
        public void addFirst(E e) {
            linkFirst(e);
        }
        // 添加元素到尾部
        public void addLast(E e) {
            linkLast(e);
        }
        private void linkFirst(E e) {
            // 头节点
            final Node<E> f = first;
            // 创建新节点
            final Node<E> newNode = new Node<>(null, e, f);
            // 将新节点设为头节点
            first = newNode;
            if (f == null)
                last = newNode;
            else
                f.prev = newNode;
            size++;
            modCount++;
        }
        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++;
        }
        // 添加集合c 
        public boolean addAll(Collection<? extends E> c) {
            return addAll(size, c);
        }
        // 添加集合c到指定位置
        public boolean addAll(int index, Collection<? extends E> c) {
            checkPositionIndex(index);
            Object[] a = c.toArray();
            int numNew = a.length;
            if (numNew == 0)
                return false;
            Node<E> pred, succ;
            if (index == size) {
                succ = null;
                pred = last;
            } else {
                // 找到第index节点
                succ = node(index);
                pred = succ.prev;
            }
            // 遍历集合,将其插入链表
            for (Object o : a) {
                @SuppressWarnings("unchecked") E e = (E) o;
                Node<E> newNode = new Node<>(pred, e, null);
                if (pred == null)
                    first = newNode;
                else
                    pred.next = newNode;
                pred = newNode;
            }
            if (succ == null) {
                last = pred;
            } else {
                pred.next = succ;
                succ.prev = pred;
            }
            size += numNew;
            modCount++;
            return true;
        }
        // 查找第 index 个节点
        Node<E> node(int index) {
            // assert isElementIndex(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;
            }
        }
    View Code

    在 LinkedList 中的几个内部类:

    1. ListItr (extends Itr implements ListIterator<E>),ListItr 类优化了 AbstractList.ListItr 类,迭代不再依赖于 get() 方法,而是直接遍历节点。
    2. static Node<E> ,链表节点的数据结构。
    3. DescendingIterator (implements Iterator<E>)
    4. static LLSpliterator (implements Spliterator<E>)

    2.3 Vector

    Vector 在 Java0 时就已经出现,和 ArrayList 的区别是 Vector 是线程安全的(方法中全都有 synchronized 关键字)。在 Java2 之后 Vector也实现自 List 接口,所以两者的用法和内部的实现方式基本一样,另外 Vector 还保留了原有的元素操作方法,如 setElementAt(E, int)、insertElement(E, int)、addElement(E)、removeElement(Object)等。Vector 还提供了中 Enumeration 元素遍历方式,与 Iterator 类似,只不过 Iterator 多了遍历是删除元素的方法。另外当空间不足时 Vector 会增加100%,ArrayList 只会增加50%+1。

    由于 Vector 和 ArrayList 类似且不常用,就不做讨论。

    2.4 Stack

    Stack 是继承自 Vector,且也是在 Java0 时就已经存在。Stack 是模拟了栈的操作,在 Vector 基础上扩展了 5 个方法:push(E)、pop()、peek()、empty()、search(Object)。在使用上应该优先使用 Dueue,这里就不对 Stack 做讨论

    3 Set

    Set 接口继承于 Collection 接口,在 Set 接口中并没有增加任何方法。

    3.1 TreeSet

    TreeSet 的实现完全依赖于 TreeMap,其内部定义了一个 transient NavigableMap<E,Object> m,但实际上在构造方法中是创建的 TreeMap对象,我们来看一下它的方法

    1) 构造方法

        TreeSet(NavigableMap<E,Object> m) {
            this.m = m;
        }
        public TreeSet() {
            this(new TreeMap<E,Object>());
        }
        public TreeSet(Comparator<? super E> comparator) {
            this(new TreeMap<>(comparator));
        }
        public TreeSet(Collection<? extends E> c) {
            this();
            addAll(c);
        }
        public TreeSet(SortedSet<E> s) {
            this(s.comparator());
            addAll(s);
        }
    View Code

    2) 添加元素

        public boolean add(E e) {
            return m.put(e, PRESENT)==null;
        }
        public  boolean addAll(Collection<? extends E> c) {
            // Use linear-time version if applicable
            if (m.size()==0 && c.size() > 0 &&
                c instanceof SortedSet &&
                m instanceof TreeMap) {
                SortedSet<? extends E> set = (SortedSet<? extends E>) c;
                TreeMap<E,Object> map = (TreeMap<E, Object>) m;
                Comparator<?> cc = set.comparator();
                Comparator<? super E> mc = map.comparator();
                if (cc==mc || (cc != null && cc.equals(mc))) {
                    map.addAllForTreeSet(set, PRESENT);
                    return true;
                }
            }
            return super.addAll(c);
        }
    View Code

    3.2 HashSet 

    HashSet 的实现完全依赖于 HashMap,其内部定义了一个 transient HashMap<E,Object> map,利用了 HashMap 的 key 来保存元素,我们来看一下它的方法

    1) 构造方法

        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);
        }
    View Code

    2) 添加元素

        public boolean add(E e) {
            return map.put(e, PRESENT)==null;
        }
    View Code

    3) 移除元素

        public boolean remove(Object o) {
            return map.remove(o)==PRESENT;
        }
    View Code

    4) 迭代元素

        public Iterator<E> iterator() {
            return map.keySet().iterator();
        }
    View Code

    二、Map

    以下是 Map 接口中定义的一些基本的方法:

    • int size();
    • boolean isEmpty();
    • boolean containsKey(Object key);
    • boolean containsValue(Object value);
    • V get(Object key);
    • V put(K key, V value);
    • V remove(Object key);
    • void putAll(Map<? extends K, ? extends V> m); // 添加 m 中的所有元素
    • void clear();
    • Set<K> keySet(); // 性能不如 entrySet()
    • Collection<V> values();
    • Set<Map.Entry<K, V>> entrySet(); // 可理解为将 Map 放在了 Set 中,这样就可以通过 Iterator 进行遍历,效率要高于 keySet()。
    • boolean equals(Object o);
    • int hashCode();
    • default V getOrDefault(Object key, V defaultValue) // key 的值存在则将其返回,否则返回 defaultValue。
    • default void forEach(BiConsumer action) // 遍历 Map 的每个元素时调用 action.accept(k, v) 方法执行该操作。
    • default void replaceAll(BiFunction function) // 遍历 Map 的每个元素时调用 function.apply(k, v) 更新 value。
    • default V putIfAbsent(K key, V value) // key 的值为空则将其设置为 value 并返回空,否则返回其值。
    • default boolean remove(Object key, Object value) // 删除能同时匹配 key 和 value 的元素。
    • default boolean replace(K key, V oldValue, V newValue) // 替换能同时匹配 key 和 oldValue 的元素的值为 newValue。
    • default V replace(K key, V value) // key 的值不为空,才将其替换。
    • default V computeIfAbsent(K key, Function mapping) // key 的值为空,则 mapping.apply(key) 计算新值。
    • default V computeIfPresent(K key,  BiFunction remapping) // key 的值不空,则 remapping.apply(key,old) 计算新值。
    • default V compute(K key, BiFunction remapping) // 利用 remapping.apply(key,oldValue) 计算新值。
    • default V merge(K key, V value, BiFunction remapping) // key 的值为空则赋为 value,否则与 value 计算确定新值。

    另外在 Map 接口的内部还定义了一个 Map.Entry<K,V> 接口,它表示 Map 中的一个实体(一个key-value对),在这个接口内有一些基本的操作方法:

    • K getKey();
    • V getValue();
    • V setValue(V value);
    • boolean equals(Object o);
    • int hashCode();
    • Comparator<Map.Entry<K,V>> comparingByKey()
    • Comparator<Map.Entry<K,V>> comparingByValue()
    • comparingByKey(Comparator<? super K> cmp)
    • comparingByValue(Comparator<? super V> cmp)

    1.1 TreeMap

    TreeMap 是利用红黑树实现,红黑树是在二叉排序树的基础上增加了以下要求:

    • 每个节点只能是红的或黑的。
    • 根节点是黑的。
    • 叶节点(指空节点)是黑的。
    • 如果一个节点是红的,则它的子节点是黑的,即红色节点不能相邻。
    • 从任一节点到每个叶子节点的路径都包含相同数目的黑色节点。

    我们先来看一个红黑树的图,明白叔叔节点及兄弟节点的概念

    在上图中,以节点 10 为例,节点 100 为它的叔叔节点,节点 30 为它的兄弟节点。

    我们再来了解下左旋和右旋,如下图

    即左旋的时候,X 的原位置交给了 Y,Y 的左节点(如果有)变成了 X 的右节点;右旋的时候,Y 的原位置交给了 X,X 的右节点(如果有)变成了 Y 的左节点。无论是左旋还是右旋,在旋转前后都是一颗二叉排序树。左旋与右旋的代码如下:

        private void rotateLeft(Entry<K,V> p) {
            if (p != null) {
                Entry<K,V> r = p.right;
                p.right = r.left;
                if (r.left != null)
                    r.left.parent = p;
                r.parent = p.parent;
                if (p.parent == null)
                    root = r;
                else if (p.parent.left == p)
                    p.parent.left = r;
                else
                    p.parent.right = r;
                r.left = p;
                p.parent = r;
            }
        }
    左旋
        private void rotateRight(Entry<K,V> p) {
            if (p != null) {
                Entry<K,V> l = p.left;
                p.left = l.right;
                if (l.right != null) l.right.parent = p;
                l.parent = p.parent;
                if (p.parent == null)
                    root = l;
                else if (p.parent.right == p)
                    p.parent.right = l;
                else p.parent.left = l;
                l.right = p;
                p.parent = l;
            }
        }
    右旋

    1)红黑树元素的插入

    向红黑树插入元素时,将其当作一颗二叉排序树进行插入,将插入节点设为红色,因为这样要处理的情况最少。这样可能会违反的是“如果一个节点是红的,则它的子节点是黑的”这条规则。我们来分析下插入一个节点的着色情况:

    1. 插入节点是根节点:直接设置为黑的
    2. 插入节点的父节点是黑的:不需要做处理
    3. 插入节点的父节点是红的:
      1. 叔叔节点是红的:将父节点、叔叔节点设为黑的,将祖父节点设为红的,将祖父节点看做插入节点继续操作。
      2. 叔叔节点是黑的:
        1. 父节点是左孩子:以父节点为支点左旋(如果当前节点是右孩子),设置父节点为黑的、祖父节点为红的,以祖父节点为支点右旋。
        2. 父节点是右孩子:以父节点为支点右旋(如果当前节点是左孩子),设置父节点为黑的、祖父节点为红的,以祖父节点为支点左旋。

    上述情况是在 HashMap 源码中得到的结论,因为查看过一些讲解红黑树的博客,对于情况讨论的都不怎么理想,干脆就直接从源码中找答案,下面是 HashMap 中关于红黑树插入新节点后的着色代码:

        private void fixAfterInsertion(Entry<K,V> x) {
            x.color = RED;
    
            while (x != null && x != root && x.parent.color == RED) {
                if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                    Entry<K,V> y = rightOf(parentOf(parentOf(x)));
                    if (colorOf(y) == RED) {
                        setColor(parentOf(x), BLACK);
                        setColor(y, BLACK);
                        setColor(parentOf(parentOf(x)), RED);
                        x = parentOf(parentOf(x));
                    } else {
                        if (x == rightOf(parentOf(x))) {
                            x = parentOf(x);
                            rotateLeft(x);
                        }
                        setColor(parentOf(x), BLACK);
                        setColor(parentOf(parentOf(x)), RED);
                        rotateRight(parentOf(parentOf(x)));
                    }
                } else {
                    Entry<K,V> y = leftOf(parentOf(parentOf(x)));
                    if (colorOf(y) == RED) {
                        setColor(parentOf(x), BLACK);
                        setColor(y, BLACK);
                        setColor(parentOf(parentOf(x)), RED);
                        x = parentOf(parentOf(x));
                    } else {
                        if (x == leftOf(parentOf(x))) {
                            x = parentOf(x);
                            rotateRight(x);
                        }
                        setColor(parentOf(x), BLACK);
                        setColor(parentOf(parentOf(x)), RED);
                        rotateLeft(parentOf(parentOf(x)));
                    }
                }
            }
            root.color = BLACK;
        }
    插入新节点后着色

    我们来看一下插入操作的过程

    • 当父节点是红的、叔叔节点是黑的,父节点是左孩子时,过程如下

    • 当父节点是红的、叔叔节点也是红的时,过程如下

    2)红黑树元素的删除

    从红黑树中删除元素时,先将其按二叉排序树的方式删除节点,然后将红黑树重新着色,我们来分析一下着色情况:

    1. 当前节点是根节点:直接设为黑的
    2. 当前节点是左孩子:
      1. 如果兄弟节点是红的:将兄弟节点设为黑的,将父节点设为红的,以父节点为支点左旋,获取新的兄弟节点继续判断。
      2. 如果兄弟节点的孩子节点都是黑的:将兄弟节点设为红的,将父节点看做当前节点,继续判断。
      3. 如果兄弟节点的孩子节点有红的:如果左孩子是红的,则将其设为黑的,并将兄弟节点设为红的,以兄弟节点为支点右旋,获取新的兄弟节点。将兄弟节点设为与父节点同样的颜色,将父节点设为黑的,将兄弟节点的右孩子设为黑的,以父节点为支点左旋。
    3. 当前节点是右孩子:

    注:以上操作过程是在数据结构可视化网址中进行的。

    TreeMap 中利用 transient Entry<K,V> root 作为红黑树的根,TreeMap 的部分方法如下:

    1) 放入元素

        public V put(K key, V value) {
            Entry<K,V> t = root;
            // 如果此时TreeMap中没有元素,则将这个元素设为根节点
            if (t == null) {
                compare(key, key); // type (and possibly null) check
                root = new Entry<>(key, value, null);
                size = 1;
                modCount++;
                return null;
            }
            int cmp;
            Entry<K,V> parent;
            // split comparator and comparable paths
            Comparator<? super K> cpr = comparator;
            // 如果有比较器就使用比较器,然后按照二叉排序树将其插入
            if (cpr != null) {
                do {
                    parent = t;
                    // 与当前节点比较
                    cmp = cpr.compare(key, t.key);
                    if (cmp < 0)
                        t = t.left;
                    else if (cmp > 0)
                        t = t.right;
                    else
                        // 将元素放入,替换旧值并返回
                        return t.setValue(value);
                } while (t != null);
            }
            else {
                if (key == null)
                    throw new NullPointerException();
                @SuppressWarnings("unchecked")
                    Comparable<? super K> k = (Comparable<? super K>) key;
                do {
                    parent = t;
                    cmp = k.compareTo(t.key);
                    if (cmp < 0)
                        t = t.left;
                    else if (cmp > 0)
                        t = t.right;
                    else
                        return t.setValue(value);
                } while (t != null);
            }
            // 能执行到这,说明是要插入新元素,而不是去替换旧元素
            Entry<K,V> e = new Entry<>(key, value, parent);
            if (cmp < 0)
                parent.left = e;
            else
                parent.right = e;
            // 根据红黑树的规则,进行调整
            fixAfterInsertion(e);
            size++;
            modCount++;
            return null;
        }
    View Code

    2) 获取元素

        public V get(Object key) {
            Entry<K,V> p = getEntry(key);
            return (p==null ? null : p.value);
        }
        final Entry<K,V> getEntry(Object key) {
            // Offload comparator-based version for sake of performance
            // 出于性能考虑,卸载基于比较的版本
            if (comparator != null)
                return getEntryUsingComparator(key);
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                Comparable<? super K> k = (Comparable<? super K>) key;
            Entry<K,V> p = root;
            // 按二叉排序树的方式查找
            while (p != null) {
                int cmp = k.compareTo(p.key);
                if (cmp < 0)
                    p = p.left;
                else if (cmp > 0)
                    p = p.right;
                else
                    return p;
            }
            return null;
        }
        final Entry<K,V> getEntryUsingComparator(Object key) {
            @SuppressWarnings("unchecked")
                K k = (K) key;
            Comparator<? super K> cpr = comparator;
            if (cpr != null) {
                Entry<K,V> p = root;
                while (p != null) {
                    int cmp = cpr.compare(k, p.key);
                    if (cmp < 0)
                        p = p.left;
                    else if (cmp > 0)
                        p = p.right;
                    else
                        return p;
                }
            }
            return null;
        }
    View Code

    3) 移除元素

        public V remove(Object key) {
            Entry<K,V> p = getEntry(key);
            if (p == null)
                return null;
            V oldValue = p.value;
            deleteEntry(p);
            return oldValue;
        }
        // 按照二叉排序树的方式删除,然后再重新着色
        private void deleteEntry(Entry<K,V> p) {
            modCount++;
            size--;
            // If strictly internal, copy successor's element to p and then make p
            // point to successor.
            // 有两个孩子节点
            if (p.left != null && p.right != null) {
                // 找出当前节点的后继节点
                Entry<K,V> s = successor(p);
                // 将后继节点值赋给当前节点
                p.key = s.key;
                p.value = s.value;
                // 将后继节点看做当前节点处理
                p = s;
            }
            // Start fixup at replacement node, if it exists.
            Entry<K,V> replacement = (p.left != null ? p.left : p.right);
            // 有一个孩子节点
            if (replacement != null) {
                // 将孩子节点放到当前位置
                replacement.parent = p.parent;
                if (p.parent == null)
                    root = replacement;
                else if (p == p.parent.left)
                    p.parent.left  = replacement;
                else
                    p.parent.right = replacement;
                // 置空,以便垃圾回收
                p.left = p.right = p.parent = null;
                // 如果被删除的节点是黑色,需要将树重新着色
                if (p.color == BLACK)
                    fixAfterDeletion(replacement);
            //没有孩子节点,也没有父节点
            } else if (p.parent == null) { // return if we are the only node.
                // 置空,以便垃圾回收
                root = null;    
            //没有孩子节点,但有父节点
            } else { //  No children. Use self as phantom replacement and unlink.
                // 如果要删除节点是黑的,需要将树重新着色
                if (p.color == BLACK)
                    fixAfterDeletion(p);
                // 如果存在父节点,则将引用置空,以便垃圾回收
                if (p.parent != null) {
                    if (p == p.parent.left)
                        p.parent.left = null;
                    else if (p == p.parent.right)
                        p.parent.right = null;
                    p.parent = null;
                }
            }
        }
    View Code

    在 TreeMap 中还有几个内部类:

    2.2 HashMap

    HashMap 利用哈希表,综合了链表(寻址容易)和数组(插入删除容易)的优点。哈希表有不同实现方式(数组+数组、数组+链表、单个数组等),最常用的一种是链表+数组的方式。

    那哈希值是如何计算的呢?哈希表也称作散列表,常用的散列方式有:

    • 除法散列法
    • 平方散列法
    • 斐波那契散列法

     

    HashMap 中定义了一个内部类 Node 来作为链表的节点,利用 transient Node<K,V>[] table 存储元素,默认的数组长度为16,默认的负载因子(loadFactor)为0.75。当 HashMap 存储的元素越来越多时,hash 冲突的机率也越来越高,为了提高查询效率,就要对数组进行扩容(扩容要重新计算元素在数组中的位置,消耗很大)。那么什么时候扩容呢?这就要看负载因子了,当元素个数超过数组大小 * loadFactor 时就会扩容。为什么负载因子为0.75呢?因为这是一个对空间和时间的折中取值,负载因子越大,散列表中越集中,空间利用越高,查找效率越低;负载因子越小,散列表中越稀疏,查找效率越高,空间利用越低。HashMap 的部分方法如下:

    1) 调整hash表大小

        // 调整hash表大小
        final Node<K,V>[] resize() {
            Node<K,V>[] oldTab = table;
            int oldCap = (oldTab == null) ? 0 : oldTab.length;
            int oldThr = threshold;
            int newCap, newThr = 0;
            if (oldCap > 0) {
                if (oldCap >= MAXIMUM_CAPACITY) {
                    threshold = Integer.MAX_VALUE;
                    return oldTab;
                }
                else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)
                    newThr = oldThr << 1; // double threshold
            }
            else if (oldThr > 0) // initial capacity was placed in threshold
                newCap = oldThr;
            else {               // zero initial threshold signifies using defaults
                newCap = DEFAULT_INITIAL_CAPACITY;
                newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
            }
            if (newThr == 0) {
                float ft = (float)newCap * loadFactor;
                newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE);
            }
            threshold = newThr;
            @SuppressWarnings({"rawtypes","unchecked"})
                Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
            table = newTab;
            if (oldTab != null) {
                for (int j = 0; j < oldCap; ++j) {
                    Node<K,V> e;
                    if ((e = oldTab[j]) != null) {
                        oldTab[j] = null;
                        if (e.next == null)
                            newTab[e.hash & (newCap - 1)] = e;
                        else if (e instanceof TreeNode)
                            ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                        else { // preserve order
                            Node<K,V> loHead = null, loTail = null;
                            Node<K,V> hiHead = null, hiTail = null;
                            Node<K,V> next;
                            do {
                                next = e.next;
                                if ((e.hash & oldCap) == 0) {
                                    if (loTail == null)
                                        loHead = e;
                                    else
                                        loTail.next = e;
                                    loTail = e;
                                }
                                else {
                                    if (hiTail == null)
                                        hiHead = e;
                                    else
                                        hiTail.next = e;
                                    hiTail = e;
                                }
                            } while ((e = next) != null);
                            if (loTail != null) {
                                loTail.next = null;
                                newTab[j] = loHead;
                            }
                            if (hiTail != null) {
                                hiTail.next = null;
                                newTab[j + oldCap] = hiHead;
                            }
                        }
                    }
                }
            }
            return newTab;
        }
    View Code

    2) 放入元素

        public V put(K key, V value) {
            return putVal(hash(key), key, value, false, true);
        }
        final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
            // tab用来暂存hash表,n为hash表长度,i为插入hash表的位置,p是i位置的头节点
            Node<K,V>[] tab; Node<K,V> p; int n, i;
            if ((tab = table) == null || (n = tab.length) == 0)
                // 调整hash表大小,获取其大小
                n = (tab = resize()).length;
            if ((p = tab[i = (n - 1) & hash]) == null)
                // 当 i 位置上头节点为空时,创建一个新节点将元素放入
                tab[i] = newNode(hash, key, value, null);
            else {
                // e是元素应放入的节点
                Node<K,V> e; K k;
                // 如果hash相等,key值也相等,则头节点p就是元素应放入的节点
                if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
                    e = p;
                // 
                else if (p instanceof TreeNode)
                    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                // 从头节点开始遍历比较
                else {    
                    for (int binCount = 0; ; ++binCount) {
                        // 如果已经是最后一个节点,那将元素插入到新节点
                        if ((e = p.next) == null) {
                            p.next = newNode(hash, key, value, null);
                            // 链表长度大于8时,将结构转为红黑树
                            if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                treeifyBin(tab, hash);
                            break;
                        }
                        // 如果hash相等,key值也相等,则p就是元素应放入的节点
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            break;
                        // 将e看做当前节点,继续遍历
                        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)
                // 调整hash表大小
                resize();
            afterNodeInsertion(evict);
            return null;
        }
        // 创建一个新节点
        Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
            return new Node<>(hash, key, value, next);
        }
    View Code

    3) 获取元素

        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;
            // 根据hash计算出元素所在链表的头节点
            if ((tab = table) != null && (n = tab.length) > 0 &&
                (first = tab[(n - 1) & hash]) != null) {
                // 判断头节点是否是所查找元素
                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;
                    } while ((e = e.next) != null);
                }
            }
            return null;
        }
    View Code

    4) 移除元素

        public V remove(Object key) {
            Node<K,V> e;
            return (e = removeNode(hash(key), key, null, false, true)) == null ?
                null : e.value;
        }
        final Node<K,V> removeNode(int hash, Object key, Object value,
                                   boolean matchValue, boolean movable) {
            Node<K,V>[] tab; Node<K,V> p; int n, index;
            // 根据hash获取元素所在链表的头节点
            if ((tab = table) != null && (n = tab.length) > 0 &&
                (p = tab[index = (n - 1) & hash]) != null) {
                Node<K,V> node = null, e; K k; V v;
                // 如果头节点就是要移除的元素
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    node = p;
                // 遍历查找要移除的元素
                else if ((e = p.next) != null) {
                    if (p instanceof TreeNode)
                        node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                    else {
                        do {
                            if (e.hash == hash &&
                                ((k = e.key) == key ||
                                 (key != null && key.equals(k)))) {
                                node = e;
                                break;
                            }
                            p = e;
                        } while ((e = e.next) != null);
                    }
                }
                // 将获取到的元素移除
                if (node != null && (!matchValue || (v = node.value) == value ||
                                     (value != null && value.equals(v)))) {
                    if (node instanceof TreeNode)
                        ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                    else if (node == p)
                        tab[index] = node.next;
                    else
                        p.next = node.next;
                    ++modCount;
                    --size;
                    afterNodeRemoval(node);
                    return node;
                }
            }
            return null;
        }
    View Code

    在 HashMap 内还有几个内部类:

    1) Node

    Node 是哈希表中链表一个节点,即 Map 中的一个实体(一个key-value对)。

    2) TreeNode

    TreeNode 是红黑树的节点,当哈希表内的链表长度大于8时,HashMap 会将链表转换为红黑树。

    3) KeySet

    当要遍历 HashMap 所有的 key 时,可以通过这个类的对象进行遍历。

    4) Values

    当要遍历 HashMap 所有的 value 时,可以通过这个类的对象进行遍历。

    5) EntrySet

    用这个类的到的是 Map.Entry<K,V> 类型的 Set 集合。

  • 相关阅读:
    java实例:一个简单的图书管理程序
    教你如何一键退出USB设备(转)
    四种方法处理“无法停止通用卷设备”(转)
    简单数据恢复菜鸟教程 (转)
    安装flash纠结
    java:文本I/O实例
    数据结构之链表(1)
    win7屏幕录制软件psr.exe使用教程(转)
    SQL:基本知识
    .Net Micro Framework研究—FAT文件系统实现探索
  • 原文地址:https://www.cnblogs.com/tengyunhao/p/7430185.html
Copyright © 2020-2023  润新知