ArrayList、LinkedList、Vector、CopyOnWriteArrayList的区别和源码分析

1. ArrayList

ArrayList 是一个数组队列,相当于动态数组。与Java中的数组相比,它的容量能动态增长。它继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。默认容量是10(从源码中可以看出每次容量扩大为原来的1.5倍,int newCapacity = oldCapacity + (oldCapacity >> 1);)。ArrayList中的操作不是线程安全的!所以,建议在单线程中才使用ArrayList,而在多线程中可以选择Vector或者CopyOnWriteArrayList

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    ......
    
    /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;

    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() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
    public ArrayList(Collection<? extends E> c) {
        ......
    }

    private void grow(int minCapacity) {
            // 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);
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);
        }

    public int size() {
        return size;
    }

       ......
}

1.1 ArrayList的扩容

ArrayList的默认容量为10,当插入第11个元素的时候就会扩容,每次扩容增大为原来的1.5倍,可从代码int newCapacity = oldCapacity + (oldCapacity >> 1);看出。

2. LinkedList

LinkedList和ArrayList一样实现了List接口,但是LinkedList是用双向链表实现的。

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
       transient int size = 0;

    transient Node<E> first;

    transient Node<E> last;

    public LinkedList() {
        }

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

    ......
}

2.1 ArrayList和LinkedList的区别

  • 遍历都可以使用普通for循环、foreach循环、Iterator接口实现
  • ArrayList基于数组实现,LinkedList基于双向链表实现
  • ArrayList随机访问和修改的效率比较高,LinkedList插入和删除的效率比较高

3. Vector

Vector是线程安全的,从源码中有很多的synchronized就可以看出。Vector是Java早期提供的线程安全的动态数组,synchronized关键字几乎修饰了所有对外暴露的方法,所以在读远大于写的操作场景中,Vector将会发生大量锁竞争,从而给系统带来性能开销。适用于写大于读的场景

public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    ......
    protected Object[] elementData;
    protected int elementCount;
    protected int capacityIncrement;

    public Vector(int initialCapacity) {
            this(initialCapacity, 0);
    }

    public Vector() {
            this(10);
        }
    
    public synchronized int capacity() {
            return elementData.length;
        }

    public synchronized int size() {
            return elementCount;
        }
    ......
}

ArrayList和Vector的区别

  • Vector是线程安全的,源码中有很多的synchronized可以看出,而ArrayList不是。所以在单线程中,ArrayList比Vector性能要高
  • ArrayList和Vector底层都采用数组进行连续存储,当存储空间不足需要扩容的时候,ArrayList默认增加为原来的1.5倍,Vector默认增加为原来的2倍
  • 查找一个指定位置的数据,vector和arraylist使用的时间是相同的,都是0(1),这个时候使用Vector和ArrayList都可以

4. CopyOnWriteArrayList

CopyOnWriteArrayList具有如下特性:

  • 实现了List接口;
  • 内部持有一个ReentrantLock lock = new ReentrantLock();
  • 增加时复制出一个新的数组,完成插入、修改或者移除操作后将新数组赋值给array
  • 增删改都需要获得锁,并且锁只有一把,而读操作不需要获得锁,支持并发。为什么增删改中都需要创建一个新的数组,操作完成之后再赋给原来的引用?这是为了保证get的时候都能获取到元素,如果在增删改过程直接修改原来的数组,可能会造成执行读操作获取不到数据;
  • 内部使用ReentrantLock保证线程安全;

CopyOnWriteArrayList是java.util.concurrent包提供的方法,它实现了读操作无锁写操作则通过操作底层数组的新副本来实现,是一种读写分离的并发策略。 

CopyOnWrite它的原理是,任何修改操作,如add、set、remove,都会拷贝原数组,修改后替换原来的数组,通过这种防御性的方式,实现另类的线程安全。

所以这种数据结构,相对比较适合读多写少的操作,不然修改的开销还是非常明显的,适用于读远大于写的场景

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
        private static final long serialVersionUID = 8673264195747942595L;
    
    /** The lock protecting all mutators */
    final transient ReentrantLock lock = new ReentrantLock();
    
    /** The array, accessed only via getArray/setArray. */
    private transient volatile Object[] array;

    public boolean add(E e) {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                Object[] elements = getArray();
                int len = elements.length;
                Object[] newElements = Arrays.copyOf(elements, len + 1);
                newElements[len] = e;
                setArray(newElements);
                return true;
            } finally {
                lock.unlock();
            }
        }
    
    }

    ......
}

猜你喜欢

转载自www.cnblogs.com/windpoplar/p/11886116.html