• ArrayList和LinkedList 内部结构分析(一)


       在了解集合的时候,都会学到不同集合之间的区别,比如ArrayList和LinkedList,其中ArrayList是类似于数组结构的,查询比较快速。而LinkedList则是链表结构,在插入和删除的时候效率较高。

      通过研究源码,可以更深入的了解其内部实现,真的是ArrayList所有查询都快么?  真的是 LinkedList所有的插入和删除都更效率么?

      废话不多说,上代码!

      一、先摘抄一些ArrayList的代码吧,这里只分析一些关键的方法:

    1、ArrayList的内部结构是怎么样的?

    1public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    {
       private static final Object[] EMPTY_ELEMENTDATA = {};
    //这就是ArrayList集合内部结构,是一个object数组
    private transient Object[] elementData;
      //集合里面实际有值的长度
    private int size;   //构造函数,入参为初始化list长度 public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; }
      //默认构造函数,初始化数组大小为0
    public ArrayList() { super(); this.elementData = EMPTY_ELEMENTDATA; }
      //入参为集合将集合类型数据为数组,list长度为数组长度
    public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); size = elementData.length; // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } }

    2、对ArrayList中元素进行操作的实现?

    get(index):其实就是根据index,直接获取对应数组下角标的值

    \根据下角标获取元素
    public E get(int index) {
        \检查下角标范围
            rangeCheck(index);
          \根据下角标返回数组元素
            return elementData(index);
        }
    
    \下角标范围必须小于数组有值的实际长度
    private void rangeCheck(int index) {
            if (index >= size)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }

    \获取下角标元素
    E elementData(int index) {
    return (E) elementData[index];
    }
    set(int index, E element):这里需要稍微注意一下的是,set()方法返回的结果是对应index值的原始值。
    public E set(int index, E element) {
           \检查下角标
            rangeCheck(index);
            \原index 对应的值
            E oldValue = elementData(index);
           \设置新值
            elementData[index] = element;
          \返回原值
            return oldValue;
        }

    add(e):当添加一个元素时候,默认添加到list最后,并且对容器大小是否够容纳进行check,如果容器大小不够,则将原来容器

    增大到1.5倍,在这种情况还不够的情况,则增加到最小要求容器长度。

    \添加一个元素
    public boolean add(E e) {
          \确保当前elementData容量是否足够
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }
    
    \确保当前elementData容量是否足够,入参:最小需要长度
    private void ensureCapacityInternal(int minCapacity) {
          
            if (elementData == EMPTY_ELEMENTDATA) {
               \如果elementData为空,则选择默认长度DEFAULT_CAPACITY或者最小长度
                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) {
            \原始容器长度
            int oldCapacity = elementData.length;
           \新容器长度为原来长度的1.5倍
            int newCapacity = oldCapacity + (oldCapacity >> 1);
    
           \1.5倍的容器长度还比最小要求长度小
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
    
            \判断新容器长度超过最大限制
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            
            //将原来数组的值copy新数组中去, ArrayList的引用指向新数组
         //这儿会新创建数组,如果数据量很大,重复的创建的数组,那么还是会影   响效率,
            elementData = Arrays.copyOf(elementData, newCapacity);
        }

    在最后需要通过Arrays.copyOf(elementData, newCapacity),复制整个数组的方式来实现添加数据,由此看来add(e)在需要扩容的时候添加元素,

    效率是比较低的。

    add(int index, E element):在指定位置后插入元素
     public void add(int index, E element) {
            rangeCheckForAdd(index);
            // 如果数组长度不足,将进行扩容。
            ensureCapacityInternal(size + 1);  // Increments modCount!!
    
           //将当前位于该位置的元素以及所有后续元素右移一个位置。
            System.arraycopy(elementData, index, elementData, index + 1,
                             size - index);
            elementData[index] = element;
            size++;
        }

    可以看到,在指定位置插入元素,首先需要进行容器长度check,如果不足,会进行一个数组copy,接下来还

    将当前位于该位置的元素以及所有后续元素右移一个位置。效率更加低了!


    remove(int index):
    public E remove(int index) {
            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;
        }

    首先是检查范围,修改modCount,保留将要被移除的元素,将移除位置之后的元素向前挪动一个位置,将list末尾元素置空(null),返回被移除的元素,

    移除一个元素,除了最后一位,别的都需要将之后的元素前移一位,可见效率很低!

     

    contains(o):直接通过遍历数组,来判断,说明遍历查找效率较高

    //是否包含某元素,遍历整个list 
    public boolean contains(Object o) {
            return indexOf(o) >= 0;
        }
    
    
    public int indexOf(Object o) {
          //直接通过遍历数组,来判断是否存在元素,效率较高
            if (o == null) {
                for (int i = 0; i < size; i++)
                    if (elementData[i]==null)
                        return i;
            } else {
                for (int i = 0; i < size; i++)
                    if (o.equals(elementData[i]))
                        return i;
            }
            return -1;
        }

    ------------------------------------

    上面的源码能看出来,对于ArrayList是如何动态的控制长度,在插入元素、新增元素、删除元素的时候,效率是比较低的。而遍历查找元素,效率较高!

    LinkedList,见下一篇吧,太困了!

    23:57:43

    2016/03/23  夜  杭州

     
    
    
  • 相关阅读:
    java序列化进阶
    jQuery学习一(选择器)
    java poi 操作Excel常用方法总结
    mybatis批量foreach的使用
    java中关于try、catch、finally的总结
    MyEclipse使用指南(精简版)
    单例模式
    抽象工厂模式
    java.io.Serializable
    java.lang.string
  • 原文地址:https://www.cnblogs.com/guoliangxie/p/5306318.html
Copyright © 2020-2023  润新知