ArrayList一些问题小结

三个变量的作用

    private static final Object[] EMPTY_ELEMENTDATA = {};

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    transient Object[] elementData; // non-private to simplify nested class access

初始化的时候如果不指定长度,将会使用构造器 public ArrayList(),这时将 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 赋值给 elementData,会创建一个大小为 10 的 Object 数组。也就是,当 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 才会被设置长度为 10。

如果使用指定长度的构造器,也就是

    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

这是如果等于 0 的话,这是 EMPTY_ELEMENTDATA 被赋值给 elementData.

集合扩容是怎么扩的?

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);
}

我们可以看到一句代码

    int newCapacity = oldCapacity + (oldCapacity >> 1);

发现增长率为 50%

计算 HashCode

首先我们从 List 类中看出对 hasCode 的定义:这个方法主要是用于对比两个 list 类是否相等。所以当我们使用这个方法的时候,具体实现(AbstractList)是遍历好所有里面的数据元素的。

public int hashCode() {
    int hashCode = 1;
    for (E e : this)
        hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
    return hashCode;
}

这一般来说,是用来做比较两个 ArrayList 是否相等的情况。

ArrayList 是如何 remove 元素的?

其实 ArrayListremove 是通过将删除的元素设为 null ,然后等着 jvm 进行垃圾内存回收。

public E remove(int index) {
    rangeCheck(index);

    modCount++;
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    elementData[--size] = null; // 清除等待虚拟机进行回收

    return oldValue;
}

不仅仅是 remove 方法,还有 clear

关于 elementData 已经被 transient 修饰了为啥还可以被序列化?

其实这个问题很简单,我们直接看序列化的源码

private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException{
    int expectedModCount = modCount;
    // 让 jvm 知道你要写入 non-transient 和 non-static 的数据
    s.defaultWriteObject();

    // 确定写入的个数
    s.writeInt(size); 

    // 按照顺序序列化
    for (int i=0; i<size; i++) {
        s.writeObject(elementData[i]); 
    }

    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

我们可以看出,其实被 transient 修饰的变量是告诉 jvm,在序列化 ArrayList 的时候,不要处理 elementData,由我自己来处理这些数据。为什么这么做呢?这个节省空间有关。因为如果一个 ArrayList 定义了 100 个元素,但实际上只有 69 个元素。那么多出来的造成序列化浪费了。所以我们需要手动操作。

扫描二维码关注公众号,回复: 2868201 查看本文章

补充:在手动序列化的过程中,记得序列和反序列的字段顺序需要一致。例如你序列化了字段 a=1,b=2,c=3。那么你读的时候也要按照这个顺序来。否则出现反序列数据错误。

猜你喜欢

转载自blog.csdn.net/T1014216852/article/details/80475736