Java集合源码--ArrayList

最近学习了一下常用的两种动态代理技术(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明显不是线程安全的,很明显,没有同步修饰符;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值