深入Java源码分析Vector、ArrayList、LinkedList的特点和区别

深入Java源码分析Vector、ArrayList、LinkedList的特点和区别?

* 三者区别:
  • ArrayList和Vector底层的数据结构基于【动态数组】实现的; LinkedList底层的数据结构是基于 【双链表】实现的.
  • ArrayList和Vector实现了RandomAccess接口【支持随机访问】; LinkedList没有实现RandomAccess接口【不支持随机访问】.
  • ArrayList和Vector随机访问数据效率高, 插入和删除数据的效率低 (需要涉及元素移动) ; LinkedList插入和删除操作效率高, 读取数据效率很低 (需要从第一个元素开始遍历).
  • Vector线程安全; ArrayList和LinkedList线程不安全.
* 一致特点:
  • ArrayList、Vector和LinkedList都实现了Serializable接口【都可以被序列化】.
  • ArrayList、Vector和LinkedList都实现了Cloneable接口【此接口只有声明、没有方法体、表示都支持克隆复制】.
  • ArrayList、Vector和LinkedList都继承AbstractList抽象类 【都支持 “增删改查” 操作】.
  • ArrayList、Vector和LinkedList都允许为空、都允许有重复数据、都有序.
    ······

一. Vector源码分析

向量类(Vector)实现了一个动态数组。和 ArrayList 很相似,但是两者是不同的:

  1. Vector 是同步访问的。
  2. Vector 包含了许多传统的方法,这些方法不属于集合框架。
  3. Vector线程安全; ArrayList线程不安全。
1. Vector的成员变量分析

Vector集合的底层为数组,对Vector集合的操作,其实就是对底层elementCount数组的操作。JDK1.7源码如下:

	//与ArrayList一样,Vector的底层也是使用数组elementData进行存储数据
    protected Object[] elementData;
	//当前elementData数组中元素的个数
    protected int elementCount;
	//当前elementData数组进行扩容的增量
    protected int capacityIncrement;
	//序列化版本号
    private static final long serialVersionUID = -2767605614048989439L;
2. Vector的构造方法分析

Vector构造方法有4个,分别是:

  1. 无参构造:Vector()【底层elementData数组的长度默认容量为10,扩容增量为0】
  2. 带参构造:Vector(int initialCapacity),指定initialCapacity容量大小,创建Vector集合;
  3. 带参构造:Vector(int initialCapacity, int capacityIncrement),指定initialCapacity容量大小和扩容增量大小,创建Vector集合;
  4. 带参构造:Vector(Collection<? extends E> c),通过一个集合,创建Vector集合;

构造方法JDK1.7源码如下:

    public Vector() {
    	//初始化elementData数组的容量长度为initialCapacity==10,capacityIncrement ==0
        this(10);
    }
    public Vector(int initialCapacity) {
    	//初始化elementData数组的容量长度为initialCapacity,capacityIncrement ==0
        this(initialCapacity, 0);
    }
    public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        //初始化elementData数组的容量长度为initialCapacity
        this.elementData = new Object[initialCapacity];
        //设置elementData数组的扩容增量为capacityIncrement
        this.capacityIncrement = capacityIncrement;
    }
    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. Vector的扩容源码分析

例:add添加元素方法源码的具体实现:JDK1.7源码如下:

    public synchronized boolean add(E e) {
        modCount++;
        //元素e在存储之前会调用ensureCapacityHelper(int minCapacity)方法
        ensureCapacityHelper(elementCount + 1);
        //把元素e存放在数组elementData[elementCount++]下标位置
        elementData[elementCount++] = e;
        return true;
    }

ensureCapacityHelper(int minCapacity)判断是否需要扩容方法的实现:JDK1.7源码如下:

    private void ensureCapacityHelper(int minCapacity) {
    	//元素个数size+1大于当前elementData.length时,Vector集合将进行扩容
        if (minCapacity - elementData.length > 0)
        	//true,扩容
            grow(minCapacity);
    }

grow(int minCapacity)扩容方法的实现:JDK1.7源码如下:

   private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
        	//true,则newCapacity取两者最大值
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
        	//newCapacity 最大值限定为2147483647
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) 
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

小结:

  • Vector大量的方法基本上均加synchronized修饰,因此Vector是线程安全的
  • Vector集合创建时,会初试化成员elementData数组的容量长度和扩容增量,既elementData.length和capacityIncrement;
  • 在add元素时,元素个数size+1大于当前elementData.length时,Vector集合将进行扩容扩容标准根据capacityIncrement和elementData.length,以及当前elementCount元素个数来确定;
  • 扩容过程实际就是elementData数组按照newCapacity,进行数组的复制,生成1个新elementData数组,替换掉旧的elementData数组。

二. ArrayList源码分析

ArrayList是最常见以及每个Java开发者最熟悉的集合类了。

  1. ArrayList底层的数据结构基于动态数组实现的;
  2. ArrayList实现了Cloneable和Serialiable接口,所以可以被克隆和序列化;
  3. ArrayList和Vector实现了RandomAccess接口,所以可以支持随机访问;
  4. ArrayList随机访问数据效率高, 插入和删除数据的效率低(需要涉及元素移动);
  5. ArrayList允许为空、允许有重复数据、有序;
  6. ArrayList线程不安全。
1. ArrayList的成员变量分析
  • :elementData是使用transient修饰的呢?
    我来说说我的看法:因为ArrayList实现了Serializable接口,这意味着ArrayList是可以被序列化的。用transient修饰elementData意味着我不希望elementData数组被序列化。
    这是为什么?因为序列化ArrayList的时候,ArrayList里面的elementData未必是满的,比方说elementData有10的大小,但是我只用了其中的3个,那么是否有必要序列化整个elementData呢?显然没有这个必要,因此ArrayList中重写了writeObject方法,每次序列化的时候调用这个方法,先调用defaultWriteObject()方法序列化ArrayList中的非transient元素,elementData不去序列化它,然后遍历elementData,只序列化那些有的元素。
  • 好处【 加快了序列化的速度】、【减小了序列化之后的文件大小】

JDK1.7源码如下:

	//序列化版本号
	private static final long serialVersionUID = 8683452581122892189L;
 	//存放元素的数组,被transient修饰
    transient Object[] elementData; 
 	//ArrayList当前元素的个数
    private int size;
    //数组最大长度
    private static final int MAX_ARRAY_SIZE = 2147483639;
2. ArrayList的构造方法分析

ArrayList构造方法有3个,分别是:

  1. 无参构造:ArrayList()【底层elementData数组的长度默认容量为10】
  2. 带参构造ArrayList(int i),指定i容量大小;
  3. 带参构造:ArrayList(Collection collection),入参为集合;

JDK1.7源码如下:

	//没有入参的构造方法,默认初始长度为10,随着存入的数据增多时,会调用扩容方法进行扩容
    public ArrayList()
    {
        this(10);
    }
    //制定初始化容量的构造方法
    public ArrayList(int i)
    {
        if(i < 0)
        {
            throw new IllegalArgumentException((new StringBuilder()).append("Illegal Capacity: ").append(i).toString());
        } else
        {
            elementData = new Object[i];
            return;
        }
    }
	//入参为集合的构造方法
    public ArrayList(Collection collection)
    {
        elementData = collection.toArray();
        size = elementData.length;
        if(((Object) (elementData)).getClass() != [Ljava/lang/Object;)
            //使用 Arrays.copyOf方法拷创建一个 Object 数组
            elementData = Arrays.copyOf(elementData, size, [Ljava/lang/Object;);
    }
3.ArrayList的增删改查分析

ArrayList随机访问数据效率高, 插入和删除数据的效率低(需要涉及元素移动),这与LinkedList相反。JDK1.7源码如下:

    /**添加数据,在i下标下添加obj元素*/
    public void add(int i, Object obj)
    {
        rangeCheckForAdd(i);
        //判断是否需要扩容
        ensureCapacityInternal(size + 1);
        //System,arraycopy方法做一个整体的复制,向后移动位置
        System.arraycopy(((Object) (elementData)), i, ((Object) (elementData)), i + 1, size - i);
        elementData[i] = obj;
        size++;
    }
    /**删除数据*/
    public Object remove(int i)
    {
        rangeCheck(i);
        modCount++;
        Object obj = elementData(i);
        int j = size - i - 1;
        if(j > 0)
        	System,arraycopy方法做一个整体的复制,向前移动位置
            System.arraycopy(((Object) (elementData)), i + 1, ((Object) (elementData)), i, j);
        elementData[--size] = null;
        return obj;
    }
    /**获取数据*/
    public Object get(int i)
    {
    	//校验下标是否合法
        rangeCheck(i);
        //返回数据
        return elementData(i);
    }
    private void rangeCheck(int i)
    {
        if(i >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(i));
        else
            return;
    }
    Object elementData(int i)
    {
        return elementData[i];
    }
4. ArrayList的扩容源码分析

调用ensureCapacityInternal(int minCapacity)方法对数组的容量进行判断,如果不够就调用grow(int minCapacity)方法对数组进行扩容,新容量默认为老容量的1.5倍,如果老容量的1.5倍依然不够,则将最小容量作为新容量。JDK1.7源码如下:

	//容量进行判断,是都需要扩容
    private void ensureCapacityInternal(int i)
    {
        modCount++;
        if(i - elementData.length > 0)
            grow(i);
    }
	//扩容
    private void grow(int i)
    {
        int j = elementData.length;
        新容量为老容量的1.5倍
        int k = j + (j >> 1);
        //如果新容量小于最小容量,则将最小容量赋值给新容量
        if(k - i < 0)
            k = i;
        //如果新容量大于阈值
        if(k - 2147483639 > 0)
            k = hugeCapacity(i);
        //新建的数组容量为 k
        elementData = Arrays.copyOf(elementData, k);
    }

    private static int hugeCapacity(int i)
    {
        if(i < 0)
            throw new OutOfMemoryError();
        else
            return i <= 2147483639 ? 2147483639 : 2147483647;
    }

三. LinkedList源码分析

LinkedList底层的数据结构是基于双向链表的,每个节点分为头部、尾部以及业务数据,前一个节点尾部指向后一个节点的头部,后一节点的头部指向前一个节点的尾部。但是头结点和尾节点比较特殊,头结点的头部没有上一个结点,尾节点的尾部也没有指向下一个结点。对应的就是下面图示:
在这里插入图片描述

  1. LinkedList底层的数据结构是基于双链表实现的;
  2. LinkedList实现了Cloneable和Serialiable接口,所以可以被克隆和序列化;
  3. LinkedList插入和删除操作效率高, 读取数据效率低(需要从第一个元素开始遍历);
  4. LinkedList允许为空、允许有重复数据、有序;
  5. LinkedList线程不安全。
1. LinkedList元素的存储结构分析

在LinkedList中,每一个元素都是Node存储,Node拥有一个存储值的item与一个前驱prev和一个后继next。JDK1.7源码如下:

	// 典型的链表结构
    private static class Node
    {
        Object item;// 存储元素
        Node next;// 指向上一个元素
        Node prev;// 指向下一个元素
        Node(Node node1, Object obj, Node node2)
        {
            item = obj;
            next = node2;
            prev = node1;
        }
    }
2. LinkedList构造函数与成员变量分析
  • 变量主要有4个:JDK1.7源码如下:
	// 当前列表的元素个数
    transient int size;
    // 第一个元素
    transient Node first;
    // 最后一个元素
    transient Node last;
    // 序列化版本号
    private static final long serialVersionUID = 876323262645176354L;
  • LinkedList中的构造函数有两个:JDK1.7源码如下:
	//无参构造函数
    public LinkedList()
    {
        size = 0;
    }
	//入参为集合的构造方法
    public LinkedList(Collection collection)
    {
        this();
        //将c中的元素都添加到此列表中
        addAll(collection);
    }
3.ArrayList的增删改查分析
  • 添加数据add一:尾部添加 JDK1.7源码如下:
    void linkLast(Object obj)
    {
        Node node1 = last;
        //创建一个尾节点
        Node node2 = new Node(node1, obj, null);
        last = node2;
        if(node1 == null)
            first = node2;
        else
            node1.next = node2;
        size++;
        modCount++;
    }

实现原理如下图:
在这里插入图片描述

  • 添加数据add二:指定位置添加 JDK1.7源码如下:
    void linkBefore(Object obj, Node node1)
    {
        Node node2 = node1.prev;
        Node node3 = new Node(node2, obj, node1);
        node1.prev = node3;
        if(node2 == null)
            first = node3;
        else
            node2.next = node3;
        size++;
        modCount++;
    }

实现原理如下图:
在这里插入图片描述

  • 删除数据remove JDK1.7源码如下:
	//删除数据
    public Object remove(int i)
    {
        checkElementIndex(i);
        return unlink(node(i));
    }
    //修改指向
    Object unlink(Node node1)
    {
        Object obj = node1.item;
        Node node2 = node1.next;
        Node node3 = node1.prev;
        if(node3 == null)
        {
            first = node2;
        } else
        {
            node3.next = node2;
            node1.prev = null;
        }
        if(node2 == null)
        {
            last = node3;
        } else
        {
            node2.prev = node3;
            node1.next = null;
        }
        node1.item = null;
        size--;
        modCount++;
        return obj;
    }

实现原理如下图:
在这里插入图片描述

  • 查询数据get:【从第一个元素开始遍历链表查找数据,效率低】 JDK1.7源码如下:
	//查询数据
    public Object get(int i)
    {
        checkElementIndex(i);
        return node(i).item;
    }
    //从第一个元素开始遍历链表查找数据
    Node node(int i)
    {
        if(i < size >> 1)
        {
            Node node1 = first;
            for(int j = 0; j < i; j++)
                node1 = node1.next;

            return node1;
        }
        Node node2 = last;
        for(int k = size - 1; k > i; k--)
            node2 = node2.prev;

        return node2;
    }

实现原理如下图:
在这里插入图片描述

……
帮助他人,快乐自己,最后,感谢您的阅读!
所以如有纰漏或者建议,还请读者朋友们在评论区不吝指出!
……
个人网站…知识是一种宝贵的资源和财富,益发掘,更益分享…

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值