因为是楼主第一篇博客,楼主又是菜鸡,所以决定挑个软柿子捏一捏,瞅一瞅ArrayList。
简介:
楼主是jdk1.8,大家看的时候注意下版本。ArrayList的底层是数组,并且可以动态扩容的。
继承关系:
实现了四个接口,一个抽象类。四个接口没啥好说的。
属性:
东西比较少,并且也不复杂。
构造方法:
上面的两个构造方法大家应该都很熟悉,下面有个不太常见的构造方法,我们仔细看一下。
Arrays.copyOf和c.toArray()最后调用的下面的copyOf这个方法if (elementData.getClass() != Object[].class)这一步的判断特地拿出来说一下,虽然上面的elementData = c.toArray()基本上得到的大概率是Object类型,但是这里的判断还是看不懂,既然需要一个Object类型的数组,为啥第一次调的时候不给定Object类型呢?第二次再去拷贝一遍?大家谁知道可以留下言指点一下。
public static native void arraycopy(Object src, int srcPos,Object dest, int destPos,int length)
这个方法的大概意思如下:
其实就是浅拷贝一个新的数组,指定了老数组从哪开始(srcPos),拷贝多少个(length),新数组从哪开始(destPos)。
因为是native方法,底层是c写的,看不见。楼主也没去找具体的源码,就简单用java实现下伪代码:
可以看到,要是ArrayList扩容的次数过多,肯定是极其影响性能的,所以用ArrayList还是要尽量给定容量。
下面看一下ArrayList的方法,这里只分析一下几个常用的方法。
add(E e)方法:
也比较简单,就是判断一下当前数组的元素的个数加1,是否会大于容量,大于就要进行扩容,这样的add不用扩容的话其实也很快。
扩容grow方法:
扩容方法也不复杂,大概就是扩容1.5倍,然后重新copy一份新的数组。顺便说一下Arrays.copyOf这个方法,是浅拷贝。
这里大概捋一下浅拷贝的意思:
1:当数据类型是基本数据类型,浅拷贝会进行值传递,也就是复制一份新的数据,改变其中一个对象,不会影响另一个对象。
2:当数据类型是引用数据类型,浅拷贝会进行引用传递,也就是将原对象的引用值(内存地址)复制给新对象,说白了就是2个对象指向同一块内存地址,所以改变其中一个对象,会影响另一个对象。
下面我们简单验证一下:
这里首先用基本数据类型测试一下,发现是不会相互影响的。
这是引用数据类型的测试,发现改变一个对象是会影响另一个,因为他们同时指向的同一块内存地址。
add(int index, E element):
if (index > size || index < 0)这句话的意思是个人的想法,不知道对不对。
System.arraycopy(elementData, index, elementData, index + 1, size - index)到最后仍然调用的nactive的public static native void arraycopy(Object src, int srcPos,Object dest, int destPos,int length)方法。
这个方法的意思其实上面说过了,只不过这里的新老数组是同一个,再描述一下:
下面再看下伪代码:
可以看到,指定下标的add确实很慢。
remove(int index):
System.arraycopy(elementData, index+1, elementData, index, numMoved)大概意思如下图:
下面再看下伪代码:
可以看到,指定下标的remove确实很慢。
remove(Object o):
其实2种删除方法差不多,只不过下面多了一步,先遍历找到下标,然后再删,,效率也很低。
set(int index, E element):
更新超级简单,就是检查下,重新赋值。
get(int index):
查询也没啥好说的,可以看到查询确实快,直接根据下标查找的。
contains(Object o):
也很简单,遍历判断,但是这种遍历查找相当于线性查找,效率最低。
modCount:
相信大家多次看到过这个参数,下面来看下这个参数有啥用。其实直接操作ArrayList是没有的,但是我们遍历ArrayList的时候,很多人要用到list.iterator()迭代遍历,这时候就有用了。
相信很多人这么写过:
迭代遍历,让我们来看下迭代的源码:
这个modCount是为了保证迭代的时候没有别的线程操作当前ArrayList,保证准确性的。
第一篇文章终于结束了,下篇分析LinkedList(不知道还有没有…)。