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

时间:2022-06-23 10:32:43

   在了解集合的时候,都会学到不同集合之间的区别,比如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  夜  杭州