阅读源码后对List集合实现原理的理解和总结

本文详细介绍了List接口的实现和继承结构,重点对比了ArrayList、Vector和LinkedList的区别与特点。ArrayList基于数组实现,查询速度快但增删慢,非线程安全。Vector线程安全,增删速度较慢。LinkedList采用双向链表,增删快但查询慢。文章还探讨了线程同步、扩容策略和适用场景。
摘要由CSDN通过智能技术生成

目录
  1 .List接口实现和继承结构图
  2 .ArrayList , Vector ,LinkendList的区别
  3 .ArrayList和Vector的区别
  4 .ArrayList
    4.1 初始化
    4.2 add
    4.3 set
    4.4 indexof
  5 .LinkedList
    5.1 LinkedList初始化
    5.2 LinkedList总结
  6 .Vector
    6.1 Vector总结

  想起前几天面试的时候,被问到List的时候,我居然不会。天天用,却没有了解原理,这就很伤心。今天就花了一些时间,把几个常用的List源码全部看了一遍。一些常用方法做了总结,对于这篇文章,我认为几个总结是非常重要的,可以应对常用的实践问题。

List接口实现和继承结构图

  java集合分为两大类,一个是Collection下,一个是map下的结构。由下图可知,List是继承Collection集合而来。(图由idea自动生成的,看的不清晰,请见谅)

这里写图片描述

我们就只详细介绍常用的LinkedList,Vector,ArrayList。

ArrayList , Vector ,LinkendList的区别

ArrayList:底层数据结构为数组,查询速度快,增删改速度慢,线程不安全的
Vector:底层数据结构为数组,线程安全的,底层方法都加了Synchronize关键字
LinkedList:底层数据结构为双向链表,增删速度快,查询稍慢

ArrayList和Vector的区别:

ArrayList:初始容量为10,超过10的时候会new一个50%的空间,将原来的放入这150%的空间内。
Vector:初始容量也是为10,超过10的时候会new一个100%的空间。

如果要实现Arraylist线程同步,可以通过下面方式:

1.如果多个线程同时访问一个 ArrayList 实例,而其中至少一个线程从结构上修改了列表,那么它必须 保持外部同步。(结构上的修改是指任何添加或删除一个或多个元素的操作,或者显式调整底层数组的大小;仅仅设置元素的值不是结构上的修改。)这一般通过对自然封装该列表的对象进行同步操作来完成。如果不存在这样的对象,则应该使用Collections.synchronizedList 方法将该列表“包装”起来。这最好在创建时完成,以防止意外对列表进行不同步的访问:

    List list = Collections.synchronizedList(new ArrayList(...)); 

2.如果集合中的元素数量大于当前集合数组的长度时,Vector的增长率是目前数组长度的100%,而ArryaList增长率为目前数组长度的50%。所以,如果集合中使用数据量比较大的数据,用Vector有一定优势。

  List接口对Collection进行了简单的扩充,它的具体实现类常用的有ArrayList和LinkedList。你可以将任何东西放到一个List容器中,并在需要时从中取出。ArrayList从其命名中可以看出它是一种类似数组的形式进行存储,因此它的随机访问速度极快,而LinkedList的内部实现是链表,它适合于在链表中间需要频繁进行插入和删除操作。在具体应用时可以根据需要自由选择。前面说的Iterator只能对容器进行向前遍历,而ListIterator则继承了Iterator的思想,并提供了对List进行双向遍历的方法。

ArrayList

  ArrayList就是动态数组,用MSDN中的说法,就是Array的复杂版本,它提供了动态的增加和减少元素,实现了Collection和List接口,可以灵活的设置数组的大小。要注意的是ArrayList并不是线程安全的,因此一般建议在单线程中使用ArrayList。

  由结构图可知ArrayList继承AbstractList 并且实现了List和RandomAccess,Cloneable, Serializable接口。
  
  ArrayList也采用了快速失败的机制,通过记录modCount参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。

初始化

//初始化容量为10
private static final int DEFAULT_CAPACITY = 10;
//初始化底层数据结构,为数组
transient Object[] elementData;
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) { //如果new一个实例,参数大于0,就new一个默认容量
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {//等于0,就new了一个空数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    //没有指定,就默认new一个空数组
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
        // 当c.toArray返回的不是object类型的数组时,进行下面转化。
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
        //用一个空数组替换
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

  并且用户可以往ArrayList中传入一个容器只要这个容器是Collection类型的。调用ArrayList(Collection<? extends E> c)接口的时候会将容器数组化处理并将这个数组值赋给Object数组。

add

 public boolean add(E e) {
        //检查满容了没有,满了就扩
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    public void add(int index, E element) {
        rangeCheckForAdd(index); // 判断index合不合法
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //把index后面的所有元素后移一位
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
    //指定从index位置开始,将集合的所有元素插入
    public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount

        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

set

    //覆盖之前的元素,返回之前的元素
    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

indexof

    //返回第一个出现该元素的索引
    public int indexOf(Object o) {
        if (o == null) { //因为ArrayList允许值为null
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

LinkedList

  • LinkedList继承于AbstractSequentialList,并且实现了Dequeue接口。
  • LinkedList包含两个重要的成员:header 和 size

    • header

           header是双向链表的表头,它是双向链表节点所对应的类Entry的实例。
       Entry中包含成员变量: previous, next, element。其中,previous是该节
       点的上一个节点,next是该节点的下一个节点,element是该节点所包含的值。
      
    • size

       是双向链表中节点的个数
      

双向链表,那么它的顺序访问会非常高效,而随机访问效率比较低

  既然LinkedList是通过双向链表的,但是它也实现了List接口{也就是说,它实现了get(int location)、remove(int location)等“根据索引值来获取、删除节点的函数”}。LinkedList是如何实现List的这些接口的,如何将“双向链表和索引值联系起来的”?

  实际原理非常简单,它就是通过一个计数索引值来实现的。例如,当我们调用get(int 
location)时,首先会比较“location”和“双向链表长度的1/2”;若前者大,则从链表头
开始往后查找,直到location位置;否则,从链表末尾开始先前查找,直到location位置。

LinkedList初始化

     transient int size = 0;

    transient Node<E> first;

    transient Node<E> last;

LinkedList初始化其实很简单,就是将节点个数设为0,定义一个指向头的frist的指针,一个指向尾的last指针。所以LinkedList底层其实是一个具有双端性质的链表

LinkedList的插入,删除就是一个具有双端性质链表的操作,可以参考我详细解释双端性质的双向链表的博文:双向链表代码实现和详解—java实现

LinkedList的查找,对输入的索引参数进行判断,当索引小于一半容量时,从头开始遍历。否则从尾部开始遍历。

LinkedList总结

  1. 从LinkedList的实现方式中可以发现,它不存在LinkedList容量不足的问题。
  2. LinkedList的克隆函数,即是将全部元素克隆到一个新的LinkedList对象中。
  3. LinkedList实现java.io.Serializable。当写入到输出流时,先写入“容量”,再依次写入“每一个节点保护的值”;当读出输入流时,先读取“容量”,再依次读取“每一个元素”。
  4. 由于LinkedList实现了Deque,而Deque接口定义了在双端队列两端访问元素的方法。提供插入、移除和检查元素的方法。每种方法都存在两种形式:一种形式在操作失败时抛出异常,另一种形式返回一个特殊值(null 或 false,具体取决于操作)。

LinkedList可以作为先进先出的队列,也可以作为后进先出的栈,对方法适当的调用。

切记,不可以随机访问LinkedList的元素,那样的花销太大

Vector

  Vector初始化,各种方法跟ArrayList实现的方法思想几乎一样,只是Vector在各方法中加了同步锁synchronized。所以Vector是线程安全的。
  跟ArrayList还有点不同的地方就是,Vector在创建的时候,如果没有使用泛型创建,那Vector的每个元素可以是不同的类型。
  

这里写图片描述
这里写图片描述
  还有ArrayList还有点不同的地方就是,在扩容的时候,扩容的大小是new一个100%的空间

  private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        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);
    }

    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总结

1 . 创建了一个向量类的对象后,可以往其中随意地插入不同的类的对象,既不需顾及类型也不需预先选定向量的容量,并可方便地进行查找。对于预先不知或不愿预先定义数组大小,并需频繁进行查找、插入和删除工作的情况,可以考虑使用向量类。
2 .Vector的构造器
public vector()

public vector(intinitialcapacity,int capacityIncrement)

public vector(intinitialcapacity)
使用第一种方法,系统会自动对向量对象进行管理。若使用后两种方法,则系统将根据参数initialcapacity设定向量对象的容量(即向量对象可存储数据的大小),当真正存放的数据个数超过容量时,系统会扩充向量对象的储存容量。

3,参数capacityIncrement给定了每次扩充的扩充值。当capacityIncrement为0时,则每次扩充一倍。利用这个功能可以优化存储。在Vector类中提供了各种方法方便用户使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值