您的位置:首页 > 编程语言 > Java开发

深入了解Java ArrayList及其动态调节数组容量机制

2017-08-14 22:16 573 查看
我们知道,java是允许在运行时才确定数组大小的,Object[] obj = new Object[actualSize];但是这样并没有解决动态更改数组的问题,一旦空间大小被确定,就无法再扩大了,ArrayList很好的解决了这个问题,它可以动态地增加数组列表的容量。

1.构造:ArrayList< Object> obj = new ArrayList< Object>();

jdk1.7构造函数源码:
private transient Object[] elementData;
private int size;

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

/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this(10);
}


ArrayList类中存在两个私有成员(当然不仅仅只有两个),elementData是一个Object类型的数组,用来保存元素;size表示ArrayList中实际的元素数目(所以size()方法返回的是实际元素的数目)。我们可以看到以这种形式定义的数组列表的默认容量是10。这里注意一下,在以上的构造函数中,并没有给私有成员size赋值,因为size在这里表示的是实际的元素数目,而刚刚初始化之后,数组列表中是没有任何实际元素的,所以size默认为0。所以,在使用obj.get(0)是会报错的。

public E get(int index) {
rangeCheck(index);
return elementData(index);
}

private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

E elementData(int index) {
return (E) elementData[index];
}


2.除了以上的构造方法外,当我们大致可以估计需要存储多少元素数量时,还可以用这种方法:ArrayList< Object> obj = new ArrayList< Object>(20); 这样数组列表的容量就是20了,这等价于

ArrayList<Object> obj = new ArrayList<Object>(20);
obj.ensureCapacity(20);


我们来看一下ensureCapacity(minCapacity)的源码

public void ensureCapacity(int minCapacity) {
if (minCapacity > 0)
ensureCapacityInternal(minCapacity);
}

private void ensureCapacityInternal(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

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


看了这几段代码,ensureCapacity(minCapacity)的作用就显而易见了,就是增加数组列表的容量,原理很简单,首先判断该参数是否大于0并且是否大于当前容量,然后就到了grow方法,其他的语句先别管,看最后一句,elementData = Arrays.copyOf(elementData, newCapacity); 这条语句先按指定参数创建一个更大的数组,然后把原数组的元素一个个拷贝到大数组中,最后将elementData 的引用指向这个大数组。这样,就增大了数组列表的容量,而小数组也会被gc回收。所以数据量很大的时候还是建议初始化的时候就指定容量,提高效率

Arrays:
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;
}


3.看到这里,也对ArrayList动态增加数组容量有了一定的理解,现在来看看add方法在添加新元素时是怎么动态增加数组容量的

public boolean add(E e) {
ensureCapacityInternal(size + 1);  // Increments modCount!!
elementData[size++] = e;
return true;
}

private void ensureCapacityInternal(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); //jdk1.7之后的容量增长机制是这样的,还有这样计算的newCapacity = (oldCapacity * 3)/2 + 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);
}


其实原理一样,因为数组的大小一旦确定是不能被改变的,所以当内部数组已经满了之后还要再添加元素时,是创建一个更大的数组,然后把原数组的元素一个个拷贝到大数组中,最后改变引用指向。这里有个疑问,add方法动态增加了多少容量呢,现在可以看一下grow方法的其他语句, int newCapacity = oldCapacity + (oldCapacity >> 1); 假设原容量为10,那么新容量就为15,再次增加容量会变成22,原容量不同,每次增加的容量也就不同。jdk1.7之后容量增加的计算机制是这样的,我还见过有这样的:newCapacity = (oldCapacity * 3)/2 + 1。这里要注意,如果原容量为10,使用ensureCapacity(11),得到的新的容量不是11,而是15。所以,ensureCapacity并不是按照参数来指定新容量的。

4.

ArrayList<Object> array = new ArrayList<>();
Object[] obj = new Object[10];


这两条语句都开辟了10个元素的空间,一个表示数组列表,一个表示数组。虽然ArrayList是用数组来实现的,这两者还是有一定区别的,数组有大小,是固定的;数组列表拥有的是容量,并且是可变的,数组和数组列表在初始化之后,System.out.println(obj[0]); 可以打印null;而System.out.println(array.get(0));会报错。如果不看源码,还以为数组列表连存储空间都没开辟。

5.看源码可以知道ArrayList里的方法都不是同步的,所以当不止一个线程要修改ArrayList实例时,必须让它保持外部同步。可以使用List list = Collections.synchronizedList(new ArrayList(…)); 来防止这一问题发生。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: