List接口底层实现类

一、java集合类分类

java集合框架分为俩种,单列集合框架Collection,和双列集合框架Map

1.单列集合框架结构

Collection接口:单列集合,用来存储一个一个的对象

  • |—-List接口:存储序的、可重复的数据。 –>“动态”数组
    • |—-ArrayList、LinkedList、Vector
  • |—-Set接口:存储无序的、不可重复的数据 –>高中讲的“集合”
    • |—-HashSet、LinkedHashSet、TreeSet

本文将对List接口进行解析

对应图示:

单列集合框架

实线为直接继承或实现,虚线为间接继承或实现

二、ArrayList

1.jdk1.7的情况

​ ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementData

  • list.add(123);//elementData[0] = new Integer(123);

  • list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。

  • 默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。

  • 结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)

2.jdk1.8的情况

​ 由于现主流开发都是1.8版本的jdk,所有以下对ArrayList进行深入解读

3.继承情况

使用idea自带的可视化工具查看继承与实现情况,可以清楚的看到ArrayList间接的实现了Collection接口

继承情况

4.创建ArrayList

​ 接下来我们来看一下创建一个ArrayList容器会发生什么

创建ArrayList

当我用debug创建一个arrayList时 返回的是一个空的,长度为0的数组

我们跟进去构造函数

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
transient Object[] elementData; //一个是Object[]对象,transient关键词是指该对象不需要被序列化
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//一个空的Object[]对象

与jdk7版本的区别是 jdk7创建后就创建一个长度为10的数组,但jdk8并没有,更加节省内存

5.添加数据

arrayList.add(123);

我们用debug跟进源码

因为存入123是基本数据类型,这里有一个自动装箱的过程(装换为Integer类型)

后面就是进入add方法

size默认初始化为0,进入ensureCapacityInternal方法

private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
            //max方法取俩个参数最大的一个(这里DEFAULT_CAPACITY为10),将最大的值赋给minCapacity
        }

        ensureExplicitCapacity(minCapacity);
    }

进入ensureExplicitCapacity(minCapacity)方法

private void ensureExplicitCapacity(int minCapacity) {
        modCount++;//默认为0 

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)//判断最小容量减去原数组长度是否大于0
            grow(minCapacity);
    }   
进入grow(minCapacity)方法

 private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;//记录老容量长度 此时elementData长度为0
        int newCapacity = oldCapacity + (oldCapacity >> 1);//定义新容量值,老容量长度右移一位后加再上老容量长度,而右移一位是取数值的一半,这里就决定了ArrayList以后进行扩容都是对原数组扩容1.5倍
        if (newCapacity - minCapacity < 0)//0 - 10 小于0 所有进入 
            newCapacity = minCapacity;		//新容量长度就为10
        if (newCapacity - MAX_ARRAY_SIZE > 0) //判断此容量会不会超过最大长度(Integer.MAX_VALUE - 8)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);//调用Arrays工具类,对数组进行扩容,原理为将旧数组数据复制到新数组
    }

三、LinkedList

1.继承情况

可以看到不仅间接的实现了List接口,还间接的实现了队列Queue的接口

2.创建LinkedList

LinkedList linkedList = new LinkedList();

通过new的方式只是初始化LinkedList容器,底层双向链表结构还未创建。LinkedList Node类型的first和last属性,默认为null

3.添加数据

linkedList.add(123);

进入linkLast中

//在linkedList里面有first和last属性
transient Node<E> first;
transient Node<E> last;

void linkLast(E e) {
    final Node<E> l = last; //将原料的last对象赋值给l
    final Node<E> newNode = new Node<>(l, e, null);//Node为一个静态内部类 在下面:作用是初始化Node对象(链表对象,prev为头指针,next为尾指针,element为数据)
    last = newNode;//将有数据的newNode赋值给last
    if (l == null)//判断是否是第一个节点,如果是
        first = newNode;	//将NewNode直接赋值给尾节点
    else
        l.next = newNode;	//不是的话。将节点添加到l.next 相当于将链表连接起来
    size++;
    modCount++;
}

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

四、Vector

jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。
在扩容方面,默认扩容为原来的数组长度的2倍。

1.继承情况

2.创建Vector

让我们跟进去构造函数

 public Vector() {
        this(10);
    }

再往下跟,调用一个参数的构造函数
public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }
    
再往下跟,调用凉参数的构造函数
public Vector(int initialCapacity, int capacityIncrement) {
        super();		//显示调用父构造函数
        if (initialCapacity < 0)	//为0就报错,参数传进来为10
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity]; //创建并初始化了对象数组
        this.capacityIncrement = capacityIncrement;
    }
    
上面俩个属性定义如下:
protected Object[] elementData;
protected int elementCount;

此版本为jdk8,jdk7的Vector也是创建一个Vector容器就默认初始化长度为0,与jdk7的ArrayList一样,但是人家开发者对ArrayList进行了改进,使用懒加载,可能Vector要被放弃了吧!!

接下来讲为什么会放弃Vector

3.添加数据

接下来跟进源码

elementCount默认为0。

可以看到有一个ensureCapacityHelper方法,此方法是校验长度是否足够,并去扩容的方法,如下:

private void ensureCapacityHelper(int minCapacity) {
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);//扩容方法
}

但此处因为默认初始化长度为10,所以这里不会有扩容的需求。

稍等!Vector不是挺好的嘛,为什么要放弃他??因为…

public synchronized boolean add(E e)
public synchronized E remove(int index) 
public synchronized boolean containsAll(Collection<?> c)
public synchronized boolean addAll(int index, Collection<? extends E> c)

因为他的方法上全加了synchronized,加上这个字段他就是一个线程安全的方法,而他的全部方法都是线程安全方法,又众所周知,线程安全效率大大减低,所以连开发jdk人员都懒得救他,毕竟,出现了更好的人,老情人全忘掉了~

五、总结

上述介绍了List接口的全部实现类,如果数据不需要线程安全,则不要去考虑使用Vector,而因为ArrayList和LinkedList底层实现原理不同,他们使用的地方也有所不同,ArrayList底层使用的是数组实现,因此在需要频繁的插入和删除数据时效率会大大减低,而数组有一个好处,就是有下标,下标最直接好的好处就是查询速度非常的快,而LinkedList底层使用的是双向链表实现,所以在频繁的插入和删除数据时,有超高性能的表现,但是在查询的速度远远比不上ArrayList。

记重点:ArrayList数组实现,每次扩容1.5倍,使用懒加载,首次扩容长度为10

​ LinkedList双向链表实现