Collection->List->ArrayList、LinkedList实现原理

  我们在开发中会进场用到List来进行数据的处理尤其是它的两个实现ArrayList、LinkedList
  但是这两种实现究竟有什么区别,这个在之前一直是没有仔细考虑的,一般写代码就直接一个ArrayList实现初始化对象,在企业级的互联网开发中比较少有数据处理超过100万的,这导致了我们在开发中对这两个实现的具体适合的应用场景有所忽略。
  那么下面我们从这两种实现类的存储原理、源代码处理逻辑来进行分析,找出这两种类的区别以及何时的使用场景。

环境JDK8

一、 存储原理:
1、ArrayList内存存储原理:

ArrayList的类成员变量

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;

从成员变量的定义可以看出ArrayList是对数组的一个封装,那么这时候可以根据数组的特点初步判断:在进行正常的读写操作时将会是比较便利的。

2、LinkedList内存处处原理:

LinkedList的类成员变量:

transient int size = 0;

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

我们可以看到LinkedList的两个关键的变量first、last,类型是一个内部的Node类,而且里面有next以及prev两个引用以及泛型对象本身的引用,这是一个链表式的结构:

此结构不难看出:要对这个链表结构进行操作一定是从first或者last开始的,获取中间的元素都要通过一个一个遍历的方式来进行获取,当然其也有他的优势,在排序或者常规递增增加元素时有ArrayList所没有的优势。

二、功能实现对比

1、List的常规add(obj)方法

(1)ArrayList实现

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);
        elementData[size++] = e;
        return true;
    }
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//确认初始化的容器大小是否是长度为0的容器
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);//判断容器是否满足要求,进行扩容
    }
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    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);//新建一个数组然后将原数组进行复制
    }

    private static int hugeCapacity(int minCapacity) {//最大容量是Integer.MAX_VALUE
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

这段代码可以看出如果new一个ArrayList没有进行大小初始化的时候,在第一次add的时候会有一个初始化容器大小10,在操作过程中如果出现容器大小不够时,会重新创建一个3/2大小容量的数组,然后进行数组复制。也就是说如果在容器够的情况下ArrayList

的操作速度还是很快的。

(2)LinkedList实现

    public boolean add(E e) {
        linkLast(e);
        return true;
    }
    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++;
    }

典型的链表操作逻辑,重新new一个Node节点然后调整last引用以及newNode和lastNode内部的引用,达到add的操作效果。从实现原理来看LinkedList是没有add的数量上限的,并且add操作的也比较快。

2、List的常规add(int index, E element)方法

(1)ArrayList实现

    public void add(int index, E element) {
        rangeCheckForAdd(index);
        ensureCapacityInternal(size + 1);  //扩容判断以及执行
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);//将index往后的元素整体向后移动一位
        elementData[index] = element;
        size++;
    }

这里的实现很明显这种操作需要对数组内容copy,并且index的值相对于容器size越小,进行copy的元素就越多,资源消耗的也就越多了。

(2)LinkedList实现

    public void add(int index, E element) {
        checkPositionIndex(index);//进行index越界检查

        if (index == size)
            linkLast(element);//直接进行尾部追加
        else
            linkBefore(element, node(index));
    }
    Node<E> node(int index) {//遍历找到对应的Node节点
        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;
        }
    }

在插入某一个节点时,还是会对链表进行遍历,找到对应index位置的节点,然后进行节点的插入操作,也就是index的值相对于容器size的值越大,遍历的数量越多,相应的消耗的也越多,这种情况会比ArrayList的会优越一些。

3、List的常规get(int index)方法

(1)ArrayList实现

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

        return elementData(index);
    }
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }
    E elementData(int index) {//直接通过数组下标获取元素返回
        return (E) elementData[index];
    }

内部实现直接通过数组下表获取元素这种方式无疑是最快的方式了。

(2)LinkedList实现

    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;//通过遍历获取对应index位置的元素
    }

从实现上可以看到index位置越是靠后,需要遍历的元素就越多,这种方法的性能是没法跟ArrayList进行比较的。

 4、List常规方法remove

(1)ArrayList实现

    public E remove(int index) {//直接通过index对数组进行copy移位,并且最后一个赋值null
        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;
    }

    public boolean remove(Object o) {//先要遍历对比,获取对应的index然后进行移除操作
        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) {//数组移除,需要将index后的数据copy前移一位,最后一位赋值null
        modCount++;
        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
    }

两种remove都要进行数组中部分元素的copy移位,不同的是remove(obj)多了一个遍历比较的过程。

(2)LinkedList实现

    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));//node方法遍历寻找对应的index的node然后调用unlink
    }
    public boolean remove(Object o) {//遍历比较相等的数值找出对应的node然后调用unlink
        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) {
            first = next;//边界情况
        } else {
            prev.next = next;//改变上一个节点的next引用
            x.prev = null;//这里进行引用的消除方便回收
        }

        if (next == null) {
            last = prev;//边界情况
        } else {
            next.prev = prev;//改变下一个节点的prev引用
            x.next = null;//这里进行引用的消除方便回收
        }

        x.item = null;//这里进行引用的消除方便回收
        size--;
        modCount++;
        return element;
    }

两种remove都要进行遍历,然后调用unlink进行链路节点的删除,不同的是remove(obj)多了一个equals的对比操作

 小结:

通过以上的list常用的功能对比他们的实现,暂做出如下的总结:

  当list的读操作比较多(不涉及排序、无序插入、元素移除)时,ArrayList具有明显的优势,如果可以根据业务判断容器使用的大小,那么ArrayList是绝佳的选择。

  而LinkedList在排序、无序插入、元素移除等写的方面具有优势。

  我们在使用的时候可以根据业务场景判断读写的具体情况进行选择。

 补充:

ArrayList以及LinkedList都实现了iterator相关的接口,但是获取的iterator对象的相关遍历操作都是基于原有的数据存储结构进行操作的,因此在性能上与原有的list本身操作并无差距

三、性能测试验证

我们已经了解了数据的存储结构,以及根据接口实现原理大致推断了性能差别,但是还是需要实际的测试进行验证,关于验证会在后续进行补充的

  

原文地址:https://www.cnblogs.com/liushi805347332/p/java_List.html

时间: 2024-10-09 03:15:22

Collection->List->ArrayList、LinkedList实现原理的相关文章

LinkedList实现原理

一.源码解析 1. LinkedList类定义. public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable LinkedList 是一个继承于AbstractSequentialList的双向链表.它也可以被当作堆栈.队列或双端队列进行操作.LinkedList 实现 Li

List ArrayList LinkedList vector简介与区别

ArrayList,LinkedList,Vestor这三个类都实现了java.util.List接口,但它们有各自不同的特性,主要如下: ArrayList:底层用数组实现的List 特点:查询效率高,增删效率低 轻量级 线程不安全 LinkedList:底层用双向循环链表 实现的List 特点:查询效率低,增删效率高 Vector: 底层用数组实现List接口的另一个类 特点:重量级,占据更多的系统开销 线程安全 一.同步性 ArrayList,LinkedList是不同步的,而Vestor

ArrayList LinkedList Vector

ArrayList是基于数组实现的,没有容量的限制. 在删除元素的时候,并不会减少数组的容量大小,可以调用ArrayList的trimeToSize()来缩小数组的容量. ArrayList,LinkedList,Vestor这三个类都实现了java.util.List接口,但它们有各自不同的特性,主要如下: ArrayList:底层用数组实现的List 特点:查询效率高,增删效率低 轻量级 线程不安全 LinkedList:底层用双向循环链表 实现的List 特点:查询效率低,增删效率高 Ve

ArrayList的实现原理--转

1. ArrayList概述: ArrayList是List接口的可变数组的实现.实现了所有可选列表操作,并允许包括 null 在内的所有元素.除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小.   每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小.它总是至少等于列表的大小.随着向ArrayList中不断添加元素,其容量也自动增长.自动增长会带来数据向新数组的重新拷贝,因此,如果可预知数据量的多少,可在构造ArrayList时指定其容

ArrayList,LinkedList的对比

ArrayList,LinkedList都是Collection接口的通用实现方式,两者采用了不用的存储策略,用来适应不同场合的需要. 实现方式 ArrayList的内部采用集合的方式存储数据 唯一需要注意的是对于容量超过阈值的处理逻辑,数组的默认容量大小是10,最大容量是Integer.Max_Value,超过最大容量会抛内存溢出异常, 扩容机制看下面 扩容后的容量是原有容量的1.5倍 LinkedList的实现方式 内部采用双向链表Node内部类来存储数据,由于采用了双向链表,LinkedL

Android ArrayList LinkedList Set HashMap的介绍.

在Android开发中我们经常需要对数据进行分类和操作,对于轻量级的数据存储我们可能不需要动用SQLite或效率以及类库不完善的XML,由于 SharedPreferences不具备数据枚举方法,如果仅仅是一个String或Int数组可以通过一个标记分割设计外,我们还是主要来看看 Android或者说Java提供的基础数据类型辅助类ArrayList LinkedList Set HashMap的介绍. 在Java中提供了Collection和Map接口.其中List和Set继承了Collect

ArrayList,LinkedList,Vestor

Collection是最基本的集合接口,声明了适用于JAVA集合的通用方法,list和set都继承自collection接口. Collection接口的方法 boolean add(Object o):向集合中加入一个对象的引用 void clear():删除集合中所有的对象,即不再持有这些对象的引用 boolean isEmpty():判断集合是否为空 boolean contains(Object o): 判断集合中是否持有特定对象的引用 Iterartor iterator():返回一个

Collectio集合,List《ArrayList,LinkedList》

集合: Collection类 package com.collection.demo; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; /** * 数组: 存储同一类型的元素组成集合,固定长度,数组中元素可以是基本数据,也可以是对象. * 集合: 存储不同类型对象的容器, 长度可变,集合中的元素必须是Object的子类,不能是

LinkedList实现原理(JDK1.8)

LinkedList实现原理(JDK1.8) LinkedList底层采用双向链表,如果对链表这种结构比较熟悉的话,那LinkedList的实现原理看明白就相当容易. 链表通过"指针"将一组零散的内存块串联起来使用,每一个元素(节点)通过指针指向它的下一个元素,最后一个节点的下一个指向为null,而双向链表就是除头节点的每一个元素都有指针同时再指向它的上一个元素.链表不同于数组,由于其地址的不连续,且元素占用大小的不确定,所以没法根据地址直接取值,获取元素时需要遍历访问,而双向链表相比