我们经常使用ArrayList,Vector,Hashmap等集合,一般都是直接用new跟上类名声明出一个集合来,然后使用add,remove,等方法进行操作,而且因为它们是自动管理长度的,所以不用我们特别费心超长问题,这确实是一个非常好的优点,但也有我们需要注意的事项.
下面以ArrayList为例深入了解一下Java是如何实现长度的动态管理的,先从add方法的阅读开始.
1 public boolean add(E e) { 2 ensureCapacity(size + 1); 3 elementData[size++] = e; 4 return true; 5 }
我们知道ArrayList是一个大小可变的数组,但它在底层使用的是数组存储(也就是elementData变量),而且数组是定长的,要实现动态长度必然要进行长度的扩展,ensureCapacity方法提供了此功能,代码如下:
1 public void ensureCapacity(int minCapacity) { 2 modCount++; //修改计数器 3 int oldCapacity = elementData.length; 4 //当前需要的长度超过了数组长度,进行扩容处理 5 if (minCapacity > oldCapacity) { 6 Object oldData[] = elementData; 7 //新的容量 = 旧容量 * 1.5 + 1 8 int newCapacity = (oldCapacity * 3)/2 + 1; 9 if (newCapacity < minCapacity) 10 newCapacity = minCapacity; 11 //数组拷贝,生成新的数组 12 elementData = Arrays.copyOf(elementData, newCapacity); 13 } 14 }
注意看新数组的长度计算方法,并不是增加一个元素,elementData的长度就加1,而是在达到了elementData长度的临界点时,才将elementData扩容1.5把倍,这样实现有什么好处呢?好处就是避免了多次调用copyOf()方法的性能开销,否则每增加一个元素都要扩容一次,那性能岂不是非常的糟糕.
为什么是1.5倍呢,因为一次扩容太大,占用的内存也就越大,浪费的内存也就越多(1.5倍扩容,最多浪费33%的数组空间,而2.5倍扩容则最多浪费60%的内存),而扩容太小(比如每次扩容1.1倍),则需要多次对数组重新分配内存,性能消耗严重,经过测试验证,扩容1.5倍即满足了性能要求,也减少了内存消耗.
elementData的默认长度是10,如果我们使用默认的方式生命ArrayList,如new ArrayList(),则elementData的初始长度就是10.我们来看ArrayList的无参构造:
1 // ArrayList无参构造函数。默认容量是10。 2 public ArrayList() { 3 this(10); 4 } 5 // ArrayList带容量大小的构造函数。 6 public ArrayList(int initialCapacity) { 7 super(); 8 if (initialCapacity < 0) 9 throw new IllegalArgumentException("Illegal Capacity: "+ 10 initialCapacity); 11 // 新建一个数组 12 this.elementData = new Object[initialCapacity]; 13 }
默认初始化时声明了一个长度为10的数组 在通过add方法增加第11个元素时,ArrayList类就自动扩展了,新的elementData数组长度就是(10*3)/2 +1 也就是16,当增加到第17个元素时再次扩容为(16*3)/2+1 也就是25,以此类推,实现了ArrayList的动态数组管理.
本文转自SummerChill博客园博客,原文链接:http://www.cnblogs.com/DreamDrive/p/5422175.html,如需转载请自行联系原作者