ArrayList和LinkedList的源码学习,理解两者在插入、删除、和查找的性能差异

List的使用

List的子类

1). ArrayList

  数据结构:数组

2). Vector

  数据结构:数组

3). LinkedList

   数据结构:循环双向链表

ArrayList 、Vector、LinkedList都来自AbstractList的实现,AbstratList直接实现了List接口并扩展自AbstactCollection。

一)、对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对象 ==

创建new ArrayList()时默认数组大小为 elementData = {}

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

== 对ArrayList进行add操作的源码剖析==

1). 首次添加元素时,给 elementData重新建立长度为10 的数组

2).添加元素时通过ensureCapacityInternal(size + 1)来判断内部容量是否需要扩容

I: 当内部容量需要扩容时,在原数组长度的基础上以1.5倍的规则进行扩展。

   II: 通过System.arraycopy()将原数组的元素复制到新数组中。

3).将元素添加到尾部add(E e):

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

private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

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

        // overflow-conscious code
        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) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

4).添加元素到指定位置add(int index, E element):

public void add(int index, E element) {
        //范围检查
        rangeCheckForAdd(index);
        //是否进行扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
    
        /**
        以index为原数组elementData的起始位置,顺序获取size -             index个元素,将这size - index个元素复制到以index+1为起始
        位置的elementData数组中。
        */
        System.arraycopy(elementData, index, elementData, index + 1,size - index);
        elementData[index] = element;
        size++;
    }

添加元素的核心代码:

/**
        以index为原数组elementData的起始位置,顺序获取size -             index个元素,将这size - index个元素复制到以index+1为起始
        位置的elementData数组中。
        */
        System.arraycopy(elementData, index, elementData, index + 1,size - index);

== 删除指定位置的元素 remove(int index) ==

public class Test {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("a");
        list.remove(0);
    }
}

public E remove(int index) {
        //范围查找
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            /**
            将elementData数组的的元素从index+1,开始共复制numMove
            个元素到elementData中从index开始为起始赋值位置
            */
            System.arraycopy(elementData, index+1, elementData, index,numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }
private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

remove的核心代码:

 /**
 将elementData数组的的元素从index+1,开始共复制numMove
 个元素到elementData中从index开始为起始赋值位置
 */
System.arraycopy(elementData, index+1, elementData, index,numMoved)

二)、对LinkedList进行操作

定义:一个LinkedList由多个表项连接而成,一个表项由3部分构成,前驱表项、内容、后驱表项。

== 向末尾添加元素add(E) ==

public class Test {
    public static void main(String[] args) {
        LinkedList list1 = new LinkedList();
        list1.add("1");
    }
}


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;
    //判断链表是否为null,若链表为空则将当前新建表项设为第一个表项
        if (l == null)
            first = newNode;
    //若最后一个表项不为空,则将该表项的next指针指向新的表项
        else
            l.next = newNode;
        size++;
        modCount++;
    }
    }

== 向指定位置插入元素add(int index, E e) ==

public class Test {
    public static void main(String[] args)  {
        LinkedList<String> list1 = new LinkedList();
        list1.add("1");
        list1.add(0,"bb");
         list1.add(0,"bb");
        System.out.println(list1.size());//size = 3
    }
}


public void add(int index, E element) {
        //检查范围,判断需要插入的位置是否在size范围内
        checkPositionIndex(index);
        //末尾插入
        if (index == size)
            linkLast(element);
        //在某个位置前插入
        else
            linkBefore(element, node(index));
    }

//根据index查询对应的表项
Node<E> node(int index) {
        // assert isElementIndex(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;
        }
    }

//改变链表的部分指向
void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

结论:

     1).当需要向List的末尾添加元素时,使用ArrayList的效率比LinkedList的效率

          高,但总体速率相差不大。

   原因: 

      i).使用LinkedList添加元素时每次都要new 一个Node对象,不间断的生成新

           的对象占用了一定的系统资源。

        ii).ArrayList只有在空间不足的情况下才会产生数组扩容和数组复制,所以

            决定大部份的追加操作效率非常高。



   2).操作List向指定位置添加元素时,若插入的位置在前半段或后半段部分使用

    LinkedList进行插入,若插入位置在中间使用ArrayList进行插入性能较好,若

    插入的数据在末尾 LinkedList和ArrayList的速率大致相同。



   3). 当频繁对List进行删除和插入操作时使用LinkedList.



    注:删除指定位置的元素原理和结论与在指定位置插入元素相同。

== 删除指定位置的元素remove(index) ==

public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }
//先找到对应的index对应的表项
 Node<E> node(int index) {
        // assert isElementIndex(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;
        }
    }
//修改链表的指向
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;
    }

三)、对List集合进行遍历操作

三种遍历方式:

List<String> list = new ArrayList();
List<String> list = new LinkedLisr();

//foEach;增强for循环
String temp = null;
for(String str : list){
    temp = str;
}

//迭代器
for(Iterator it = list.Iterator() ; it.hasNext() ; ){
    temp = it.next();
}

//for循环
for(int i = 0 ; i < list.size() ; i++){
    temp = list.get(i)
}

结论:

1).使用迭代器遍历LinkedList和ArrayList列表时速度相同

2).使用forEach遍历LinkedList和ArrayList列表时速度相同

3). 迭代器遍历速度比forEach遍历的速度快

4).使用for循环时遍历list时ArrayList的速率远远的大于LinkedList。

原因:使用for循环遍历采用了随机访问机制。遍历LinkedList列表时,每次 get(int index),都要对列表进行一次遍历操作,查找index对应的表项,再取出表项对应的值,而ArrayList是基于数组结构的顺序存储,直接通过下标则可以获取元素。

遍列表的速率:

迭代器 > forEach > for循环

四)、为什么更简单的forEach遍历速率会低于迭代器遍历呢?

原因:通过反编译知道forEach的遍历是基于迭代器遍历而实现的

反编译的迭代器遍历:

for(Iterator it = list.Iterator() ; it.hasNext() ; ){
    String s = it.next();
    String s1 = s; //forEach遍历比迭代器遍历多了一步赋值操作
    
}

迭代器遍历:

for(Iterator it = list.Iterator() ; it.hasNext() ; ){
    String s = it.next(); 
}

猜你喜欢

转载自www.cnblogs.com/Auge/p/11649304.html
今日推荐