集合框架——ArrayList与LinkedList、Vector的区别

目录

一、相同之处

二、不同之处

1、ArrayList与LinkedList区别

2、ArrayList与Vector区别

三、使用场景


List接口是Collection接口下的子接口List中的元素是有序的,可以重复的,List接口的主要实现类有三个:ArrayList、LinkedList和Vector。

一、相同之处

  • 都是List接口的实现类,因此他们具有List列表的共同特性,他们是有序的,可以容纳重复的元素,允许元素为null。
  • 都可以使用Collection下的Iterator接口的iterator()方法和Iterator接口的子接口ListIterator的listIterator()方法,并且Iterator和ListIterator迭代器都具有fail-fast机制。

二、不同之处

1、ArrayList与LinkedList区别

(1)底层数据结构不同,ArrayList和Vector都是基于动态数组实现的,LinkedList是基于双向链表实现的。

ArrayList是由Object类型的数组实现的,如果传入初始容量参数,则创建一个initialCapacity大小的数组,如果没有传入initialCapacity,那么创建一个默认大小(10)的数组。

(2)实现接口不同,除了都有的Serializable、Cloneable、Iterable、Collection和List,ArrayList还实现了RadomAccess接口,LinkedList还实现了Deque接口

(3)不同操作效率不同,对于随机访问get和set效率不同,ArrayList优于LinkedList,因为LinkedList要移动指针遍历得到待查找的node。对于新增和删除操作add和remove效率不同,LinedList比较占优势,因为ArrayList要移动数据。 

List的操作效率
  get和set add remove
ArrayList O(I) O(n) O(n)
LinkedList O(n) O(I) O(n)

这一点要看实际情况的。若只对单条数据插入或删除,ArrayList的速度反而优于LinkedList。但若是批量随机的插入删除数据,LinkedList的速度大大优于ArrayList. 因为ArrayList每插入一条数据,要移动插入点及之后的所有数据。

ArrayList的get

public E get(int index) {
    rangeCheck(index);
    return elementData(index);
    }

E elementData(int index) {
    return (E) elementData[index];
    //elementData是用于存储元素的动态数组
    }

可以看出ArrayList的get就是从数组进行读取,效率为O(1)。

LinkedList的get

 public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }
 
   Node<E> node(int index) {
        // assert isElementIndex(index);
        if (index < (size >> 1)) {  //判断index是否超过size的一般,如果不是则从head开始遍历
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {       //否则从tail开始遍历
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

可以看出,虽然LinkedList实现了List接口,因此必须实现get(index)方法,但是get()方法是使用迭代器遍历来实现的,因此效率为O(n)。 

ArrayList的add和remove:通过System.arraycopy来实现

add操作:

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

remove操作:

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; // clear to let GC do its work
        return oldValue;
    }

可以看出ArrayList的add和remove操作都是通过System.arraycopy来实现的,需要移动index后每一个元素,操作效率为O(n)。

LinkedList的add和remove:通过link和unlink来实现

add操作:

public void add(int index, E element) {
        checkPositionIndex(index);  //查看index是否在链表范围内

        if (index == size)
            linkLast(element); 
        else
            linkBefore(element, node(index));
    }

public void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);   //Node(NOde <E> pre, E element, Node<E> next)
        last = newNode;
        if (l == null)  //如果链表还是空链表,那么把当前节点设为头节点
            first = newNode;
        else
            l.next = newNode;   //否则把当前节点添加到last后面
        size++;
        modCount++;  //用于fail-fast机制
    }
 
public void linkBefore(E e, Node<E> succ) {  //把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)  //如果succ是头结点,那么把当前节点设置为头结点
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

可以看出,由于add操作只需要修改双向链表的next和prev指针的地址,因此操作效率为O(1)。

remove操作:

//删除index下标位置的元素
public E remove(int index) {
        checkElementIndex(index); //检查index是否在链表范围内
        return unlink(node(index));
    }

//删除元素o,因为LinkedList中的元素是可以重复且为null的,所以要遍历整个表,把所有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;
                }
            }
        }
 
public 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++;   //用于fail-fast机制
        return element;
    }

可以看出,虽然unlink操作的效率是O(1),但是不论是索引(index)删除还是元素(object)删除都需要遍历最多半个链表(索引删除调用node方法进行了遍历查找)才能找到要删除的node,因此效率为O(n)

(4)占用内存不同,LinkedList 比 ArrayList 需要更多的内存,因为链表除了数据本身还需要存储next指针指向下一个node的地址。

 

 

2、ArrayList与Vector区别

(1)同步性不同,LinkedList和ArrayList是不同步的,Vector是同步的(使用synchronized进行强同步)。

(2)扩容方法不同,LinkedList基于链表实现,容量是无限的;ArrayList和Vector是基于数组实现的,容量是有限的,在需要的时候要扩容,但是ArrayList和Vector的扩容方式稍微不同,ArrayList是newCapacity=oldCapacity*3/2-1,而Vector是与“增长系数有关”,若指定了“增长系数”,且“增长系数有效(即大于0)”;那么,每次容量不足时,“新的容量”=“原始容量+增长系数”。若增长系数无效(即,小于/等于0),则“新的容量”=“原始容量 x 2”。

 

ArrayList的动态数组的扩容

因为数组的大小是固定的,而ArrayList的大小是动态可调的,那么他是如何实现的呢?首先,由ensureCapacity()方法确认ArrayList容量,然后由grow()方法扩容。

 //ensureCapacity()方法用于确认当前的elementData[]数组是否能容纳minCapacity的元素
public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            // any size if not default element table
            ? 0
            // larger than default for default empty table. It's already
            // supposed to be at default size.
            : DEFAULT_CAPACITY;

        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }
//ensureExplicitCapacity()方法用于扩容,也就是说调用这个函数说明明确的要扩容,扩容对数组的结构进行了修改,因此modcount加1
private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
//数组的扩容:
//首先计算需要的容量,newCapacity = oldCapacity + (oldCapacity >> 1),说明newCapacity是oldCapacity的大概3/2,如果
//确切一点,就是newCapacity = 3/2*oldCapacity-1;
//然后,如果newCapacity<minCapacity,那么newCapacity设为minCapacity
//最后调用Array.copyOf()方法把旧数组复制到新数组中
 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);
    }

这里我们来看看Array.copyOf( )是怎么操作的:

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        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;
    }

可以看到Array.copyOf( )方法中,是新建了一个数组,然后把就数组拷贝在新数组中,此外,如果新数组和旧数组的类型不同,还需要把旧类型轻质转换为新类型。那么可以看出,ArrayList的扩容是通过新建一个数组来实现的。

 

三、使用场景

如果涉及到“队列”、“栈”和“链表”等场景,那么可以使用List下的实现类,具体要根据需求和各个实现类的特点选择。

  • 如果需要快速随机访问,那么使用ArrayList
  • 如果需要频繁的插入和删除中间节点,那么使用LinkedList
  • 如果需要在多线程中同步,那么使用Vector,但是由于历史原因Vector基本已经被弃用,可以用java.util.Concurrent包下的concurrentLinkedDeque或者concurrentQueue来代替Vector
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章