List源码解析

点击上方「10分钟编程」关注我呦 

让我们在一起每天「博学」一点点,成为更好的自己!

List源码解析

本篇文章有点长,所以先列个目录

  • List源码解析

    • 1、ArrayList

    • 2、LinkedList

    • 3、vector

    • 4、总结

在上一篇文章中,我们对Collection接口有个一个总体的了解,这篇文章我们将会深入的学习实现Collection的一个重要子接口---List

首先,我们需要看一下List的「继承链」,它有哪些具体的实现类,

在这里,我们会主要讲解ArrayList、LinkedList和Vector

1、ArrayList

ArrayList使用非常广泛,它也是我们使用频率最高的一个集合(很多情况下不考虑场景直接使用),

  • 数据结构

ArrayList的底层数据结构是一个存储Object类型的数组,我们对ArrayList的操作都是基于数组的。

1.1成员变量

long serialVersionUID:标识码,用于序列化和反序列化

transient object[ ] elementData:不可被序列化的数组,只能存在于内存中

int size:ArrayList中存放元素的个数,默认为0

int DEFAULT_CAPACITY:初始化的数组的容量,默认为10

object[ ] EMPTY_ELEMENTDATA:当ArrayList为空时,将该数组赋值elementData数组

object[ ] DEFAULTCAPACITY_EMPTY_ELEMENTDATA:当构造器未指明数组的长度时,默认使用该数组

final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;最大数组的容量,-8的原因是因为要去掉header Words,防止内存溢出

private static final long serialVersionUID = 8683452581122892189L;
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
private int size;
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
1.2构造方法

AyyayList():构造一个初始容量为0的空列表

public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

ArrayList(int initialCapacity):以一个确定的容量构造数组列表,当初始容量>0,直接构造该容量的列表,当初始容量=0,将 EMPTY_ELEMENTDATA赋给elemData,初始容量<0,则抛出异常

public ArrayList(int initialCapacity) {
    //当初始容量>0,直接构造该容量的列表
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
      //当初始容量=0,将 EMPTY_ELEMENTDATA赋给elemData
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
            //初始容量<0,则抛出异常
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

ArrayList(Collection<? extends E> c):构造一个包含指定集合元素的列表,其顺序由集合的迭代器返回

 public ArrayList(Collection<? extends E> c) {
     //将集合转为数组
        elementData = c.toArray();
     //判断数组的长度
        if ((size = elementData.length) != 0) {
            // 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;
        }
    }
1.3 add方法

add(E e):在列表的尾部增加元素e,我们具体分析下add方法的执行流程

在添加元素e时,先调用函数ensureCapacityInternal(size + 1),目的保证数组内的容量可用,它会首先计算传入的容量与默认容量的的大小,取最大值,如果传入的容量比默认容量大,则需要扩容,扩为元容量的1.5倍,扩容后继续比较取最大值。

对下面的代码进行详细了注释,读者可按照步骤「一二三四五」的顺序理解即可

  public boolean add(E e) {
      //步骤一
      //size为添加前数组的容量
        ensureCapacityInternal(size + 1);  // Increments modCount!!
      //添加元素到相应位置,元素数量+1 
      elementData[size++] = e;
        return true;
    }
//步骤二
//确保内部的容量
 private void ensureCapacityInternal(int minCapacity) {
     //步骤三、四
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
//步骤三
//计算最小需要的空间
 private static int calculateCapacity(Object[] elementData, int minCapacity) {
     //判断传入的容量与原有容量的大小,返回两者之间最大的
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
//步骤四
//判断是否需要扩容
//如果最小需要的空间比原有的elementData的内存空间要大,则扩容
   private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
       
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
//步骤五
//扩容
 private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
     //扩容为原有容量的1.5倍
        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);
    }

add(int index,E element):在确定的位置插入元素e,首先判断index位置是否越界,然后检查数组容量是否够用,不够则需要进行扩容,最后进行数组的复制来完成数组的添加

  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++;
    }
1.4 set方法

set(int index, E e):将指定位置的元素更新为e,首先它会先检查index是否越界,找到该位置的替代元素并替换成新元素,返回被替换的值。

 public E set(int index, E element) {
        rangeCheck(index);
        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

1.5 get方法

get(int index):获取指定位置的元素值

  public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }
1.6 remove方法

remove(int index):删除指定位置的的元素,将指定下标后面的的一位到数组的末尾全部元素向前移动一个单位,并且把数组最后一个元素设置为null

 public E remove(int index) {
     //检查是否越界
        rangeCheck(index);
        modCount++;
        E oldValue = elementData(index);
//需要移动元素的个数
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }
1.7indexOf(Object o)

indexOf(Object o):返回指定元素在列表中首次出现的索引位置,如果此列表不包含该元素,则返回-1

   public int indexOf(Object o) {
       //ArrayList中的值可以为null
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                //采用equals()方法比较
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }
1.8其它方法

contains(Object o):判断列表中是否包含对象o

public boolean contains(Object o) {
        return indexOf(o) >= 0;

Object[] toArray():转换成对象数组

public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }
1.9总结
  • ArrayList的底层使用数组实现的,可以进行动态扩容,遍历和访问元素时,效率很高,可以存放null值

  • 插入和删除元素时效率较低,线程不安全

2、LinkedList

LinkedList是一个用「双向链表」实现的List,除此之外,它还可以作为队列和栈使用,为了接下更好的分析LinkedList,我们先回顾下双向链表的一些常用操作

  • 双向链表的中间删除

S->pre->next=S->next
S->next->pre=S->pre
  • 双向链表的中间插入

D->pre=B
D->next=C
C->pre=D
B->next=D
  • 双向链表的末尾删除

P->pre->next=P->next
  • 双向链表的末尾添加

B->next=C
C->pre=B
  • 链表的遍历

PersonNode temp = headNode;
        while (true) {
            if (temp.next == null) {
                break;
            }
            temp = temp.next;
            System.out.println(temp);
        }

复习了链表的基本操作后,我们来学习LinkedList的源码

2.1成员变量

int size:结点个数

Node first:指向头结点指针

Node last:指向尾结点的指针

transient int size = 0;
transient Node<E> first;
transient Node<E> last;
2.2构造方法

LinkedList():构造一个空链表

 public LinkedList() {
    }

LinkedList(Collection<? extends E> c):根据指定集合c构造LinkedList,先构造一个空linkedlist,在把指定集合c中的所有元素添加到linkedlist,如果c为null,则抛出异常

public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }
2.3link..方法

linkFirst(E e):在表头插入元素

 private void linkFirst(E e) {
     //使结点f指向原来的头结点
        final Node<E> f = first;
     //新建结点newNode,结点的前指针指向null,后指针指向原来的头结点
        final Node<E> newNode = new Node<>(null, e, f);
     //头指针指向新结点
        first = newNode;
     //如果原来的头结点为null,更新尾指针,否则使原来的头结点f的前置指针指向新的头结点newNode
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }

linkLast(E e):在链表尾部插入元素,分析方法和linkFirst类似

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

linkBefore(E e,Node<E> succ):在非空节点succ前插入元素e

 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++;
    }
2.4unlink...方法

unlinkFirst(Node<E> f):删除头f并返回

 private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;
        final Node<E> next = f.next;
        f.item = null;
        f.next = null; // help GC
        first = next;
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }

unlinkLast(Node<E> l):删除尾结点l并返回

  private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        final E element = l.item;
        final Node<E> prev = l.prev;
        l.item = null;
        l.prev = null; // help GC
        last = prev;
        if (prev == null)
            first = null;
        else
            prev.next = null;
        size--;
        modCount++;
        return element;
    }

unlink(Node<E> x):删除指定结点x

E unlink(Node<E> x) {
 ...... 
}
2.5 add方法

add(E e):在列表尾部插入元素e

 public boolean add(E e) {
        linkLast(e);
        return true;
    }

add(int index,E element):在指定的位置插入元素

public void add(int index, E element) {
    //检查是否越界
        checkPositionIndex(index);
//判断index与列表的大小
        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }
2.6 remove方法

remove(Object o):移除首先出现对象o的元素

  public boolean remove(Object o) {
       ......
    }

remove(int index):在指定位置删除元素并返回

 public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

remove():移除表头元素

 public E remove() {
        return removeFirst();
    }
2.7 get方法

get(int index):获取指定位置(index)的元素

 public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }
2.8 set方法

set(int index,E element):在指定位置更新元素,返回被替换的元素

 public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }
2.9其他方法
//获取列表的第一个元素
public E getFirst() {......}
//获取列表的最后一个元素
public E getLast() {......}
//从列表中移除并返回第一个元素
 public E removeFirst() {......}
//从列表中移除并返回最后一个元素
public E removeLast() {......}
//在表头插入元素
public void addFirst(E e) {
        linkFirst(e);
    }
//在表尾插入元素
 public void addLast(E e) {
        linkLast(e);
    }
//列表中是否包含对象o
public boolean contains(Object o) {
        return indexOf(o) != -1;
    }
//列表中元素的数量
public int size() {
        return size;
    }
2.10总结

LinkedList的底层数据结构是用链表实现的,

「要重点理解LinkedList中的link...方法(1.3)和unlink...方法(1.4),因为我们常用的add,set,get,remove方法都是在此基础上调用得来的。」

LinkedList内的元素可重复,增添、删除效率高,查找效率低,它是线程不安全的,如果要使得线程安全,可以使用Collection中的方法

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

3、vector

vector的底层数据接口同ArrayList一样,都是采用数组来实现的。所以它们具有「查找快、增删慢」的特点,当添加的元素大于所给的容量时,都会进行扩容。

3.1成员变量

Object[] elementData:保存vector中元素的数

elementCount:vector实际元素的个数

capacityIncrement:vector自动扩容是需要增加的容量

serialVersionUID:标识符,序列化和反序列化

  protected Object[] elementData;
  protected int elementCount;
  protected int capacityIncrement;
  private static final long serialVersionUID = -2767605614048989439L;
3.2构造方法

Vector():构造一个容量为10、自增容量为0的Vector

public Vector() {
        this(10);
    }

Vector(int initialCapacity):构造一个指定容量为initialCapacity、自增容量为0的vector

public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }

Vector(int initialCapacity, int capacityIncrement):构造一个指定容量为capacity、自增容量为capacityIncrement的Vector

 public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }

Vector(Collection<? extends E> c):构造一个Vector,该Vector包含指定集合的元素,其顺序由集合的迭代器返回

   public Vector(Collection<? extends E> c) {
        elementData = c.toArray();
        elementCount = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
    }
3.3 add

add(E e):在vector尾端添加元素,并将其实际数量+1,如果增加后的容量大于vector本身的容量,则需要扩容

「我们还是以add方法来讲解扩容机制」,请看下面的程序解析

 public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }

 private void ensureCapacityHelper(int minCapacity) {
        // overflow-conscious code
     //如果至少需要的容量>数组缓冲区当前的长度,就进行扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
//扩容方法,保证vector至少能存储minCapacity个元素
 private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
     //首先扩容
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
     //容量小于minCapacity,直接将容量增为minCapacity
        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) {
    //如果minCapacity<0,抛出异常
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    //如果想要的容量大于MAX_ARRAY_SIZE,则分配Integer.MAX_VALUE,否则分配MAX_ARRAY_SIZE
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}
3.4 set方法

set(int index, E element):将指定位置(index)的元素替换成element,并返回被替换的元素

 public synchronized E set(int index, E element) {
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }
3.5 get方法

get(int index):获取指定位置(index)的元素

public synchronized E get(int index) {
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);

        return elementData(index);
    }
3.6 remove方法

remove(Object o):移除指定位置的元素

 public boolean remove(Object o) {
        return removeElement(o);
    }
其它方法

copyInto(Object[] anArray):将vector中的所有的元素拷贝到指定的数组anArray中去

 public synchronized void copyInto(Object[] anArray) {
        System.arraycopy(elementData, 0, anArray, 0, elementCount);
    }

trimToSize():将底层的数组容量调整为当前vector实际元素个数,来释放空间

 public synchronized void trimToSize() {
        modCount++;
        int oldCapacity = elementData.length;
        if (elementCount < oldCapacity) {
            elementData = Arrays.copyOf(elementData, elementCount);
        }
    }

ensureCapacity(int minCapacity):增加vector容量,如果vector的当前容量小于至少需要的容量,就增加容量

  public synchronized void ensureCapacity(int minCapacity) {
        if (minCapacity > 0) {
            modCount++;
            ensureCapacityHelper(minCapacity);
        }
    }

4、总结

  • ArrayList、Vector和LinkedList特点

ArrayList访问速度快

Vector与ArrayList相似。但Vector的方法是线程安全的,而ArrayList的方法不是线程安全,由于线程的同步必然要影响性能,因此ArrayList的性能比Vector好

LinkedList随机访问元素慢,顺序访问快,增删元素快。

  • 使用的场景选择

对插入效率要求高,有LinlkedList

对插入效率要求不高,要求访问效率高使用ArrayList和Vector

线程安全Vector

线程不安全ArrayList

「参考文献」

[1]https://www.cnblogs.com/ccff-2016-12-24/p/9498690.html

[2]https://blog.csdn.net/panweiwei1994/article/details/77164647

[3]https://blog.csdn.net/panweiwei1994/article/details/77110354

[4]https://blog.csdn.net/panweiwei1994/article/details/76972890

[5] jdk1.8开发文档

笔者组建了技术交流群,请加笔者微信,笔者拉你进群学习,欢迎大家加入,一起进步。

看了这篇文章,你是否「博学」

「扫码关注我」,每天博学一点点。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值