zoukankan      html  css  js  c++  java
  • 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(); 
    }
    金麟岂能忍一世平凡 飞上了青天 天下还依然
  • 相关阅读:
    OnEraseBkgnd、OnPaint与画面重绘
    .编译ADO类DLL时报错的解决方案
    VC列表框样式
    Codeforces 131D. Subway 寻找环树的最短路径
    Codeforces 103B. Cthulhu 寻找奈亚子
    Codeforces 246D. Colorful Graph
    Codeforces 278C. Learning Languages 图的遍历
    Codeforces 217A. Ice Skating 搜索
    Codeforces 107A. Dorm Water Supply 搜图
    Codeforces 263 D. Cycle in Graph 环
  • 原文地址:https://www.cnblogs.com/Auge/p/11649304.html
Copyright © 2011-2022 走看看