Java常用集合源码分析 Ⅰ

2021-08-10  本文已影响0人  眼若繁星丶

Java常用集合源码分析 Ⅰ

@Version: JDK 1.8

@IDE: IntellJ IDEA 2021.1

@Date: 2021/8/7

@Author: Hypocrite30

一、集合

Ⅰ、Collection接口

Ⅱ、Iterator接口

常见方法:

boolean hashNext() Returns: true if the iteration has more elements
E next() Returns: the next element in the iteration
void remove() Removes from the underlying collection the last element returned by this iterator

📌:在调用 iterator.next() 之前必须调用 iterator.hasNext() 判断后面是否存在元素。若不调用,到最后一个元素,调用 next() 会抛出 NoSuchElementException

Ⅲ、List接口

二、ArrayList

Ⅰ、Field

/**
 * 默认初始化容量
 */
private static final int DEFAULT_CAPACITY = 10;

/**
 * 空数组容量为0,有参构造默认使用的存储数据结构
 */
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
 * 空数组容量为0,无参构造时默认使用的存储数据结构
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**
 * 这ArrayList底层用到的存取数据的数组
 * 非私有,以简化嵌套类访问
 * transient 不允许序列化
 */
transient Object[] elementData;

/**
 * 实际ArrayList集合大小
 */
private int size;

/**
 * 可分配的最大容量
 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

📌几点说明:

ArrayList 序列化与反序列化

private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException{
    // 预期的修改次数,用于判断并发修改的问题
    int expectedModCount = modCount;
    s.defaultWriteObject();

    // 写入 ArrayList size
    s.writeInt(size);

    // 写入每一个元素值
    for (int i = 0; i < size; i++) {
        s.writeObject(elementData[i]);
    }
    // 判断序列化的并发问题
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}
private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    // 这里的 EMPTY_ELEMENTDATA 就是字段中的用于给有参构造用的空数组
    elementData = EMPTY_ELEMENTDATA;

    // 按照 size 读入数据
    s.defaultReadObject();

    // 读取容量
    s.readInt(); // ignored

    if (size > 0) {
        // 与 clone() 类似,根据大小而不是容量分配阵列
        int capacity = calculateCapacity(elementData, size);
        SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
        ensureCapacityInternal(size);

        Object[] a = elementData;
        // 按正确顺序读入所有元素
        for (int i = 0; i < size; i++) {
            a[i] = s.readObject();
        }
    }
}

MAX_ARRAY_SIZE 数值说明

/**
 * 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;

官方注释解释,有些虚拟机会保留一些字段空间,如果用满 Integer.MAX_VALUE 可能会 OOM。

StackOverflow 上的部分解释提及到「内存模型里的对象头的组成」[1]

于是对于 Java数组对象 剖析[2]

IBM官方说到「与Java对象相比,区别在于数组对象有一个额外的元数据,用于表示 数组的大小

剖析 Java对象 [3]

不同的JVM厂商对元数据数量的设计有差异,但通常包括:

Java数组对象 还多一个 Size,即数组的大小。

对象头的大小不能超过 8 字节

标记字 Mark Word「哈希值、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳」在 32 位架构占 4 byte,64 位架构占 8 byte;类型指针Klass pointer 在 32 为架构上具有字长,但如果堆地址可以用 4 byte 编码,则依然可以占 4 byte。

📌:所以为了保险起见「防止OOM」,最大数组长度设置为 Integer.MAX_VALUE - 8

Ⅱ、Constructor

无参构造

/**
 * 构造一个初始容量为10的空列表
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

有参构造

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}
public ArrayList(Collection<? extends E> c) {
    // 按正确顺序包含此列表中所有元素的数组
    Object[] a = c.toArray();
    // 先更新 size 值,再判断。如果是空数组则直接使用默认空数组
    if ((size = a.length) != 0) {
        // 判断 c 集合是不是ArrayList,是则直接赋值
        if (c.getClass() == ArrayList.class) {
            elementData = a;
        } else { // 否则手动拷贝一份 Object[]
            elementData = Arrays.copyOf(a, size, Object[].class);
        }
    } else {
        // replace with empty array.
        elementData = EMPTY_ELEMENTDATA;
    }
}

Ⅲ、Method

增添元素 add(E e)

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

添加元素 add(int index, E element)

public void add(int index, E element) {
    rangeCheckForAdd(index);

    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}
private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

删除 remove(int index)

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; // 方便 GC

    return oldValue;
}
// 上述使用到的方法实现
private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
E elementData(int index) {
    return (E) elementData[index];
}

删除 remove(Object o)

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;
}
// 没有返回值的 remove(int index)
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; // 方便 GC
}

删除 clear()

public void clear() {
    modCount++;
    // 方便 GC
    for (int i = 0; i < size; i++)
        elementData[i] = null;
    size = 0;
}

修改 set(int index, E element)

public E set(int index, E element) {
    rangeCheck(index);

    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

查找 get(int index)

public E get(int index) {
    rangeCheck(index);

    return elementData(index);
}

扩容机制

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

📌Q:if 语句返回的一直都是 minCapacity「因为正数minCapacity > DEFAULT_CAPACITY (0)」,而 if 语句外也是返回 minCapacity。设计意义在哪?

A:假设传入的 minCapacity 是负数,即 add() 中的 size + 1 仍为负数,则会返回 DEFAULT_CAPACITY AKA 10,即无参构造默认创建容量为 10 的数组,所以这么设计可以解决非法入参。

Q:为什么要判断「elementData」是否是 默认空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA

A:其实此 if 语句只有第一次扩容会使用到,真正的扩容 grow() 在每一次扩容都会创建出新的数组覆盖掉 elementData,扩容之后肯定就能确保入参 minCapacity 是正数([size + 1](#增添元素 add(E e))),不需要再判断。

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

📌官方在条件判断这里注解了 溢出警告「overflow-conscious」,这里涉及到 a < ba - b < 0 区别的问题,[下文说明](#关于 a < b 与 a - b < 0 应用说明)。

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1); // AKA 1.5倍扩容
    // 只有第一次才会 true 修改新容量
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 如果新容量太大,则按照大容量处理方式
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

📌Q:第一个条件 newCapacity < minCapacity 判断奇怪,新容量为什么会比预测容量小。预测容量是 size + 1,即所有数据长度 + 1,而新容量是在原容量基础上扩大 1.5 倍,肯定比 minCapacity 要大。

A:在无参构造创建空数组时,oldCapacity 原容量为 0,扩大 1.5 倍仍然为 0。此时新容量自然比预测容量要小,将值为0的 newCapacity 更新为 预测容量。而在此之后,这一更新操作都不会进行,因为扩容容量肯定比预测容量大「(x1.5) > (+ 1)」。

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}
public static <T> T[] copyOf(T[] original, int newLength) {
    return (T[]) copyOf(original, newLength, original.getClass());
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}
public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

关于 a < b 与 a - b < 0 应用说明

Q:看到上述扩容的很多条件判断使用的都是 a - b < 0 的形式,而不是直接比较,这种设计的好处在哪?

在数学中,这两个不等式是完全等价的。但在计算机中,需要考虑到存储的问题,有可能会出现变量 a | b 出现

溢出的情况。

🌰Demo [4]

int a = Integer.MAX_VALUE;
int b = Integer.MIN_VALUE;
if (a < b) System.out.println("a < b");
if (a - b < 0) System.out.println("a - b < 0");

结果:a - b < 0

分析:a - b 超出 int 存储最大范围,于是溢出,变成负数

ArrayList 前的判断:

// overflow-conscious code
if (minCapacity - elementData.length > 0)
    grow(minCapacity);

官方也提出 溢出警告

grow() :

// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
    newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
    newCapacity = hugeCapacity(minCapacity);

同样也有溢出警告

三、Vector

Ⅰ、Field

// 存储数组
protected Object[] elementData;
// 真实元素的数量
protected int elementCount;
// 扩容因子,详细作用见扩容函数
protected int capacityIncrement;

Ⅱ、Constructor

无参构造

public Vector() {
    this(10);
}

有参构造

public Vector(int initialCapacity) {
    this(initialCapacity, 0);
}
public Vector(int initialCapacity, int capacityIncrement) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    this.elementData = new Object[initialCapacity];
    this.capacityIncrement = capacityIncrement;
}
public Vector(Collection<? extends E> c) {
    Object[] a = c.toArray();
    elementCount = a.length;
    if (c.getClass() == ArrayList.class) {
        elementData = a;
    } else {
        elementData = Arrays.copyOf(a, elementCount, Object[].class);
    }
}

Ⅲ、Method

扩容机制

public synchronized boolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
}
private void ensureCapacityHelper(int minCapacity) {
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                     capacityIncrement : oldCapacity); // 扩容机制
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

newCapacity 是旧容量 加上一个值,三目运算符判断扩容因子是多少,如果是默认的 0 或者小于 0,则扩容两倍。

否则扩容 1 + capacityIncrement

四、LinkedList

List list = Collections.synchronizedList(new LinkedList(...));

Ⅰ、Field

// 节点(元素)个数
transient int size = 0;
// 指向第一个节点的指针。
transient Node<E> first;
// 指向末尾节点
transient Node<E> last;

根据双向链表储存特点

在运行中的不变量:

(first == null && last == null) || (first.prev == null && first.item != null)
(first == null && last == null) || (last.next == null && last.item != null)

静态内部类

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

Ⅱ、Constructor

无参构造

// Constructs an empty list.
public LinkedList() {
}

有参构造

public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

Ⅲ、Method

添加 add(E e)

public boolean add(E e) {
    linkLast(e);
    return true;
}

添加 add(int index, E element)

public void add(int index, E element) {
    checkPositionIndex(index);

    if (index == size)
        linkLast(element);
    else
        linkBefore(element, node(index));
}
private void checkPositionIndex(int index) {
    if (!isPositionIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// 检查下标合法性 index ∈ [0, size]
private boolean isPositionIndex(int index) {
    return index >= 0 && index <= size;
}

添加 addFirst(E e)

public void addFirst(E e) {
    linkFirst(e);
}

添加 addLast(E e)

public void addLast(E e) {
    linkLast(e);
}

删除 remove(Object o)

public boolean remove(Object o) {
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}

尾插法添加元素 linkLast(E e)

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++;
}

插入元素到非空节点之前 linkBefore(E e, Node<E> succ)

void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    final Node<E> pred = succ.prev;
    final Node<E> newNode = new Node<>(pred, e, succ);
    succ.prev = newNode;
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode;
    size++;
    modCount++;
}

插入元素到链表头 linkFirst(E 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++;
}

删除节点 unlink(Node<E> x)

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

检索节点在链表的下标位置 node(int 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;
    }
}

获取元素四种方法的区别

getFirst() & getLast() & peekFirst() & peekLast()

public E peekFirst() {
    final Node<E> f = first;
    return (f == null) ? null : f.item;
}
public E getFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return f.item;
}

LinkedList 作双端队列和栈的细节

因为 LinkedList 同时也实现了 Deque 接口,所以可以作为双端队列,自然也可以当做「一开一闭」。

public class Solution {
    public static void main(String[] args) {
        Deque<Integer> stack = new ArrayDeque<Integer>();
        stack.push(1);
        stack.push(2);
        stack.push(3);
        System.out.println(stack);
    }
}

[3, 2, 1]

public class Solution {
    public static void main(String[] args) {
        Deque<Integer> queue = new ArrayDeque<Integer>();
        queue.offer(1);
        queue.offer(2);
        queue.offer(3);
        System.out.println(queue);
    }
}

[1, 2, 3]

既然可以同时作队列和栈,引发思考,同为 peek() 获得「栈顶」| 「队头」元素,那么猜测队列首部栈顶开口同个方向的。

/**
 * Retrieves, but does not remove, the head of the queue represented by
 * this deque (in other words, the first element of this deque), or
 * returns {@code null} if this deque is empty.
 *
 * <p>This method is equivalent to {@link #peekFirst()}.
 *
 * @return the head of the queue represented by this deque, or
 *         {@code null} if this deque is empty
 */
E peek();

peek() 返回 deque 队列头, 如果双端队列为空,则返回 null

public E peek() {
    final Node<E> f = first;
    return (f == null) ? null : f.item;
}

获取左侧链表首部

public E peek() {
    return peekFirst();
}

获取队列首部

📌结论:官方定义的左边(First)是栈首队列头。由此才能实现一个方法达到相同的peek 效果。

🔗Reference:


  1. Why the maximum array size of ArrayList is Integer.MAX_VALUE - 8?

  2. Anatomy of a Java array object

  3. Anatomy of a Java object

  4. [Difference between if (a - b < 0) and if (a < b)]

上一篇下一篇

猜你喜欢

热点阅读