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