• 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(); 
    }
    金麟岂能忍一世平凡 飞上了青天 天下还依然
  • 相关阅读:
    C#控制键盘大小写切换
    C#表示空字符
    正则表达式30分钟入门教程[转自deerchao]
    学习deercao的正则笔记
    c# winform 全角自动转化半角问题(C#中ImeMode的值)
    C# 键盘中的按键对应的KeyValue
    这个Replace可以忽略大小写
    Keys枚举
    反射总结
    C# 中 KeyPress 、KeyDown 和KeyPress的详细区别[转]
  • 原文地址:https://www.cnblogs.com/Auge/p/11649304.html
Copyright © 2020-2023  润新知