集合类学习之List

List学习

List接口继承了collection接口,其常用的实现类有ArrayList、Vector、LinkedList、CopyOnWriteArrayList
在这里插入图片描述

一、ArrayList

特性

1、ArrayList类实现了可变的数组,允许保存所有元素,包括null。
2、可以根据索引位置对集合进行快速的随机访问,查询时间复杂度为O(1)。
3、向指定位置插入或删除对象时速度较慢,时间复杂度为O(n),因为数组在内中的存储是连续的,插入或删除,需要移动元素。
4、线程不安全

底层实现原理

ArrayList的创建,常用两种方式

1、无参构造方法,创建一个默认capacity为10的Object数组

private static final int DEFAULT_CAPACITY = 10;//默认初始化容量
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//一个默认容量且为空的Object数组
transient Object[] elementData; //ArrayList的底层数据结构,一个Object数组
/**
 * Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {//无参构造方法
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

2、有参构造方法,参数为一个int整形,用于自定义数组的容量

private static final Object[] EMPTY_ELEMENTDATA = {};//一个空的数组List
public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {//当initialCapacity大于0时,创建自定义容量数组
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {//当initialCapacity等于0时,则将EMPTY_ELEMENTDATA空列表赋值给elementData
            this.elementData = EMPTY_ELEMENTDATA;
        } else {//当小于0时则抛出非法参数异常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

ArrayList中添加对象

//数组的大小
private int size;
//添加对象
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  //确保对象数组elementData有足够的容量,可以将新加入的元素e加进去
    elementData[size++] = e;//向数组中添加对象
    return true;
}

ArrayList的扩容机制

当添加对象时,add(E e)方法内部调用了ensureCapacityInternal(size + 1)法以确保容量足够,执行以下方法来判断是否需要扩容

 private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)//如果minCapacity 大于当前数组长度则进入扩容
        grow(minCapacity);
}

通过Arrays.copyOf(T[] original, int newLength)方法,复制原数组来实现扩容操作

//扩容
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;//原数组容量
    int newCapacity = oldCapacity + (oldCapacity >> 1);//oldCapacity>>1为oldCapacity/2,新的数组扩容量为原数组的1.5倍
    if (newCapacity - minCapacity < 0)//如果扩容后还不满足最小容量要求,则将最小容量赋值给newCapacity
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)//如果新newCapacity大于MAX_ARRAY_SIZE,则newCapacity赋值为Integer.MAX_VALUE
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);//复制出新的容量的数组,并赋值给自己elementData 
}

二、Vector

特性

1、实现原理与ArrayList相同。
1、线程安全的,因其方法是被synchronized修饰的,线程同步方法
2、因同步锁的原因,效率比ArrayList低

底层实现原理

Vector的创建

其三个构造方法其实都是调用:public Vector(int initialCapacity, int capacityIncrement)

//1.无参
public Vector() {
    this(10);//默认初始容量玉arraylist相同10,调用构造方法2
}
//2.一个参数
public Vector(int initialCapacity) {
    this(initialCapacity, 0);//自定义的初始化容量,0为扩容比例,调用构造方法3
}
//3.两个参数
public Vector(int initialCapacity, int capacityIncrement) {//自定义初始化容量,及扩容比例
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    this.elementData = new Object[initialCapacity];//创建Object数组
    this.capacityIncrement = capacityIncrement;//定义扩容比例
}

Vector的扩容机制

方式与ArrayList一样数组复制方式,默认扩容2倍

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                     capacityIncrement : oldCapacity);//当capacityIncrement大于0时,为自定义扩容比例;当capacityIncrement<=0时扩容比例为2倍
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

三、LinkedList

特性

1、采用链表结构保存对象,插入和删除效率较高,因为链表每个节点为单个对象,插入和删除只需要修改节点上的指向,不需要像数组似的还要移动其他元素。
2、查询效率比较低,因为需要遍历来查找

底层实现原理

底层实现是一个双向链表,数据结构为LinkedList的一个静态内部类Node节点类,该节点储存了三个内容:数据的信息、前一个节点的指向、下一个节点的指向
在这里插入图片描述

//链表中节点个数
transient int size = 0;
//链表中第一个节点
transient Node<E> first;
//链表中最后一个节点
transient Node<E> last;
//链表的的元素节点对象,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;
    }
}

查询效率慢的原因,get(index)查询需要从头或从尾遍历到该下标位置

Node<E> node(int index) {
    // assert isElementIndex(index);
    if (index < (size >> 1)) {//如果index小于size的一半,则从第一个元素向后遍历
        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;
    }
}

四、CopyOnWriteArrayList

特性

1、与Vector不同,使用的是ReentrantLock锁
2、效率要比Vector高,添加删除上锁,get不加锁
3、底层也是数组,先复制一个length+1的数组在添加

底层实现原理

先来看下它的 add 方法源码:
添加元素时,先加锁,再进行复制替换操作,最后再释放锁

public boolean add(E e) {
	//加锁
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    	//获取原数组
        Object[] elements = getArray();
        int len = elements.length;
        //复制一个长度为length+1的新数组
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        //在新数组中添加元素
        newElements[len] = e;
        //set替换原始集合为新集合
        setArray(newElements);
        return true;
    } finally {
    	//释放锁
        lock.unlock();
    }
}

再看看get方法:
并没有加锁操作

private E get(Object[] a, int index) {
    return (E) a[index];
}
public E get(int index) {
    return get(getArray(), index);
}

从源码中,看到add操作中使用了重入锁,添加值的操作并不是直接在原有数组中完成,而是复制原始数组,然后将值插入到新的数组中,最后用新数组替换原数组。使用这种方式,在add的过程中旧数组没有得到修改,因此写入操作不影响读取操,另外,数组定义private transient volatile Object[] array,其中用volatile修饰,保证内存可见性,读取线程可以马上知道这个修改。

五、总结

1、ArrayList查找效率更高,尾部添加效率也还可以,但是指向索引位置的插入及删除效率比较低。
2、LinkedList执行插入及删除操作室效率高,但是查找的效率比较低。
3、ArrayList和LinkedList都是非线程安全的。
4、Vector是线程安全的,方法被synchronized修饰的,线程同步方法。
5、Vector的底层都是数组,默认初始化容量都是10,ArrayList的扩容为1.5倍,Vector默认扩容为2倍且可以自定义扩容数量。
6、CopyOnWriteArrayList线程安全的,添加时先加锁,复制后添加,再替换原来的集合,读取不加锁,性能优于Vector

发布了2 篇原创文章 · 获赞 0 · 访问量 149

猜你喜欢

转载自blog.csdn.net/Pxiaok/article/details/105116198