ArrayList和LinkedList的数据结构

 

本文将从实现的角度来描述ArrayList和LinkedList的数据结构及各自的应用场景

 

ArrayList

 

从名称就可以猜到ArrayList底层使用数组来实现List容器。数组是静态容器,即它不支持扩容或者删除数据,ArrayList在数组的基础上增加了动态扩容和删除,所以ArrayList也可以称之为动态数组。

 

ArrayList有两个重要的变量:

 

int size;
Object[] elementData; 

 

size表示当前容器中存放的元素数量,elementData用来存放元素,elementData.length表示容器的容量。下图表示一个容器包含4个元素分别是1、2、3、4,容器的容量为8。

 

 

ArrayList使用下标访问(get(index)) 时效率很高,因为数组可以直接使用下标访问元素, 它的算法复杂度为O(1)。 对于那些非数组实现的容器,下标访问需要从头开始遍历容器。

 

在添加元素时如果容器的容量已满即size=elementData.length时会触发扩容动作,扩容动作会重新分配一个大容量的数组,然后把数据拷贝到新数组里面。当容器里面数据很多时,拷贝数据会影响性能,所以扩容操作应该尽可能的少,Java使用每次扩容elementData.length / 2的策略来减少扩容动作。添加元素在最坏情况下的算法复杂度为O(n),下图是扩容操作示意图:

 

 

容器允许删除任意位置的元素,为了保证数据的连续性,在一个位置删除数据以后需要把后面的数据往前移动(把元素往前面拷贝),在数据量很大时移动数据操作会影响性能。删除操作在最坏情况下的算法复杂度为O(n),删除操作示意如下:

 

 

综上, ArrayList具备很高的下标访问效率,但是增加和删除数据效率可能较低,所以它不适用于频繁增删数据的场景。

 

LinkedList

 

LinkedList底层使用双向链表来实现,它有三个重要的变量:

 

int size = 0; 
Node first;
Node last; 

 

size表示当前存储的元素数量,Node表示结点,first表示链表的头结点,last表示尾结点,Node定义如下:

 

private static class Node {
        E item;
        Node next;
        Node prev;

        Node(Node prev, E element, Node next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

 

Node有三个成员变量:item、next、prev,item表示存储的元素,next表示链表下一个结点,prev表示链表上一个结点。

 

下图是一个LinkedList结构图,它存储了四个元素分别是:1、2、3、4:

 

 

从上图可以看到first结点的prev为null,因为头结点前面已经没有结点了,同理last结点后面已经没有结点了,所以last结点的next为null。

 

LinkedList的下标访问(get(index))相对增加和删除来说效率偏低,它需要从头或者从尾遍历容器,最坏情况下需要遍历半个容器,算法时间复杂度为O(n)。

 

向LinkedList插入或增加新元素非常简单,只需重新调整结点的next和prev即可,它的算法复杂度为O(1),下图是插入一个元素的示意图:

 

 

从LinkedList中删除一个元素也很简单,它和插入新结点一样只需调整结点的prev和next,算法复杂度为O(1),下图是删除结点示意图:

 

 

综上,LinkedList的插入和删除操作效率很高,但是它的下标访问效率低,所以它不适用于需要频繁使用下标访问数据的场景。

 

【水煮Java】

你可能感兴趣的:(水煮Java,ArrayList数据结构,LinkedList数据结构)