ArrayList 源码解析 及其扩展(jdk1.7)

来源:互联网 发布:c js混淆工具 编辑:程序博客网 时间:2024/05/20 20:32

概述

ArrayList 基于数组实现,是一个动态数组,其容量能自然增长(1.5倍增长)。

不是线程安全,你可以使用Collection.synchronizedList方法将该列表包装起来,以防止意外对列表进行不同步的访问。也可以使用concurrent并发包下的CopyOnWriteArrayList类。

java 1.6API对其解释
返回指定列表支持的同步(线程安全的)列表。为了保证按顺序访问,必须通过返回的列表完成所有对底层实现列表的访问。在返回的列表上进行迭代时,用户必须手工在返回的列表上进行同步:   List list = Collections.synchronizedList(new ArrayList());      ...  synchronized(list) {      Iterator i = list.iterator(); // Must be in synchronized block      while (i.hasNext())          foo(i.next());  } 不遵从此建议将导致无法确定的行为。 如果指定列表是可序列化的,则返回的列表也将是可序列化的。

ArrayList实现了 List,RandomAccess,Cloneable,Serialiable 接口

RandomAccess接口,支持随机访问,实际上就是通过下标序号进行快速访问。实际上,实现此接口的List使用 for (int i=0, n=list.size(); i < n; i++) 这种方式迭代的速度会比用for(int i : list)会快一点

ArrayList实现(JDK1.7)

ArrayList中定义了四个私有属性:

private static final int DEFAULT_CAPACITY = 10;     //默认容量private static final Object[] EMPTY_ELEMENTDATA = {};     //一个空数组,当用户指定了0为容量时,返回该空数组private 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() {        super();        //默认等于空的数组        this.elementData = EMPTY_ELEMENTDATA;    }                //此方法的Collection是指集合类只要实现了Collection接口 都能重新转换为ArrayList(List接口已经继承了Collection接口)    public ArrayList(Collection<? extends E> c) {        //转换为数组        elementData = c.toArray();        size = elementData.length;        if (elementData.getClass() != Object[].class)            //调用native方法 快速构造数组            elementData = Arrays.copyOf(elementData, size, Object[].class);    }

ArrayList增加操作

'''    //将当前容量调整为实际个数    public void trimToSize() {        modCount++;        if (size < elementData.length) {            elementData = Arrays.copyOf(elementData, size);        }    }            public boolean add(E e) {        //此方法的关键是grow函数,增加元素是一个一个添加 所以size+1传入进去        ensureCapacityInternal(size + 1);          elementData[size++] = e;        return true;    }        private void ensureCapacityInternal(int minCapacity) {        if (elementData == EMPTY_ELEMENTDATA) {            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倍    oldCapacity>>1是把数转换为二进制 并向右移动一位 效果相当于 oldCapacity/2        int newCapacity = oldCapacity + (oldCapacity >> 1);        //判断增加后的大小够不够,够了就直接使用newCapacity创建新数组        if (newCapacity - minCapacity < 0)            newCapacity = minCapacity;        if (newCapacity - MAX_ARRAY_SIZE > 0)//如果增加后的大小比规定的最大size还大 则调用hugeCapacity方法            newCapacity = hugeCapacity(minCapacity);        //正常情况下 新的数组大小都为以前的1.5倍        elementData = Arrays.copyOf(elementData, newCapacity);    }        private static int hugeCapacity(int minCapacity) {        if (minCapacity < 0)             throw new OutOfMemoryError();        return (minCapacity > MAX_ARRAY_SIZE) ?            Integer.MAX_VALUE :            MAX_ARRAY_SIZE;    }

容量扩大,调用的是Arrays.copyOf(..,..);这个方法,虽然这个方法是native方法,源码里面是创建一个新的数组,然后将旧数组上的数组copy到新数组,这是一个很大的消耗。如果放在程序中,我们最好能够预计其大小,避免重复申请内存。


注意:

1.在jdk1.6中

public void ensureCapacity(int minCapacity) {      modCount++;     int oldCapacity = elementData.length;     if (minCapacity > oldCapacity) {         Object oldData[] = elementData;         int newCapacity = (oldCapacity * 3)/2 + 1;             if (newCapacity < minCapacity)         newCapacity = minCapacity;             // minCapacity is usually close to size, so this is a win:             elementData = Arrays.copyOf(elementData, newCapacity);     }}

很明显 这里的数组扩容没有使用位运算,而是直接使用除法和乘法,从效率上来看,jdk1.7 的ArrayList 会比 jdk1.6快一点(位运算更接近系统底层,有兴趣的同学 可以百度)

2.jdk1.6中没有定义MAX_ARRAY_SIZE的大小,所以无法做判断,这也是1.7中改进的地方。


例子

我们在15 16行打上断点

image

到达15行 还未运行完15行时 list中只有4个数据 所以size为4 因为刚开始定义了5为list的初始大小,则下标为4的为null

image

当运行完15行 到达16行时,很明显list已经满了

image

运行完16行后,list扩容为7 (5*1.5 省略小数点后面的数) 所以下标为6的为null

image

最后输出list.size()的大小,因为是返回list实际的大小 和elementData大小无关

image


说完了最复杂的增加操作,我们说删,改,查。

删除操作

//移除指定位置的数据 public E remove(int index) {        //检查是否下标越界        rangeCheck(index);        modCount++;        E oldValue = elementData(index);        int numMoved = size - index - 1;        if (numMoved > 0)            //elementData从第index+1下标开始 复制到原elementData的index下标开始 numMoved是复制的长度            //numMoved已经定义好了 是从要移除的下标号后面还剩余的数组长度            System.arraycopy(elementData, index+1, elementData, index,                             numMoved);        //数组最后一个数就为null了        elementData[--size] = null;         //返回移除后的数据        return oldValue;    }    private void rangeCheck(int index) {        if (index >= size)            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));    }                //移除指定数据public boolean remove(Object o) {    //判断对象是否为空    if (o == null) {        //迭代数组查找目标        for (int index = 0; index < size; index++)            //迭代数组里面为空数据的索引号,快速删除            if (elementData[index] == null) {                fastRemove(index);                return true;            }    } else {        for (int index = 0; index < size; index++)            //和节点比较            if (o.equals(elementData[index])) {                fastRemove(index);                return true;            }    }    return false;}//其实这个方法和remove(int index) 里面的方法如出一辙//可能开发人员没有重复把这个方法运用到remove(int index)里,private void fastRemove(int index) {        modCount++;        int numMoved = size - index - 1;        if (numMoved > 0)            System.arraycopy(elementData, index+1, elementData, index,                             numMoved);        elementData[--size] = null;     }

以上这些 就很好的说明了 为什么ArrayList增和删的效率不高了 都是要重新给数组赋值,或者新new一个数组接收更改后的旧数组,这样对内存的消耗会很大。

修改和删除

public E set(int index, E element) {        rangeCheck(index);        E oldValue = elementData(index);        elementData[index] = element;        return oldValue;    } public E get(int index) {        rangeCheck(index);        return elementData(index);    }

这个应该很简单了吧 我就不给注释了。

ArrayList扩展

我们在eclipse中 ctrl+t 查看实现list接口的类有哪些 image

这其中我们所了解的恐怕只有LinkedList和Vector了

这里大致说一下这两个集合类

LinkedList 底层是链表结构,而且是双链表,也可以当作堆栈,队列或双端队列进行操作。 特点是:查询效率低,增删效率高

Vector 底层也是用数组实现的,里面大部分方法都被声明了synchronized关键字,所以说是线程安全的集合,就是因为synchronized关键字的存在,这个集合本身就是很重量级,几乎很少用到。

关于线程安全和不安全的问题,因为我们是程序员嘛,当然会有办法外部控制这个操作,所以没必要纠结用哪个集合。但是如果有现成的类使用,就不要重复造轮子了。