ArrayList、 LinkedList 和 Vector

本文对比了ArrayList、LinkedList和Vector在Java中的实现,包括它们底层数据结构、线程安全性、扩容策略以及get、set、add和remove方法的性能差异。ArrayList适合随机访问,LinkedList适合频繁增删,Vector是线程安全但效率较低。
摘要由CSDN通过智能技术生成

ArrayList、 LinkedList 和 Vector

ArrayList、 LinkedList 和 Vector都实现了List接口,是List集合的三种实现。

  1. ArrayList底层是用数组实现的。可以认为ArrayList是一个可改变大小的数组。随着越来越多的元素被添加到ArrayList中,其规模是动态增加的。而且ArrayList可以加入null,多个空也成立。在多线程情况下不建议使用。

  2. LinkedList底层是通过双向链表实现的。所以,LinkedList和ArrayList之前的区别主要就是数组和链表的区别。数据存取的时候使用Arraylist,如果增加和删除操作多的话就用LinkList。

    所以,LinkedList和ArrayList相比,增删的速度较快。但是查询和修改值的速度较慢。同时,LinkedList还实现了Queue接口,所以他还提供了offer(), peek(), poll()等方法。

  3. Vector和ArrayList一样,都是通过数组实现的,但是Vector是线程安全的。和ArrayList相比,其中的很多方法都通过同步(synchronized)处理来保证线程安全

  • 如果你的程序不涉及到线程安全问题,那么使用ArrayList是更好的选择(因为Vector使用synchronized,必然会影响效率)。

  • 二者之间还有一个区别,就是扩容策略不一样。在List被第一次创建的时候,会有一个初始大小,随着不断向List中增加元素,当List认为容量不够的时候就会进行扩容。Vector缺省情况下自动增长原来一倍的数组长度,ArrayList增长原来的50%。

  • Vector线程同步(安全),ArrayList、LinkedList线程不同步(不安全)

源码核心实现
  1. ArrayList是基于可变数组实现。

(1) ArrayList中维护了一个Object类型的数组elementData;

(2)当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第一次添加则扩容elementData为10,如果要再次扩容,则扩容elementData为1.5倍;ArrayList()

(3)如果使用的是指定大小的构造器,则初始elementData为指定大小,如果要扩容,则直接为1.5倍。

ArrayList(int)

transient Object[] elementData;// transient 瞬间,短暂的,表示该属性不会被序列化
private int size; 
  1. LinkedList是基于双向链表实现。

transient int size = 0;
transient Node<E> first;
transient Node<E> last;
  1. Vector是基于可变数组实现。

(1)如果是无参,默认是10,满了以后按照2倍扩容

(2)指定大小的话每次直接按照2倍扩容

protected Object[] elementData;
protected int elementCount;
protected int capacityIncrement;
核心参数

ArrayList 可以设置初始大小(由于数组实现的原因),不设置默认为10。

LinkedList不需要设置参数(由于使用链表实现,无界)。

Vector不仅可以设置初始大小,还可以设置容量增幅。

//ArrayList传参构造函数
   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);
        }
    }
    //LinkedList构造函数
    public LinkedList() {
    }
   //Vector传参构造函数 
    public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }
get、set方法

ArrayList

public E get(int index) {
        Objects.checkIndex(index, size);
        return elementData(index);
    }
    public E set(int index, E element) {
        Objects.checkIndex(index, size);
        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

Vector  

 public synchronized E get(int index) {
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);
​
        return elementData(index);
    }
    public synchronized E set(int index, E element) {
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);
​
        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

二者的逻辑一样,get方法判断是否下标越界,不越界返回index下的数值。set方法判断是否越界,不越界将新值放到指定下标上。它俩的区别在与synchronized关键字,正好说明了Vector是线程安全的。

LinkedList    

public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }
    public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }
    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;
        }
    }

可以看到LinkedList的get方法会先检查是否越界,不越界返回指定下标node的item值。 set方法也是先检查越界情况,不越界将该点的node的item赋为新值。取node指定位置上的值时要循环遍历,所以对于随机的get,set,ArrayList的性能要优于LinkedList的。

add、remove方法

ArrayList  

 public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }
    private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();
        elementData[s] = e;
        size = s + 1;
    }
    public void add(int index, E element) {
        rangeCheckForAdd(index);
        modCount++;
        final int s;
        Object[] elementData;
        if ((s = size) == (elementData = this.elementData).length)
            elementData = grow();
        System.arraycopy(elementData, index,
                         elementData, index + 1,
                         s - index);
        elementData[index] = element;
        size = s + 1;
    }
    public E remove(int index) {
        Objects.checkIndex(index, size);
​
        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;
    }

Vector  

 public synchronized boolean add(E e) {
        modCount++;
        add(e, elementData, elementCount);
        return true;
    }
    private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();
        elementData[s] = e;
        elementCount = s + 1;
    }
    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)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--elementCount] = null; // Let gc do its work
​
        return oldValue;
    }

可以看到,Vector和ArrayList十分相近了,除了synchronized关键字。

add方法当elementData.length和elementCount相等时(容量满),会执行扩容操作,并将元素放到指定位置。

remove方法先判断下标是否越界,不越界会删除指定位置的元素,并且将数组重新拷贝合并。

同时它们有一个计数器modCount,在HashMap那边已经讲过,是用来fast-fail的,当多个线程同时操作,modCount不一致,就会抛出异常。

LinkedList    

public void add(int index, E element) {
        checkPositionIndex(index);
​
        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }
    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }
     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++;
    }
    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++;
    }
    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;
    }

可以看到,LinkedList的add方法开始也会校验指针位置,然后如果在末尾,就在链表最后面添加节点,否则就插入到链表指定位置上。

remove方法校验指针位置后,会删除指定位置上的node。

上面可以看到,对于add和remove,ArrayList数组要进行扩容或者删除部分长度,执行Sysetm.arraycopy方法,这是要消耗一些性能的,对于LinkedList,不需要维护容量问题,但是每次新增或者删除时,都会创建或删除一个Node对象,也是要消耗一些性能的。

扩容方法

ArrayList 扩容方法:    

private static final int DEFAULT_CAPACITY = 10;
    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity <= 0) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return minCapacity;
        }
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
    }
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE)
            ? Integer.MAX_VALUE
            : MAX_ARRAY_SIZE;
    }

Vector扩容方法    

public Vector() {
        this(10);
    }
    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity <= 0) {
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return minCapacity;
        }
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
    }
​
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

上面代码可以看到,对于ArrayList,如果不传入初始容量,默认为10。容量达到最值,执行扩容,每次扩容 int newCapacity = oldCapacity + (oldCapacity >> 1);

默认原容量的1.5倍。

Vector,如果不传入初始容量和自增容量,默认初始容量也为10.扩容时执行 int newCapacity = oldCapacity + ((capacityIncrement > 0) ?capacityIncrement : oldCapacity);

默认为原容量的2倍。

两者的最大值容量均为Integer.MAX_VALUE.

LinkedList由于是链表实现,没有容量限制。无需扩容

代码理解

构建一个有200W数据的ArrayList和LinkedList。

public static void main(String[] args){
        List<Integer> list = new ArrayList();
        //List<Integer> list = new LinkedList<Integer>();
        //Vector<Integer> list=new Vector<>();
        for (int i = 0; i < 2000000; i++) {
            list.add(i);
        }

        Integer tmp;
        long start=System.currentTimeMillis() ;   //ForEach
        for(Integer s:list){
            tmp=s;
        }
        System.out.println("foreach spend:"+(System.currentTimeMillis()-start));
        start = System.currentTimeMillis();
        for(Iterator<Integer> it = list.iterator(); it.hasNext();){
            tmp=it.next();
        }
        System.out.println("Iterator spend;"+(System.currentTimeMillis()-start));
        start=System.currentTimeMillis();
        int size=list.size();
        for(int i=0;i<size;i++){
            tmp=list.get(i);
        }
        System.out.println("for spend;"+(System.currentTimeMillis()-start));
    }

由于for循环遍历是随机访问,故LinkedList在数据量很大的情况下时间消耗会很长,基本不能接受。由于Vector线程安全,synchronized,故其整体效率会比ArrayList低些。在实际开发中,应用的ArrayList还是比较多的。

源码解读

ArrayList

先创建一个空的elem数组

执行list.add,先确定是否需要扩容,再执行赋值

先确定是不是空数组,第一次添加先赋一个最小值,即确定最小容量

第二次之后就不再执行if语句

记录当前集合被修改的次数

如果大于0说明elementData容量不够,调用grow扩容。如果这里的if条件不满足,则从这里就开始返回

使用扩容机制确定扩多大

第二行:数组大小赋给一个变量

第三行:原先数组长度加上原数组长度除以2,即扩大1.5倍(第二次及以后按照1.5倍)

第四五行:如果新容量小于这个小容量10,则就按照小容量来(也就是说,第一次 newCapacity = 10, )

第六七行:大于最大容量

第八行:扩容使用Arrays.copyOf(),可以保留原先的数据

调用之后,不断返回值,返回到add方法

Vector的扩容原理类似

主要区别在 grow() 方法,一个是扩大1.5倍,另一个是扩大2倍

  • 24
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值