浅谈ArrayList、Vector和LinkedList

ArrayList

ArrayList就是数组列表,是一个动态数组,其容量能自动增长。
特点:查询效率高,增删效率低,线程不安全,日常使用频率高。

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    //默认容量
    private static final int DEFAULT_CAPACITY = 10;

    //new ArrayList<>(0)时,就会调用this.elementData=EMPTY_ELEMENTDATA 
    private static final Object[] EMPTY_ELEMENTDATA = {};

    //new ArrayLisT<>()时,即获得这个空数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    //数据都存储在Object[] elementData中
    transient Object[] elementData; // non-private to simplify nested class access

    //size,指的是elementData中含有元素的数量,而不是数组的大小
    private int size;
..........
}

ArrayList的构造方法

public ArrayList()//构造一个空数组,在第一次add添加元素的时候,会把该数组扩容成容量为(DEFAULT_CAPACITY=10指定)的数组
public ArrayList(int initialCapacity)//构造一个指定容量大小的数组
public ArrayList(Collection<? extends E> c)//构造一个包含指定 collection 的元素的列表

ArrayList新增方法

1. public boolean add(E e) //在数组的尾部增加元素
2. public void add(int index, E element)//将>=index位置的元素往后移动一位,然后在指定位置index处插入该元素
3. public boolean addAll(Collection<? extends E> c)//在数组尾部添加一个Collection中的所有元素
4. public boolean addAll(int index, Collection<? extends E> c)//原理同2

这里详解一下新增逻辑

    public boolean add(E e) {
        //校验容量是否够,如果容量不够会进行扩容*
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        
        //校验完(不足扩容后),再把该元素加入到数组尾部,数组元素个数(size)+1
        elementData[size++] = e;
        return true;
    }

    //下面看一下扩容方法(minCapacity指的是要容纳新增 元素 后最小所需的容量)
    private void grow(int minCapacity) {
        // 获取当前容量大小
        int oldCapacity = elementData.length;
        // 获取增长1.5倍后容量的大小
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 如果扩大1.5倍后容量 还不足够*
        if (newCapacity - minCapacity < 0)
            //则新数组的容量大小直接确定为minCapacity
            newCapacity = minCapacity;

        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        //扩容,Arrays.cpoyOf会返回一个新的数组(保留原来数组的值)
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

常用方法

在数组的尾部增加元素

  • public boolean add(E e)

在指定位置处插入元素

  • public void add(int index, E element)

在数组尾部添加该Collection中的所有元素

  • public boolean addAll(Collection<? extends E> c)

在指定位置插入Collecton中的所有元素

  • public boolean addAll(int index, Collection<? extends E> c)

移除此列表中的所有元素(底层实现为for循环遍历数组,给所有值=null,size=0)

  • public void clear()

克隆数组列表(tip:浅克隆

  • public Object clone()

是否包含该元素

  • public boolean contains(Object o)

返回数组列表中指定位置的元素

  • public E get(int index)

返回数组列表中首次出现指定元素的下标

  • public int indexOf(Object o)

返回数组列表中最后一次出现指定元素的下标

  • public int lastIndexOf(Object o)

数组列表中的元素是否为0

  • public boolean isEmpty()

删除指定位置
删除指定元素
删除指定范围[fromIndex,toIndex)内的元素

  • public E remove(int index)
  • public boolean remove(Object o)
  • protected void removeRange(int fromIndex, int toIndex)

替代指定位置的元素

  • public E set(int index, E element)

以数组的形式返回数组中所有的元素

  • public Object[] toArray()

Vector

VectorArrayList类似, 区别在于Vector是同步类(synchronized)。故线程安全
特点:线程安全,有增长因子这个概念

构造方法

1. public Vector()
2. public Vector(int initialCapacity)
3. public Vector(Collection<? extends E> c)
//前面三个都和ArrayList的类似,多了第四个构造方法,其中第二个参数capacityIncrement是增长因子
4. public Vector(int initialCapacity, int capacityIncrement)

扩容源码解析

新增逻辑和ArrayList逻辑相同,但是扩容代码有些不同
在JDK1.8中,ArrayList的扩容是1.5倍
`下方源码可知,如果设置了增长因子,则扩容容量=旧容量+增长因子,否则=2*旧容量

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //如果设置了增长因子,则扩容容量=旧容量+增长因子,否则=2*旧容量
        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);
    }

LinkedList

双向链表,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于ArrayList.
特点:双向链表,继承了Deque接口,所以有peek、pool、pop、push等方法

链表在get和set方面比较耗时

    public E get(int index) {
        //检查index是否溢出
        checkElementIndex(index);
        //返回指定位置元素
        return node(index).item;
    }

    //查找index位置的Node
    Node<E> node(int index) {
        // assert isElementIndex(index);
        // index在链表的左半侧
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
        // index在链表的左半侧
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

由上面源码可知,get方法是从0遍历到index,或者从size-1遍历到index的,耗时都是非常高的。

posted @ 2020-08-20 15:21  jealous-boy  阅读(150)  评论(0编辑  收藏  举报