List接口实现类-ArrayList、Vector、LinkedList集合深入学习以及源码解析

学习List接口实现类 ArrayList  Vector  LinkedList

List接口的实现类中最常用最重要的就是这三个:ArrayList、Vector、LinkedList。

JDK中这三个类的定义:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{

<span style="font-size:18px;">public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{</span>
<span style="font-size:18px;">public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    transient int size = 0;</span>

从这三个类定义就可以看出一些信息:

  • 三个都直接实现了AbstractList这个抽象类
  • ArrayList和Vector都实现了RandomAccess接口,而LinkedList没有,这是什么意思呢?在JDK 中,RandomAccess接口是一个空接口,所以它没有实际意义,就是一个标记,标记这个类支持快速随机访问,所以,arrayList和 vector是支持随机访问的,但是LinkedList不支持
  • serializbale接口表明他们都支持序列化。

下面详细说说List的这三个实现。

查看实现源码会发现这三个里面,ArrayList和Vector使用了数组的实现,相当于封装了对数组的操作。这也正是他们能够支持快速随机访问的原因,多说一句,JDK中所有基于数组实现的数据结构都能够支持快速随机访问。

ArrayList和Vector的实现上几乎都使用了相同的算法,他们的主要区别就是ArrayList没有对任何一个方法做同步处理,所以不是线程安全的;而Vector中大部分方法都做了线程同步所以是线程安全的。

LinkedList使用的是双向循环链表的数据结构。由于是基于链表的,所以无法法实现随机访问的,只能顺序访问,这也正式它没有实现RandomAccess接口的原因。

正式由于ArrayList、Vector和LinkedList所采用的数据结构不同,注定他们适用的是完全不同的场景。

通过阅读这几个类的源码,我们可以看到他们实现的不同。ArrayList和Vector基本一样,我们就用ArrayList和LinkedList做对比。

在末尾增加一个元素

ArrayList中的add方法实现如下:

 <span style="font-size:18px;">/**
     * Appends the specified element to the end of this list.
     *将元素添加到数组的最后
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }</span>

这个方法做两件事情,首先确保数组空间足够大,然后在数组末尾增加元素并且通过后++使得完成size+1

从这个代码可以看出,如果数组空间足够大,那么只是数组的add操作就是O(1)的性能,非常高效。

在看看ensureCapacityInternal这个方法的实现:

    private void ensureCapacityInternal(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    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);
    }

可以看出,如果数组空间不够,那么这个方法就会做数组扩容和数组复制操作,看第11行,JDK利用移位运算符进行扩容计算,>>1右移一位表示除2,所以newCapacity就是扩容为原来的1.5倍。

PS:这里的代码都是JDK1.7中的实现,JDK1.7对1.6的很多代码做了优化,比如上面这段扩容代码,在JDK1.6中第11行是直接除2,显然,移位运算要更高效。

在看看LinkedList中的add方法:

<span style="font-size:18px;">/**
     * Appends the specified element to the end of this list.
     * 将元素添加到最后
     * <p>This method is equivalent to {@link #addLast}.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        linkLast(e);
        return true;
    }</span>
 /**
     * Links e as last element.
     */
    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++;
    }
 Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }

从这段add代码可以看出,LinkedList由于使用了链表,所以不需要进行扩容,直接把元素加到链表最后,把新元素的前驱指向之前的last元素,并把last元素指向新元素就可以了。这也是一个O(1)的性能。

在任意位置插入元素

ArrayList中的实现如下:

<span style="font-size:18px;"> /**
     * Inserts the specified element at the specified position in this
     * list. Shifts the element currently at that position (if any) and
     * any subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    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++;
    }</span>

这段代码,首先先检查数组容量,容量不够先扩容,然后把index之后的数组往后挪一个,最后在index位置放上新元素。由于数组是一块连续内存空间,所以在任意位置插入,都会导致这个其后数组后挪一位的情况,需要做一次数组复制操作,很明显,如果有大量的随机插入,那么这个数组复制操作开销会很大,而且插入的越靠前,数组复制开销越大。

LinkedList中的实现:

<span style="font-size:18px;"> /**
     * Inserts the specified element at the specified position in this list.
     * Shifts the element currently at that position (if any) and any
     * subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }</span>
 /**
     * Inserts element e before non-null Node succ.
     */
    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++;
    }

这段代码,取到原先index处节点的前驱,变成新节点的前驱,同时把原先index变成新节点的后驱,这样就完成了新节点的插入。这个就是链表的优势,不存在数据复制操作,性能和在最后插入是一样的。

小结:从上面的源码剖析可以看出这三种List实现的一些典型适用场景,如果经常对数组做随机插入操作,特别是插入的比较靠前,那么LinkedList的性能优势就非常明显,而如果都只是末尾插入,则ArrayList更占据优势,如果需要线程安全,则使用Vector或者创建线程安全的ArrayList。

在使用基于数组实现的ArrayList 和Vector 时我们要指定初始容量,因为我们在源码中也看到了,在添加时首先要进行容量的判断,如果容量不够则要创建新数组,还要将原来数组中的数据复制到新数组中,这个过程会减低效率并且会浪费资源。

时间: 2024-10-14 10:12:19

List接口实现类-ArrayList、Vector、LinkedList集合深入学习以及源码解析的相关文章

Java中的容器(集合)之HashMap源码解析

1.HashMap源码解析(JDK8) 基础原理: 对比上一篇<Java中的容器(集合)之ArrayList源码解析>而言,本篇只解析HashMap常用的核心方法的源码. HashMap是一个以键值对存储的容器. hashMap底层实现为数组+链表+红黑树(链表超过8时转为红黑树,JDK7为数组+链表). HashMap会根据key的hashCode得到对应的hash值,再去数组中找寻对应的数组位置(下标). hash方法如下: static final int hash(Object key

TreeSet集合的add()方法源码解析(01.Integer自然排序)

>TreeSet集合使用实例 >TreeSet集合的红黑树 存储与取出(图) >TreeSet的add()方法源码     TreeSet集合使用实例 package cn.itcast_05; import java.util.TreeSet; /* * TreeSet:能够对元素按照某种规则进行排序. * 排序有两种方式 * A:自然排序 * B:比较器排序 * * TreeSet集合的特点:排序和唯一 * * 通过观察TreeSet的add()方法,我们知道最终要看TreeMap的

ArrayList,Vector,LinkedList的存储性能和特征

ArrayListh和Vector都是采用数组的方式来存储数据,其中ArrayList是线程不安全的,Vector是线程安全,所以ArrayList的性能要比Vector的性能好一些,而LinkedList采用的双向链表来实现数据的存储,而且是线程不安全的,而且LinkedList提供了一些方法,使得LinkedList可以被当做栈和队列来使用.因为ArrayList和Vector采用的数组的方式来实现存储数据,所以查询数据比较快捷,但是进行数据增删操作比较慢些,但是LinkedList采用的事

Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例

java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例 概要 上一章,我们学习了Collection的架构.这一章开始,我们对Collection的具体实现类进行讲解:首先,讲解List,而List中ArrayList又最为常用.因此,本章我们讲解ArrayList.先对ArrayLis

Java集合干货系列-(一)ArrayList源码解析

前言 今天来介绍下ArrayList,在集合框架整体框架一章中,我们介绍了List接口,ArrayList继承了AbstractList,实现了List.ArrayList在工作中经常用到,所以要弄懂这个类是极其重要的.构造图如下:蓝色线条:继承绿色线条:接口实现 正文 ArrayList简介 ArrayList定义 public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomA

Java 集合系列06之 Vector详细介绍(源码解析)和使用示例

概要 学完ArrayList和LinkedList之后,我们接着学习Vector.学习方式还是和之前一样,先对Vector有个整体认识,然后再学习它的源码:最后再通过实例来学会使用它.第1部分 Vector介绍第2部分 Vector数据结构第3部分 Vector源码解析(基于JDK1.6.0_45)第4部分 Vector遍历方式第5部分 Vector示例 转载:http://www.cnblogs.com/skywang12345/p/3308833.html 第1部分 Vector介绍 Vec

(转)Java 集合系列06之 Vector详细介绍(源码解析)和使用示例

概要 学完ArrayList和LinkedList之后,我们接着学习Vector.学习方式还是和之前一样,先对Vector有个整体认识,然后再学习它的源码:最后再通过实例来学会使用它.第1部分 Vector介绍第2部分 Vector数据结构第3部分 Vector源码解析(基于JDK1.6.0_45)第4部分 Vector遍历方式第5部分 Vector示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3308833.html 第1部分 Vector介

Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例

概要  前面,我们已经学习了ArrayList,并了解了fail-fast机制.这一章我们接着学习List的实现类——LinkedList.和学习ArrayList一样,接下来呢,我们先对LinkedList有个整体认识,然后再学习它的源码:最后再通过实例来学会使用LinkedList.内容包括:第1部分 LinkedList介绍第2部分 LinkedList数据结构第3部分 LinkedList源码解析(基于JDK1.6.0_45)第4部分 LinkedList遍历方式第5部分 LinkedL

Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例

java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例 概要  和学习ArrayList一样,接下来呢,我们先对LinkedList有个整体认识,然后再学习它的源码:最后再通过实例来学会使用LinkedList.内容包括:第1部分 LinkedList介绍第2部分 LinkedList数