ArrayList 数据结构分析

1,621 阅读4分钟

ArrayList

在Android中的大红人,经常用到,但是我却没有仔细考虑过,为啥直接就new 一个ArrayList,为啥不用LinkedList,什么情况下用?所以对ArrayList分析一下,学习一下。

代码分析:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  • 继承AbstractList<E>类

AbstractList 继承自 AbstractCollection 抽象类,实现了 List 接口,实现了一些接口如 get,set,remove,且实现了迭代器Iterator,简化我们要实现一个数据结构的操作,比如实现一个顺序表Array,或实现一个链表LinkedList等。

  • 实现List<E>接口

List 一个元素有序,可重复,可为null的集合,实现List接口的类,元素通过索引进行排序

  • 实现RandomAccess接口

一个标记接口,支持快速随机访问

  • 实现Cloneable接口

支持clone

  • 实现Serizllizable接口

序列化,对象的信息可以通过网络传输,可以持久化写入存储区

属性


    //默认大小
    private static final int DEFAULT_CAPACITY = 10;

    //数组的实现方式 顺序表实现
    transient Object[] elementData;
    
    private int size;
    

构造方法

//可以传入容量大小
 public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
    }
    //传入已有的数据集合
     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);
    }

add(E e)

添加一个元素
   public boolean add(E e) {
   //检查容量
        ensureCapacityInternal(size + 1); 
        // Increments modCount!!
    //末尾添加
        elementData[size++] = e;
        return true;
    }
  1. 首先检查数组大小容量
 private void ensureCapacityInternal(int minCapacity) {
        if (elementData == 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);//默认扩容当前容量的1/2
        if (newCapacity - minCapacity < 0)
        //如果扩容大小还不足以容纳,采用传进的大小进行扩容
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
        //判断是否超出了最大了容量限制 
       // private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        //将原来的数据copy到新的数组,并完成扩容操作
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
  1. 添加元素默认是在末尾添加

add(int index, E element)

指定位置插入指定的元素

 public void add(int index, E element) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        /**
        *参数1:数据源的数组
        *参数2:copy的起始位置
        *参数3:新的接收数组
        *参数4:新的将要赋值的起始位置
        *参数5:copy的长度
        /
        //将index后面的数据后移一位
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
                         
        //将空出的index位置赋值element
        elementData[index] = element;
        //容量加一
        size++;
    }

从上面可以看出,在中间位置插入一个元素,时间复杂度为O(n)

get(int index)

  public E get(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
//直接取出数组index位置的元素
        return (E) elementData[index];
    }
    

可以看出,Arraylist读取元素是比较快的O(1)

remove(int index)

  public E remove(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        //记录数据结构改变的次数
        modCount++;
        //获取index位置的元素
        E oldValue = (E) elementData[index];
        //删除index的元素,其它元素移动的位数
        int numMoved = size - index - 1;
        //将index后面的元素前移numMoved位
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
        return oldValue;
    }

##其它常见方法

//获取元素位置的索引
    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;
    }
    
    //获取最后一个o的索引
     public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

  public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }
    
      public boolean addAll(int index, Collection<? extends E> c) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount

        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

      public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

从上面的简单源码分析可以看到,ArrayList底层是以个数组实现的顺序表,在插入,修改,删除的过程中效率不高,O(n) ;在查询的时候效率较高,读取速度快O(1),在Android 手机端,我们常见的listview中对读取的速度要求较高,所以常采用Arraylist.

Other

顺序表

将表中元素一个接一个的存入一组连续的存储单元中,这种存储结构是顺序结构。

transient

java语言的关键字,变量修饰符,如果用transient声明一个实例变量,当对象存储时,它的值不需要维持。换句话来说就是,用transient关键字标记的成员变量不参与序列化过程。