集合类源码Collection之List(CopyOnWriteArrayList, Stack)

Posted lua123

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了集合类源码Collection之List(CopyOnWriteArrayList, Stack)相关的知识,希望对你有一定的参考价值。

CopyOnWriteArrayList

功能

全名

public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable

简述

ArrayList的线程安全变体,其中所有的可变操作(添加、修改等)都是通过创建底层数组的新副本来实现的。

方法

// 返回列表里元素的数量
public int size()

// 如果此列表不包含任何元素,则返回true。
public boolean isEmpty()

// 如果此列表包含至少一个指定的元素,则返回true。
public boolean contains(Object o)

// 返回该列表中指定元素第一次出现的索引,如果该列表不包含该元素,则返回-1。
public int indexOf(Object o)

// 返回该列表中指定元素第一次出现时的索引(从指定索引向前搜索),如果没有找到该元素,则返回-1。
public int indexOf(E e, int index)

// 返回该列表中指定元素的最后一次出现的索引,如果该向量不包含该元素,则返回-1。
public int lastIndexOf(Object o)

// // 返回此列表中指定元素的最后一次出现的索引(从指定索引向后搜索),如果没有找到该元素,则返回-1。
public int lastIndexOf(E e, int index)

// 返回该列表的浅拷贝。
public Object clone()

// 返回一个数组,该数组按适当的顺序(从第一个元素到最后一个元素)包含列表中的所有元素。返回的数组将是“安全的”,因为这个列表不维护对它的引用。(换句话说,这个方法分配一个新数组)。因此,调用者可以自由地修改返回的数组。
public Object[] toArray()

// 返回一个数组,该数组按适当的顺序包含列表中的所有元素(从第一个元素到最后一个元素);返回数组的运行时类型是指定数组的运行时类型。
public <T> T[] toArray(T[] a)

// 返回列表中指定位置的元素。
public E get(int index)

// 将列表中指定位置的元素替换为指定元素。
public E set(int index, E element)

// 将指定的元素追加到此列表的末尾。
public boolean add(E e)

// 将指定元素插入到列表中的指定位置。将当前位于该位置的元素和任何后续元素向右移动
public void add(int index, E element)

// 删除列表中指定位置的元素。将任何后续元素向左移动。返回从列表中删除的元素。
public E remove(int index)

// 从该列表中删除指定元素的第一个匹配项
public boolean remove(Object o)

// 如果列表中没有此元素,则添加
public boolean addIfAbsent(E e)

// 如果此列表包含指定集合的所有元素,则返回true。
public boolean containsAll(Collection<?> c)

// 从此列表中移除指定集合中包含的所有元素。因为需要一个内部临时数组,所以在这个类中这是一个特别昂贵的操作。
public boolean removeAll(Collection<?> c)

// 仅保留此列表中包含在指定集合中的元素。
public boolean retainAll(Collection<?> c)

// 将指定集合中尚未包含在此列表中的所有元素按照指定集合的迭代器返回它们的顺序追加到此列表的末尾。
public int addAllAbsent(Collection<? extends E> c)

// 从列表中删除所有元素。该调用返回后,列表将为空。
public void clear()

// 将指定集合中的所有元素按照指定集合的迭代器返回它们的顺序追加到此列表的末尾。
public boolean addAll(Collection<? extends E> c)

// 从指定位置开始,将指定集合中的所有元素插入此列表。
public boolean addAll(int index, Collection<? extends E> c)

// 为可迭代的每个元素执行给定的操作,直到处理完所有元素或操作引发异常。
public void forEach(Consumer<? super E> action)

// 删除此集合中满足给定谓词的所有元素。迭代期间或由谓词抛出的错误或运行时异常将传递给调用者。
public boolean removeIf(Predicate<? super E> filter)

// 将此列表中的每个元素替换为将操作符应用于该元素的结果。操作符抛出的错误或运行时异常将传递给调用者。
public void replaceAll(UnaryOperator<E> operator)

// 根据比较器产生的顺序对这个列表进行排序。
public void sort(Comparator<? super E> c)

// 返回此列表的字符串表示形式。字符串表示由列表元素的字符串表示形式组成,其顺序由其迭代器返回,包含在方括号中(“[]”)。
public String toString()

// 将指定的对象与此列表进行相等性比较。如果指定的对象是与此对象相同的对象,并且迭代器在指定列表上返回的元素序列与迭代器在此列表上返回的序列相同,则返回true。如果两个序列长度相同,且序列中相同位置对应的元素相等,则认为两个序列是相同的。
public boolean equals(Object o)

// 返回此列表的哈希码值。这个实现使用List.hashCode()中的定义。
public int hashCode()

// 按适当的顺序对列表中的元素返回一个迭代器。
public Iterator<E> iterator()

// 返回该列表中元素的列表迭代器(按适当的顺序)。
public ListIterator<E> listIterator()

// 返回此列表中元素的列表迭代器(按适当的顺序),从列表中的指定位置开始。
public ListIterator<E> listIterator(int index)

// 在此列表中的元素上创建延迟绑定和快速失败的Spliterator。
public Spliterator<E> spliterator()

// 返回该列表中fromIndex(包含)和toIndex(排除)之间部分的视图。返回子列表中的更改将反映在此列表中。
public List<E> subList(int fromIndex, int toIndex)

原理

add

public boolean add(E e) {
    // 使用ReentrantLock保证了线程安全
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lock();
    try {
        // 获取当前数组
        Object[] elements = getArray();
        // 数组长度
        int len = elements.length;
        // 复制当前数组,新数组长度+1
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        // 设置新数据
        newElements[len] = e;
        // 将老数组替换成新数组
        setArray(newElements);
        return true;
    } finally {
        // 解锁
        lock.unlock();
    }
}

addIfAbsent

public boolean addIfAbsent(E e) {
    // 获取当前数组
    Object[] snapshot = getArray();
    // 先判断当前数组中是否存在目标对象,存在则返回false,不做添加;否则添加。
    return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
        addIfAbsent(e, snapshot);
}
private boolean addIfAbsent(E e, Object[] snapshot) {
    // 获取锁
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 获取当前数组
        Object[] current = getArray();
        int len = current.length;
        // 如果当前数组和传入的快照不是同一个对象,则代表数据发生了变化,需要确认
        if (snapshot != current) {
            // Optimize for lost race to another addXXX operation
            // 对另一个竞争失败的addXXX操作进行优化
            // 得到两份数据长度较小的一个
            int common = Math.min(snapshot.length, len);
            for (int i = 0; i < common; i++)
                // 如果与快照相比对应位置的数据不同,并且待添加对象和当前位置的数据相同,则返回false,停止add操作
                if (current[i] != snapshot[i] && eq(e, current[i]))
                    return false;
            // (前面循环没有触发条件)查看当前数组中是否存在目标对象,如果有即返回false,停止add操作
            if (indexOf(e, current, common, len) >= 0)
                    return false;
        }
        // 例行添加操作
        Object[] newElements = Arrays.copyOf(current, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

remove

public E remove(int index) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 获取当前数组
        Object[] elements = getArray();
        int len = elements.length;
        // 用临时变量保存老对象
        E oldValue = get(elements, index);
        // 计算要移动的元素
        int numMoved = len - index - 1;
        if (numMoved == 0)
            // 如果没有要移动的,代表删除了最后一个元素,只需要把末尾之前的数据复制到一份新数组即可
            setArray(Arrays.copyOf(elements, len - 1));
        else {
            // 创建一个新数组
            Object[] newElements = new Object[len - 1];
            // 把删除位置之前的数据复制到新数组
            System.arraycopy(elements, 0, newElements, 0, index);
            // 把删除位置之后的数据复制到新数组
            System.arraycopy(elements, index + 1, newElements, index,
                             numMoved);
            // 将老数组替换成新数组
            setArray(newElements);
        }
        // 返回删除的对象
        return oldValue;
    } finally {
        lock.unlock();
    }
}

get

public E get(int index) {
    return get(getArray(), index);
}

优缺点

优点:根据数据结构和实现逻辑,这个适合“读多写少”的场景。

缺点:其一、因为它添加和删除都是要复制一份新数组,所以内存占用是个问题;其二、因为读不加锁,如果此时有add或者remove操作未完成,那么在它读的那一刻就决定了它拿到的是老数据。所以它只能保证数据的最终一致性,而不能保证实时一致性

Stack

功能

全名

public
class Stack<E> extends Vector<E>

简述

Stack类表示对象的后进先出(LIFO)堆栈。它通过5个操作扩展了Vector类,这5个操作允许将一个Vector视为一个堆栈。
Deque接口及其实现提供了一组更完整、更一致的后进先出堆栈操作,应该优先使用这些操作。例如:
Deque stack = new ArrayDeque();

方法

// 压栈操作,将对象压入堆栈顶部。与addElement(item)功能相同
public E push(E item)

// 出栈操作,删除此堆栈顶部的对象并返回。
public E pop()

// 查看此堆栈顶部的对象,但不将其从堆栈中删除。
public E peek()

// 查看堆栈是否为空。
public boolean empty()

// 返回最接近堆栈顶部的搜索对象与堆栈顶部的距离;堆栈上最上面的对象被认为与堆栈顶部的距离是1。
public int search(Object o)

原理

push

// push内部调用的是Vector中的同步方法addElement,其中的扩容逻辑在前面已经讲过,这里不再赘述
// 有一点需要注意的是,Stack的push,实际上是把新元素放在了Vector内部存储结构的末尾。对应的pop(removeElementAt(len - 1))和search(int i = lastIndexOf(o))操作,也是从末尾开始的。
// 说白了,Stack将Vector的尾部当成栈顶。
public E push(E item) {
    addElement(item);

    return item;
}

public synchronized void addElement(E obj) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = obj;
}

pop

public synchronized E pop() {
    E       obj;
    int     len = size();

    // 先用临时变量存储栈顶元素
    obj = peek();
    // 然后移除栈顶元素
    removeElementAt(len - 1);

    return obj;
}
public synchronized void removeElementAt(int index) {
    // 内部结构修改次数 +1
    modCount++;
    if (index >= elementCount) {
        throw new ArrayIndexOutOfBoundsException(index + " >= " +
                                                 elementCount);
    }
    else if (index < 0) {
        throw new ArrayIndexOutOfBoundsException(index);
    }
    int j = elementCount - index - 1;
    if (j > 0) {
        // 将index之后的数据向前复制
        System.arraycopy(elementData, index + 1, elementData, index, j);
    }
    elementCount--;
    elementData[elementCount] = null; /* to let gc do its work */
}

peek

public synchronized E peek() {
    int     len = size();

    if (len == 0)
        throw new EmptyStackException();
    // 实际上调用Vector里的elementAt方法
    return elementAt(len - 1);
}

public synchronized E elementAt(int index) {
    if (index >= elementCount) {
        throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);
    }
    // 直接返回下标位置的元素
    return elementData(index);
}

search

public synchronized int search(Object o) {
    // 从栈顶开始,找到目标第一次出现的位置
    int i = lastIndexOf(o);

    if (i >= 0) {
        // 返回与栈顶的距离
        return size() - i;
    }
    return -1;
}
public synchronized int lastIndexOf(Object o) {
    return lastIndexOf(o, elementCount-1);
}
public synchronized int lastIndexOf(Object o, int index) {
    if (index >= elementCount)
        throw new IndexOutOfBoundsException(index + " >= "+ elementCount);

    if (o == null) {
        // 从后向前搜索,找到第一个匹配的即返回
        for (int i = index; i >= 0; i--)
            if (elementData[i]==null)
                return i;
    } else {
        // 从后向前搜索,找到第一个匹配的即返回
        for (int i = index; i >= 0; i--)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

优缺点

优点:实现了栈的数据结构,方便了开发;push和pop操作,都是发生在数组的末尾,没有了元素的移动,提高了效率。

缺点:它是基于Vector的,操作都加了同步代码,效率有所降低,毕竟有的情况下没有线程安全问题。

 

以上是关于集合类源码Collection之List(CopyOnWriteArrayList, Stack)的主要内容,如果未能解决你的问题,请参考以下文章

Java源码之ArrayList

Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例

Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例

java集合类源码分析之Map

Java集合框架源码阅读之AbstractCollection

Java 集合系列02之 Collection架构