ArrayList、LinkedList和Vector的异同和底层源码解析?

ArrayList、LinkedList和Vector的异同和底层源码解析?

相同点:从源码上看ArrayListLinkedListVector都实现了List接口,存储数据的特点相同:存储的数据有序可重复

不同点:

  • ArrayList:是List的接口的主要实现类,在jdk1.2才更新出来,线程不安全,效率高,查询快,增删慢,底层使用的是Object []
  • LinkedList:在jdk1.2才更新出来,线程不安全,对于频繁的插入、删除操作,使用此类效率高于ArrayList
  • Vector:是List的接口的古老实现类,在jdk1.0就已经有了,方法被synchronize修饰,线程安全,相对而言,效率低,底层也是Object []

ArrayList:底层是一个被transient修饰的Object []数组

    transient Object[] elementData;

这里看到Object[] 数组被transient修饰,这里解释一下transient的作用:

1)一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。

2)transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。

3)被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。

Vector:底层是一个被 protected修饰的Object []数组

    protected Object[] elementData;

这里看到Object[] 数组被protected修饰,这里解释一下protected的作用:

正如字面意思,proctected“受保护的”。所以,被proctected关键字修饰的成员也自然是“被保护”的,其只对本包及其子类可见

  • 父类的proctected成员包内可见,并且对子类可见
  • 子类父类不在同一个包里的话子类只能访问从父类继承的proctected方法。

LinkedList:底层是双向链表

    transient Node<E> first;//头节点

    transient Node<E> last;    


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

注意:List接口也是jdk1.2才更新出来,然后把Vector这个类归结到List接口实现类下


ArrayList底层源码分析:

ArrayList的在jdk7jdk8中各有所不同,但是他们的底层使用的是Object [] 数组是不变的

1.这里先看jdk7中开始看

1.1这里先看jdk7中开始看,先从无参构造开始查看

    public ArrayList() {
        this(10);
    }

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

从源码可以看到,ArrayList的无参构造中调用this(10)方法,调用ArrayList的有参构造,创建了一个Objecy[]数组,并添加了默认初始化容量为10

1.2 ArrayList list=new ArrayList();//底层创建了长度为10的Objects[] 数组

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

.....

list.add(11);//如果此次添加的元素导致element数组容量不够,则需要扩容。

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

//添加元素   
public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

从源码中,可以看出添加元素时,先确保数组容量是否满足需要,调用ensureCapacityInternal()方法,进行校验,点开ensureCapacityInternal()方法进行查看:

    private void ensureCapacityInternal(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

发现ensureCapacityInternal()方法判断当前容量+1后的长度-数组的长度是否大于0,如果大于0证明数组长度还够,就会跳出该方法,如果小于0,则进入grow方法,点开grow方法查看:

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

grow()方法中可以看出,对当前的elementData数组进行扩容,扩容为oldCapacity + (oldCapacity >> 1);,也就是原数组长度+原数组长度/2,也就是扩容为原来数组长度的1.5倍,并调用Arrays.copyOf()方法把之前的数组中的数据复制到新的数组中。


2.从看jdk8中再看ArrayList

先看ArrayList中的几个变量:

//定义一个空数组以供使用
private static final Object[] EMPTY_ELEMENTDATA = {};
//也是一个空数组,跟上边的空数组不同之处在于,这个是在默认构造器时返回的,扩容时需要用到这个作判断,后面会讲到
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存放数组中的元素,注意此变量是transient修饰的,不参与序列化
transient Object[] elementData;
//数组的长度,此参数是数组中实际的参数,区别于elementData.length,后边会说到
private int size;

2.1 从无参构造开始查看

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

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
 public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

从源码中发现,在jdk8中,ArrayList并没有给默认的初始化容量,而是给了一个常量,这个常量是一个空的Object数组。相对于jdk7来说,没有实例话对象,减少了一定的内存的压力.

2.2ArrayList list=new ArrayList();//底层创建了Object [] element={}

list.add(123);//elemnt[0]=new Integer(123);//第一次调用add()的时候,才会创建长度为10的Object数组

.....

list.add(11);//如果此次添加的元素导致element数组容量不够,则需要扩容。

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

再看添加元素add()的操作

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

jdk7类似,确保数组容量,点进去ensureCapacityInternal()方法查看

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

发现这里新添加了一个calculateCapacity方法,因为在jdk8中没有默认的初始化长度,所以需要来获取minCapacity长度,如果ArrayList是以无参构造的形式创建,elementData={},而如果是以有参构造的形式创建,elementData=new Object [initialCapacity]

    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

calculateCapacity()方法发现,如果elementData为空时,这里把minCapacityDEFAULT_CAPACITY进行对比,最终会返回10

**注意:**如果创建ArrayList用的无参构造,创建ArrayList时调用的是无参构造,此方法会返回DEFAULT_CAPACITY(值为10)和minCapacity的最大值,因此,最终会返回固定值10;若创建ArrayList时调用了有参构造,则此方法会返回1,注意这个minCapacity变量只是第一次调用add方法时值为1,此后的调用需要根据实际的数组长度size+1.
然后调用ensureExplicitCapacity方法

    private static final int DEFAULT_CAPACITY = 10;

modCount++用到了快速失败机制,此处先不做讨论

再回到ensureCapacityInternal方法中,点开ensureExplicitCapacity()方法查看

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

这里就跟jdk7一样,判断容量是否满足,不满足就调用grow()方法,进行扩容

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

grow()方法中可以看出,对当前的elementData数组进行扩容,扩容为oldCapacity + (oldCapacity >> 1);,也就是原数组长度+原数组长度/2,也就是扩容为原来数组长度的1.5倍,并调用Arrays.copyOf()方法把之前的数组中的数据复制到新的数组中。


LinkedList底层源码分析:LinkedList没有扩容机制,因为是链表

1.先看LinkedList内部的属性变量

//记录集合中元素的个数
transient int size = 0;
//链表中的头节点
transient Node<E> first;
//链表中的尾节点
transient Node<E> last;

NodeLinkedList的内部类,通过这个Node可以看出这个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;
    }
}

2.LinkedList list=new LinkedList();内部声明了Node类型的firstlast属性,默认值为null
list.add(111);将111封装到Node中,创建了Node对象,这里111自动装箱为new Integer(111)被添加list集合中

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

add()方法中,如果没有制定添加某个位置,默认是在末尾添加元素,所以调用了linkLast(e);方法

    void linkLast(E e) {
      //先把最后一个元素的值暂时取出
        final Node<E> l = last;
      //创建一个node节点,为新添加的元素,他的有参构造为 Node(Node<E> prev, E element, Node<E> next)
      //因为不知道新添加的元素的下一个节点是谁,所以默认为null,如果是第一次添加l也是为null的
      //final Node<E> newNode = new Node<>(null, e, null);
        final Node<E> newNode = new Node<>(l, e, null);
      //把新添加的元素作为最后一个元素
        last = newNode;
      //判断是否是第一次添加,如果是,就把头节点为当前新添加的元素节点,也就是newNode
      //如果不是第一次添加,就更改头节点的下一个元素节点为当前新添加的元素节点
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
      //集合中的元素数量+1
        size++;
      //快速失败机制,暂不讨论
        modCount++;
    }

list.remove(111) 如果要删除111的元素,调用remove方法,因为111为整形,会调用remove的删除制定索引下的元素的方法

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

从remove中可以看出,删除之前先调用checkElementIndex方法,检查元素索引是否超出,超出就抛IndexOutOfBoundsException异常

    private void checkElementIndex(int index) {
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

点开isElementIndex方法,判断删除的索引是否大于等于零,并且小于当前集合的元素个数

    private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }

删除元素的方法unlink

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) {
        first = next;
    } else {
        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;
}

如需删除111的元素,需要如下写法
list.remove(new Integer(111));这样就会调用删除元素为111的元素了,这里也是调用了unlink方法

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

Vector底层源码分析:方法被synchronize修饰,效率低一点,但是线程安全,虽然线程安全,但是在实际开发中,Vector也不会怎么用到,如果用到ArrayList的时候,可以用Collections.synchronizedList()方法可以返回线程安全的List集合

先查看Vector的属性变量

    //存储元素的数组
		protected Object[] elementData;

		//集合中元素的个数
    protected int elementCount;

		//用于扩容时进行比较的
    protected int capacityIncrement;

Vector vector=new Vector();底层是数组,无参构造初始化时,默认为容量为10的Object数组

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

vector.add("aaa"); 发现add方法被synchronize修饰,效率会低,但是线程安全

    public synchronized boolean add(E e) {
      	//快速失败机制,暂不讨论
        modCount++;
      //确保容量满足
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }

点开ensureCapacityHelper方法查看

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

发现ensureCapacityHelper方法跟ArrayList一样,点开grow方法

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
      //因为capacityIncrement初始化为0,所以不满足大于0的需求,所以这里时oldCapacity +oldCapacity 
      //也就是集合容量扩容为原来的2倍
        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);
    }

发现capacityIncrement这个属性大于0时,返回capacityIncrement,因为初始化时为0,所以这里返回固定值为2倍


面试题:请问ArrayList/LinkedList/Vector的异同?谈谈你的理解?ArrayList底层是什么?扩容机制?Vector和ArrayList的最大区别?
  • ArrayList和LinkedList的异同

    二者都线程不安全,相对线程安全的Vector,执行效率高。

    此外,ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。对于新增和删除操作add(特指插入)和remove,LinkedList比较占优势,因为ArrayList要移动数据。

  • ArrayList和Vector的区别

    Vector和ArrayList几乎是完全相同的,唯一的区别在于Vector是同步类(synchronized),属于强同步类。因此开销就比ArrayList要大,访问要慢。正常情况下,大多数的Java程序员使用ArrayList而不是Vector,因为同步完全可以由程序员自己来控制。Vector每次扩容请求其大小的2倍空间,而ArrayList是1.5倍。Vector还有一个子类Stack。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值