Java集合源码--ArrayList

来源:互联网 发布:js 将时分秒转换成数字 编辑:程序博客网 时间:2024/06/08 02:26

最近学习了一下常用的两种动态代理技术(JDK动态代理和CGLIB动态代理),感觉Java是真的妙不可言。越学越有趣,原来我们虽然在实际项目中会去配置<aop:config>,可是不清楚到底为什么XML中写几个配置就可以完成日志打印,事务管理,权限控制的功能了,知道了动态代理的技术结合Java的反射就不难了解了,AOP说白了就是复杂版的HellWorld动态代理。关于CGLIB(Code Generation Libirary)动态代理的例子后面另写一篇文章吧。电脑上没有cglib的jar包,不好测试,cglib的好处是不需要接口,提供一个实现类即可完成动态代理的技术,重点是实现一个对父类方法调用的拦截器。把实际要加的代码加在involeSuper前后,如果类是Final的不能被继承的话就不能使用Cglib代理,当然这是后话。


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

学习Java一定要看源码,这是一个艰苦的过程,源码一般比较枯燥,不管是集合这部分还是难懂的Spring,但是这也是一个很好的思考的过程,这也是为什么新目录叫“血战源码“了,ok,今天我们先学习下最常用也是最基础的集合ArrayList。


1.定义

我们程序中经常会用到ArrayList,相信大家对这句代码不会陌生。

List<String> list = new ArrayList<String>();

我们定义一个String类型的ArrayList,Ctrl+左键点进去,点到ArrzyList里面去,发现ArrayList定义的代码如下:

public class ArrayList<E> extends AbstractList<E>        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{    private static final long serialVersionUID = 8683452581122892189L;    /**     * The array buffer into which the elements of the ArrayList are stored.     * The capacity of the ArrayList is the length of this array buffer.     */    private transient Object[] elementData;    /**     * The size of the ArrayList (the number of elements it contains).     *     * @serial     */    private int size;

ArrayList继承AbstractList,实现了RandomAccess(随机读取),Cloneable(可克隆),Serializable(可序列化)。

存储数据元素的其实是一个底层数组elementData,这个数组前面的关键字transient表明了这个数组不支持序列化(因为大部分情况下不需要完全序列化这个数组,不一定一直是慢的,如果数组很长,元素很少,大部分为空的话就没有序列化的必要了)。

size是ArrayList的大小。

当我们new ArrayList的时候,系统默认分配的长度为10。

public ArrayList() {this(10);    }
2.新增(list.add())

现在我们写一个添加操作,然后debug调试跟踪一下,

public class Test {public static void main(String[] args) {List<String> list = new ArrayList<String>();list.add("hello");
进入add方法的源码看一下,

public boolean add(E e) {ensureCapacity(size + 1);  // Increments modCount!!elementData[size++] = e;return true;    }

分两步操作。

(1)第一步后面注释就是增加modCount变量,这个modCount是执行操作的次数;

(2)第二步是很常见的赋值操作。
第一步方法的代码如下:

/**     * Increases the capacity of this <tt>ArrayList</tt> instance, if     * necessary, to ensure that it can hold at least the number of elements     * specified by the minimum capacity argument.     *     * @param   minCapacity   the desired minimum capacity     */    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);}    }
这个方法的意思是如果有必要扩充ArrayList实例的空间,确保它至少能装下minimum capacity个数的元素。如上面所说,初始化的时候分配的长度为10,默认为null,现在我们要添加元素,大致过程是这样的。

(1)设置10个长度的空间,list.add("hello");之后,会发现第一个部分被Hello对象填充,其他9个还是null;modcount变为1;

(2)接下来在执行9次

for(int i = 0;i<9;i++){list.add("sgx");}
发现当前List的内容是[hello, sgx, sgx, sgx, sgx, sgx, sgx, sgx, sgx, sgx],此时我们再次添加一个内容,调试跟踪:

此时modCount=11,old capacity=10,min capacity=11,观察上面的第一部分的代码,会扩充空间,新的elementData的空间大小为new capacity = (10*3)/2+1=16,

这个方法是别人给定的,为什么刚好是这个方法?应该是这个数值算一个合理数据,不至于过多次扩充空间。

Arrays.copyOf方法:

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {        T[] copy = ((Object)newType == (Object)Object[].class)            ? (T[]) new Object[newLength]            : (T[]) Array.newInstance(newType.getComponentType(), newLength);        System.arraycopy(original, 0, copy, 0,                         Math.min(original.length, newLength));        return copy;    }

创建了一个新的elementData【16】,然后将原来的elementData复制过去,现在的elementData是这样的:

[hello, sgx, sgx, sgx, sgx, sgx, sgx, sgx, sgx, sgx, null, null, null, null, null, null]

list的内容没有后面的null而已。


3.删除(list.remove())

现在我们又在刚才的基础上删除掉一个元素,看看源码是如何执行的;

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;    }
代码很明显,会先判断当前的对象是否是空,不为空的话,执行FastRemove()方法;

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; // Let gc do its work    }

fastRemove就是把要删除的元素开始的后面的部分整体前移,然后最后一个置为null即可。gc会自动回收没有用的这个元素。


4.总结

优点:其实ArrayList很容易让我想起刚学数组的时候,当时的插入、删除读取跟这个是一样的,因为ArrayList底层存储数据的就是数组;那么它的优点也就是数组的优点,

我们知道:数组是随机读取的,获取某个元素非常快;

缺点: 但是插入和删除由于涉及到扩充空间,如果扩充空间次数多的时候需要不停复制一段elementData数组,耗费性能。而且ArrayList明显不是线程安全的,很明显,没有同步修饰符;

原创粉丝点击