Java架构直通车——ArrayList和LinkedList底层原理解析

ArrayList实现原理

初始化

ArrayList的底层是一个动态数组,初始化时,ArrayList首先会对传进来的初始化参数initalCapacity进行判断:

  • 如果参数等于0,则将数组初始化为一个空数组,
  • 如果不等于0,将数组初始化为一个容量为10的数组。

扩容方式

当数组的大小大于初始容量的时候(比如初始为10,当添加第11个元素的时候),就会进行扩容,新的容量为旧的容量的1.5倍。
扩容的时候,会以新的容量建一个原数组的拷贝,修改原数组,指向这个新数组,原数组被抛弃,会被GC回收。

add()方法

对于顺序插入,ArrayList特别快,没什么好说的。
这里想说的的是随机插入,随机插入的实现方式为:原来数组插入位置后面的元素按顺序复制到原数组插入位置+1的位置。

	public void add(int index, E element) {
        /*判断插入的索引是否符合ArrayList范围,在0 和 size之间,size是ArrayList实际元素个数,不包括底层数组的null元素*/
        rangeCheckForAdd(index);
        /*扩容机制:判断添加是否需要进行扩容*/
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        /*将旧数组拷贝到一个新数组中,参数:被复制的原数组, 被复制数组的第几个元素开始复制, 复制的目标数组, 从目标数组index + 1位置开始粘贴, 复制的元素个数,*/
        System.arraycopy(elementData, index, elementData, index + 1, size - index);           
        /*将新元素赋予该下标*/
        elementData[index] = element;
        /*元素个数+1*/
        size++;
    }

实现接口

ArrayList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了RandomAccess接口,支持快速随机访问,实际上就是通过下标序号进行快速访问,实现了Cloneable接口,能被克隆。

为什么ArrayList里面的elementData为什么要用transient来修饰?

看源码可以知道,ArrayList包含两个私有属性:

	/** 
      * The array buffer into which the elements of the ArrayList are stored. 
      * The capacity of the ArrayList is the length of this array buffer. 
      */  
     private transient Object[] elementData;  
   
     /** 
      * The size of the ArrayList (the number of elements it contains). 
      * 
      * @serial 
      */  
     private int size;

elementData里面不是所有的元素都有数据,因为容量的问题,elementData里面有一些元素是空的,这种是没有必要序列化的。ArrayList的序列化和反序列化依赖writeObject和readObject方法来实现。可以避免序列化空的元素。

/**
     * Save the state of the <tt>ArrayList</tt> instance to a stream (that
     * is, serialize it).
     *
     * @serialData The length of the array backing the <tt>ArrayList</tt>
     *             instance is emitted (int), followed by all of its elements
     *             (each an <tt>Object</tt>) in the proper order.
     */
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();


        // Write out size as capacity for behavioural compatibility with clone()
        s.writeInt(size);


        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }


        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

线程安全与否

ArrayList不是线程安全的,只能用在单线程环境下,多线程环境下可以考虑用Collections.synchronizedList(List l)函数返回一个线程安全的ArrayList类,也可以使用concurrent并发包下的CopyOnWriteArrayList类。

LinkedList实现原理

初始化

Linkedlist底层是一个双向链表,头尾都包含一个不含任何元素的dummy节点。

//指向头节点的变量first   
transient Node<E> first; 
//指向尾节点的变量last   
 transient Node<E> last;  

在这里插入图片描述

其初始化方法很简单(也就是什么都不用做):

	/**
     * Constructs an empty list.
     */
    public LinkedList() {
    }

add()方法

LinkedList包括了头插和尾插(很容易理解):

	public void addFirst(E e) {
        linkFirst(e);
    }
   	public void addLast(E e) {
        linkLast(e);
    }
    /**
     * Links e as first element.
     */
    private void linkFirst(E e) {
        final Node<E> f = first;
        final Node<E> newNode = new Node<>(null, e, f);
        first = newNode;
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }

直接使用add()方法,其实就是调用的尾插:

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

而带index的插入:

	public void add(int index, E element) {
		//检查插入位置的合法性
        checkPositionIndex(index);
		
        if (index == size)
        	//如果插入位置在尾部直接使用尾插
            linkLast(element);
        else
        	//如果index在前半段就使用头插,否则使用尾插
            linkBefore(element, node(index));
    }

	/**
     * Returns the (non-null) Node at the specified element index.
     */
    Node<E> node(int index) {
        // assert isElementIndex(index);
		//如果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;
        }
    }

实现接口

LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作
LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。
LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。

线程安全与否

同样,也不是线程安全的。可以考虑:

  1. Collections.synchronizedList(new LinkedList<>());
  2. ConcurrentLinkedQueue

总结:面试如何介绍ArrayList和LinkedList

  1. 数据结构:
    ArrayList底层是数组,采用懒加载的方式初始化,默认长度是10。添加元素如果超过数组长度,采用1.5倍大小进行扩容。
    LinkedList底层是双向链表,使头尾使用不包含有效元素的dummy节点。
  2. 查询效率:
    由于底层结构的关系,ArrayList实现了随机访问,查询更快。而LinkedList查找某个元素的时间复杂度是O(n)。
  3. 增删效率:
    ArrayList对于顺序增加非常快,但是指定索引的增删,需要移动索引后面的元素,效率不高。
    LinkedList增删一般来说很快,只需要删除指定节点并且连同前后两个节点即可。但是对于指定了索引的删除,就需要根据索引位置,选择从头节点或者尾节点出发找到索引,然后删除。
  4. 内存空间占用:
    ArrayList 的空 间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值