源码分析ArrayList/Vector/LinkedList

来源:互联网 发布:每天读书 知乎 编辑:程序博客网 时间:2024/06/05 03:56

首先看这三个类的声名部分:

public class LinkedList<E>    extends AbstractSequentialList<E>    implements List<E>, Deque<E>, Cloneable, java.io.Serializable

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

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

按照javaBean的规范都实现了Cloneable和Serializable接口,而AbstractSequentialList实质上是AbstractList的子类在父类的基础上多加了几个方法。下面我们一一讲解,先从ArrayList讲起。

首先从它定义的全局变量可以看出他的底层实现实质就是数组。

   private transient Object[] elementData;   private int size;
再看看他的构造函数

 public ArrayList(int initialCapacity) {        super();        if (initialCapacity < 0)            throw new IllegalArgumentException("Illegal Capacity: "+                                               initialCapacity);        this.elementData = new Object[initialCapacity];    }  public ArrayList() {        this(10);    }  public ArrayList(Collection<? extends E> c) {        elementData = c.toArray();        size = elementData.length;        // c.toArray might (incorrectly) not return Object[] (see 6260652)        if (elementData.getClass() != Object[].class)            elementData = Arrays.copyOf(elementData, size, Object[].class);    }
这里我们可以看到如果我们直接去new 产生对象他会帮我们初始化一个长度为10的Object[]数组。当然你也可以自己去设置这个数组的初始长度,或者给他传一个集合过去帮他完成初始化。而且结合上面提到的size是私有的,所以我们要去调它提供的size()方法获得这个数组的有效长度,注意这里的size并不是这个数值的真实长度。

先来看看add相关方法:

  public boolean add(E e) {        ensureCapacityInternal(size + 1);  // Increments modCount!!//判断是否需要加长数组        elementData[size++] = e;//这里看是后++所以第一次添加参数size是0所以这个值就被添加到下标为0的位置,添加完后size就变为1了        return true;    }  public void add(int index, E element) {        rangeCheckForAdd(index);//说明这个方法只能添加到size以内        ensureCapacityInternal(size + 1);  // Increments modCount!!        System.arraycopy(elementData, index, elementData, index + 1,                         size - index);//这个方法参数依次的意思 源数组 源数组要复制的起始位置 目标数组 目标数组放置的起始位置  复制长度。        elementData[index] = element;//为空出来的这个角标赋值        size++; //增加有效个数    } public boolean addAll(Collection<? extends E> c) {//这个方法就是将一个集合的数据加入到这个list中        Object[] a = c.toArray();        int numNew = a.length;        ensureCapacityInternal(size + numNew);  // Increments modCount        System.arraycopy(a, 0, elementData, size, numNew);        size += numNew;        return numNew != 0;     } 

你会发现要看懂这些方法就要先看懂ensureCapacityInternal和rangeCheckForAdd这两个方法:

 private void rangeCheckForAdd(int index) {        if (index > size || index < 0)  //这个方法其实很简单就是判断这个脚标是不是大于目前的有效个数,如果大于就抛出数组下标越界            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));    }  private void ensureCapacityInternal(int minCapacity) {        modCount++;    //这个是记录这个对象被操作的次数        // overflow-conscious code         if (minCapacity - elementData.length > 0)  //如果这个脚标大于了目前这个数组对象的长度那么就调grow这个方法,因为数组长度不可变。            grow(minCapacity);    }   private void grow(int minCapacity) {        // overflow-conscious code        int oldCapacity = elementData.length;   //获取当前数组长度        int newCapacity = oldCapacity + (oldCapacity >> 1);//新的数组的长度是 老数组长度的1.5倍 >>1相当于除2        if (newCapacity - minCapacity < 0)  //防止类似oldCapacity为1除以2就变成0了(向下取整),那么实际获得的newCapacity其实还是1,这是minCapacity就大于了它            newCapacity = minCapacity;        if (newCapacity - MAX_ARRAY_SIZE > 0) //这里的MAX是int的极限值很大了,基本用不到这么大            newCapacity = hugeCapacity(minCapacity);        // minCapacity is usually close to size, so this is a win:        elementData = Arrays.copyOf(elementData, newCapacity);//这里调工具类方法将当前数组加长。    } 


我们继续来看一些方法

public boolean contains(Object o) { //判断是否含有某个对象调的是indexOf这个方法        return indexOf(o) >= 0;    }public int indexOf(Object o) {          if (o == null) {    //先判断这个对象是否为空,所以list可以存入空值            for (int i = 0; i < size; i++)                  if (elementData[i]==null)  //这里做循环然后一个一个判断,因为是从第0开始取所以取出来的是第一个相同的值                    return i;        } else {            for (int i = 0; i < size; i++)                if (o.equals(elementData[i]))                    return i;        }        return -1;    } public int lastIndexOf(Object o) {        if (o == null) {     //这里因为是从最后一位开始取所以是最后一个相同的值            for (int i = size-1; i >= 0; i--)                if (elementData[i]==null)                    return i;        } else {            for (int i = size-1; i >= 0; i--)                if (o.equals(elementData[i]))                    return i;        }        return -1;    } public boolean isEmpty() {   判断是否为空,是看他的有效个数是否为0        return size == 0;    }  public E set(int index, E element) {   set其实就是把老数组的这个值替换掉,并且将它返回回来        rangeCheck(index);        E oldValue = elementData(index);        elementData[index] = element;        return oldValue;    }   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; // Let gc do its work    这里用的是前--。因为size-1就是之前数值的最后一位,但是都往前移一位了,所以这个角标应该为null        return oldValue;    } public boolean remove(Object o) {          if (o == null) {            for (int index = 0; index < size; index++)                if (elementData[index] == null) {  这里通过循环判断这个值第一次出现的角标,并且对其进行删除,                    fastRemove(index);                    return true;                }        } else {            for (int index = 0; index < size; index++)                if (o.equals(elementData[index])) {                    fastRemove(index);                    return true;                }        }        return false;    }   private void fastRemove(int index) {  //这个方法其实和最开始的那个remove差不多就不讲了        modCount++;        int numMoved = size - index - 1;        if (numMoved > 0)            System.arraycopy(elementData, index+1, elementData, index,                             numMoved);        elementData[--size] = null; // Let gc do its work    }  public void clear() {  //清除这个对象的数据        modCount++;        // Let gc do its work        for (int i = 0; i < size; i++)  //其实就是把这个数组每个值置空,但是你会发现他只把这个size归0并没有对这个数组长度进行改变。            elementData[i] = null;        size = 0;    }
   public void trimToSize() {   //这个方法就是防止数组过大,把他的长度变为size。        modCount++;        int oldCapacity = elementData.length;        if (size < oldCapacity) {            elementData = Arrays.copyOf(elementData, size);        }    }

ArrayList就写这么多吧,再写写Vector,然后把这个源码打开你会发现,所有方法的实现基本一样,也是基于数组实现,初始化也是10个长度,但是也来说说不同的地方。

 private void grow(int minCapacity) {  //grow方法之前写过是数组长度超标后,增加数值长度的方法        // overflow-conscious code        int oldCapacity = elementData.length;        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?   //这里可以看到不是1.5倍了,这里的capacityIncrement如果初始化不写入就是0                                         capacityIncrement : oldCapacity);  //如果为0那么就想当于是原数组长度的两倍        if (newCapacity - minCapacity < 0)            newCapacity = minCapacity;        if (newCapacity - MAX_ARRAY_SIZE > 0)            newCapacity = hugeCapacity(minCapacity);        elementData = Arrays.copyOf(elementData, newCapacity);    }  public Vector(int initialCapacity, int capacityIncrement) {  //这里来初始化这个capacityIncrement        super();        if (initialCapacity < 0)            throw new IllegalArgumentException("Illegal Capacity: "+                                               initialCapacity);        this.elementData = new Object[initialCapacity];        this.capacityIncrement = capacityIncrement;    }
从这些方法你可以看到这两个类最大的区别那就是这些具有操作对象属性的方法都加了synchronized这个关键字 public synchronized void trimToSize() {        modCount++;        int oldCapacity = elementData.length;        if (elementCount < oldCapacity) {            elementData = Arrays.copyOf(elementData, elementCount);        }    }    public synchronized int indexOf(Object o, int index) {        if (o == null) {            for (int i = index ; i < elementCount ; i++)                if (elementData[i]==null)                    return i;        } else {            for (int i = index ; i < elementCount ; i++)                if (o.equals(elementData[i]))                    return i;        }        return -1;    } public synchronized void addElement(E obj) {        modCount++;        ensureCapacityHelper(elementCount + 1);        elementData[elementCount++] = obj;    }

synchronized这个关键字大家都知道是同步的意思,首先每个对象都有自己的对象锁,说简单点比如多个线程同时操作同一个对象,那么就很有可能造成线程的不安全。比如多个线程同时去往同一个ArrayList对象中添加一个值,那么由于线程间数据是不可见的,所以他们相互之前不知道对方在往这个对象中添值就会照成我往角标为5的这个位置添了对象A,另一个线程也往角标为5的这个位置添了一个值B,造成数据丢失。而Vector对这些方法加了一个synchronized这个关键字,那么这个工作过程就大概变成了这样:

获得互斥锁--清空该线程的工作内存--从住内存中拷贝变量到相应的工作内存--执行代码--将操作过后的共享值刷新到主内存--释放锁。这样就保证了可见性和原子性,简单一点我在网这个数组添值时,我会拿到这个对象的互斥锁,其他线程就不能对这个对象的共享变量进行操作只有等待,然后获得该数组的最新值,进行操作,把操作后的数组刷新到主内存中,然后其他线程才能继续操作。

然后看看LinkedList它就和上面两个就不一样了,先是几个全局变量:

transient int size = 0;  //这个也是记录目前这个集合中有效数据个数transient Node<E> first;  //第一个节点transient Node<E> last;  //最后一个节点
这里有一个Node,它是LinkedList的一个静态内部类,这个类就是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;        }    }
看到这个类的三个属性,你就应该大致明白他是记录你添加的值的前一个和后一个元素用的,所以这就是双向。下面看看具体的方法:

   public LinkedList() {  //这个是它的构造方法,因为这里面不存在给数组赋初值,所以这里没有什么操作,只是简单的产生对象    } private void linkFirst(E e) { //这是添加这个链表的第一个值,相当于数组脚标的0添加值        final Node<E> f = first;  //因为这是第一个值,那么之前的first就应该是当前这个节点的后一个节点,这里你会发现这里是用final修饰,final修饰后的属性变为
常量,放入常量池中进行缓存,这样提升了销量
        final Node<E> newNode = new Node<>(null, e, f); //因为是第一个说以前一个就应该是null        first = newNode;  //并且将全局中的first改为现在当前这个节点        if (f == null)  //如果这个链表之前没有初值            last = newNode; //那么新加的这个值既是first也是last        else            f.prev = newNode;  //它是第一个那么,之前的第一就变成了第二个,那么现在的第一就是之前第一的前一个        size++;  //增加有效值        modCount++; //增加操作    }
 void linkLast(E e) { //这个方法是添加最后一个        final Node<E> l = last;   //取出当老last        final Node<E> newNode = new Node<>(l, e, null);  //新节点成为last,那么前一个值就是老last,当然就没有后一个        last = newNode; //改变全局里的last为新last        if (l == null) //如果last为空,说明这个链表之前没有值            first = newNode; //那么他也是first        else            l.next = newNode; //它是老last的后一个        size++; //增加有效数量        modCount++;//增加操作    } public void addFirst(E e) {        linkFirst(e); //调用方法    }  public void addLast(E e) {        linkLast(e); //调用方法    }
这里你应该了解了Node类的作用了吧,那我们继续

  public E getFirst() {        final Node<E> f = first; //之前讲过每加一个first都会去初始化这个全局变量first,所以这个first就是当前的第一个节点        if (f == null)               throw new NoSuchElementException();        return f.item;  //item就是当前对象。    }public E getLast() {  //和上面一样就不说了        final Node<E> l = last;        if (l == null)            throw new NoSuchElementException();        return l.item;    } public E removeFirst() {  //移除第一个对象        final Node<E> f = first; //同样也是先取出第一个节点        if (f == null)            throw new NoSuchElementException();        return unlinkFirst(f); //然后调这个方法    }public E removeLast() {        final Node<E> l = last;        if (l == null)            throw new NoSuchElementException();        return unlinkLast(l);    } 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; //所以把last也置空        else            next.prev = null;  //如果有值,那么你就要把新first的前一个节点置空,因为被我们删了呀        size--; //有效数减小        modCount++; //增加操作        return element; //返回之前的第一个值    }   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;    }
看一看删除或添加指定位置或值,及其他的一些代码:


 public boolean contains(Object o) {         return indexOf(o) != -1;    } public int indexOf(Object o) {        int index = 0;         if (o == null) { //所以这个也可以存空值            for (Node<E> x = first; x != null; x = x.next) { //这个是从第一个开始,所以返回的是最靠前的相同的值                if (x.item == null)                    return index;                 index++;  //这里进行加一,因为链表没有数组那种脚标进行表示,所以用这种方式代替脚标            }        } else {            for (Node<E> x = first; x != null; x = x.next) {                if (o.equals(x.item))                    return index;                index++;            }        }        return -1; //否则返回负一    }   public boolean add(E e) { //所以默认的添加就是添加到末尾        linkLast(e);        return true;    } public boolean remove(Object o) {        if (o == null) {            for (Node<E> x = first; x != null; x = x.next) {                if (x.item == null) { //也是循环去找第一个与之相同的值                    unlink(x);                    return true;                }            }        } else {            for (Node<E> x = first; x != null; x = x.next) {                if (o.equals(x.item)) {                    unlink(x);                    return true;                }            }        }        return false;    }  E unlink(Node<E> x) {  //找到后调用此方法删除,大概就是将此节点删除,然后把前一个和后一个链接在一起        // assert x != null;        final E element = x.item;  //取出当前值        final Node<E> next = x.next; //取后一个节点        final Node<E> prev = x.prev; //取前一个节点        if (prev == null) { //如果prev为空那么表明目前删的值为链表的第一个值,所以next就变为first            first = next;        } else {            prev.next = next;  //如果不是那么prev的next就变成了当前节点的next而不是当前节点            x.prev = null; //制空        }        if (next == null) {//同上            last = prev;        } else {            next.prev = prev;            x.next = null;        }        x.item = null;         size--; //有效值减少一位        modCount++; /增加操作        return element;    }  public void clear() {        // Clearing all of the links between nodes is "unnecessary", but:        // - helps a generational GC if the discarded nodes inhabit        //   more than one generation        // - is sure to free memory even if there is a reachable Iterator        for (Node<E> x = first; x != null; ) { //循环置空            Node<E> next = x.next;            x.item = null;            x.next = null;            x.prev = null;            x = next;        }        first = last = null; //将全局的first和last置空        size = 0; //size为0        modCount++;//增加操作    }  public E get(int index) {        checkElementIndex(index); //判断是否在size内        return node(index).item;     }  Node<E> node(int index) {        // assert isElementIndex(index);        if (index < (size >> 1)) { //折半            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;        }    }    public E set(int index, E element) {        checkElementIndex(index); //是否在size内        Node<E> x = node(index);  //获得index的Node        E oldVal = x.item; //取出老值        x.item = element; //获得新值        return oldVal;    }  public void add(int index, E element) {        checkPositionIndex(index);//还是判断是否在size内        if (index == size)   //如果等于size就加到最后一个            linkLast(element);        else            linkBefore(element, node(index));    }  void linkBefore(E e, Node<E> succ) { //大概就是在index的前面加一个Node        // 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++;    }  public E remove(int index) {        checkElementIndex(index);        return unlink(node(index));    }

好了这三个类的大概源码就写到这了。简单总结下:list最大的特点就是有序,ArrayList和Vector是基于数组来实现所以肯定是有序的,LinkedList是基于双向链表,也就是Node对象记录了。而Vector之所以线性安全是因为它操作性方法都用synchronized修饰了,就到这吧,写错了的希望大家指出来,谢谢。