java学习笔记-Arraylist(1)

ArrayList的继承树是AbstractCollection->AbstractList。它实现的接口有Collection、List、Iterable、Cloneable、Serialable等接口。


这些接口干了啥事儿呢,去看api,比我说的清楚。简单解释下就是集合、列表能使的功能他都能,然后它也能提供一个遍历器(Iterator),能用foreach(这个方法要求继承Iterator接口),也能被克隆(clone()),也能序列化保存在磁盘文件上。


我们都知道Arraylist的内部实现是一个数组。这就有意思了:我们也知道Java里只存在定长数组,而List是长度变化的呀!


其实,在new它的时候可以指定一个默认的大小,这个大小就是数组的容量,默认是10。当列表中要存超过数组容量的数据的时候,就必须扩容。ArrayList提供了一个private的grow方法来做这件事(add时会去调ensureCapacityInternal,它判断了容量够不够,不够就会去调grow),首先我们来看一下它是怎么实现的:


    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }


新容量=旧容量+旧容量除以2。>>1就是二进制数右移一位,大致相当于10进制除以2。


计算出新容量之后,进行一些容量限制的合法性判断,然后直接创建了个新的数组,把原本的内容copy进去。


那么旧的数组怎么办了呢?因为没有人用它管它了,垃圾回收器(gc)会处理掉的。换句话说,使用ArrayList时,如果初始容量定义的太小,那就会不停地去进行grow,不停地创建新数组、丢掉旧数组,就会占掉很多的内存!要知道,在内存将要耗尽之前,gc是不会执行的。而且运行gc也是需要耗时的!


那么把初始容量定义的大一点是不是比较好呢?也不是!由于数组定长的特点,如果初始容量定义的太大,就会有相当大一部分空间永远不会被使用,直到这个ArrayList的引用失效前,这部分内存甚至都不能被gc回收!


所以,ArrayList最好是在知道列表具体长度,而且不会发生太大的变化的时候用。


说的有点可怕,我们还是来看一下ArrayList有什么好处吧。(汗)


一个很有意思的地方,它的父类AbstractList在实现IndexOf和LastIndexOf的时候,使用了列表遍历器(ListIterator),通过它的next和previous方法一个一个进行遍历。但是ArrayList重写了IndexOf和LastIndexOf方法:


    public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }


汗,是不是很偷懒?实际上,由于数组的长度间隔是可知的这个特性(就算它的类型是对象:Object[],由于数组里实际存的不是对象本身而是引用,它的长度也是固定的),对ArrayList的随机访问就变得比较省时了,我们只要通过下标和类型大小计算出偏移量,然后去取那个地方的数据就行了。


比如:int型长度是32位:4个字节,那么我们要取下标为2的数据,只要偏移8个字节去取就行了。这种方式要比链表一个节点一个节点去遍历要高效很多,这也是ArrayList在随机访问的时候性能优于LinkedList的原因。


以及,toArray的实现:


    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

(笑)我本来就是数组呀,直接copy一下就行了,懒得不能再懒。


关于ArrayList更具体的实现,大家可以去看一下源码,肯定比光用要收益多得多。我也只是看了点皮毛,还需要学习一个,提高自己的姿势水平。

你可能感兴趣的:(java学习笔记-Arraylist(1))