Java集合之ArrayList源码解析
Posted 此账户已注销或冻结
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java集合之ArrayList源码解析相关的知识,希望对你有一定的参考价值。
下面我们来看看ArrayList的底层实现,
ArrayList继承了AbstractList,实现Cloneable、Serializable、RandomAccess接口,
它的成员属性有Object[] elementData 和 int size,
显然底层是以可扩展的数组来存储元素,
新增元素
有如下这段代码,
public static void main(String[] args) { List<Integer> list = new ArrayList<Integer>(); list.add(1); }
我们进到add(E e)方法,下如图,
1 public boolean add(E e) { 2 ensureCapacityInternal(size + 1); // Increments modCount!! 3 elementData[size++] = e; 4 return true; 5 }
size因为是成员属性,并且是基本数据类型,所以它的初始值为0,
第3行elementData[size++] = e;等价于第一步先elementData[0] = e ,第二步size自增,
第2行的 ensureCapacityInternal(size + 1),上面我们也提到了,Object[] elementData是一个可动态扩展的一个数组,
因此我们需要校验当前的的容量是否满足元素的存储,如果不满足,又是采取怎样的方式进行扩容呢?
我们来看 ensureCapacityInternal(int minCapacity),
1 private void ensureCapacityInternal(int minCapacity) { 2 if (elementData == EMPTY_ELEMENTDATA) { 3 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); 4 } 5 6 ensureExplicitCapacity(minCapacity); 7 }
DEFAULT_CAPACITY = 10 是默认的数组容量,
下面来看ensureExplicitCapacity(int minCapacity),
1 private void ensureExplicitCapacity(int minCapacity) { 2 modCount++; 3 4 // overflow-conscious code 5 if (minCapacity - elementData.length > 0) 6 grow(minCapacity); 7 }
如果需要的长度大于数组当前的长度,则调用grow(int minCapacity),
1 private void grow(int minCapacity) { 2 // overflow-conscious code 3 int oldCapacity = elementData.length; 4 int newCapacity = oldCapacity + (oldCapacity >> 1); 5 if (newCapacity - minCapacity < 0) 6 newCapacity = minCapacity; 7 if (newCapacity - MAX_ARRAY_SIZE > 0) 8 newCapacity = hugeCapacity(minCapacity); 9 // minCapacity is usually close to size, so this is a win: 10 elementData = Arrays.copyOf(elementData, newCapacity); 11 }
扩容的规则是,当前数组的长度乘以1.5,结果带小数则取整数,
最后调用Arrays.copyOf(T[] original, int newLength),直接看到最内层的实现,
1 public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) { 2 T[] copy = ((Object)newType == (Object)Object[].class) 3 ? (T[]) new Object[newLength] 4 : (T[]) Array.newInstance(newType.getComponentType(), newLength); 5 System.arraycopy(original, 0, copy, 0, 6 Math.min(original.length, newLength)); 7 return copy; 8 }
会创建一个新数组,同时将原数组的内容复制到新数组中,
扩容是的操作是int newCapacity = oldCapacity + (oldCapacity >> 1),我们可以这么认为
1.扩容一次性太多,势必会造成对内存空间的过多占用,
2.扩容太少,会造成次数太多,下次的扩容很快到来,同时,将原数组的元素复制到新的数组中,频繁的数组拷贝需要消耗一定的性能,
因此也许这是一种比较折中的处理方式,
删除元素
如下一段代码
1 public static void main(String[] args) { 2 List<Integer> list = new ArrayList<Integer>(); 3 list.add(111); 4 list.add(222); 5 list.remove(1); 6 list.remove(222); 7 }
元素的删除分为两种,一种是按照下标来删除,一种是按照元素来删除
按照下标来删除元素,先看一下代码,
1 public E remove(int index) { 2 rangeCheck(index); 3 4 modCount++; 5 E oldValue = elementData(index); 6 7 int numMoved = size - index - 1; 8 if (numMoved > 0) 9 System.arraycopy(elementData, index+1, elementData, index, 10 numMoved); 11 elementData[--size] = null; // clear to let GC do its work 12 13 return oldValue; 14 }
第二行,校验是否越界,第九行将删除的数据之后的元素往前移动一位,第十一行将size自减,同时将最后一位引用指向null
clear to let GC do its work!
按照元素来删除元素,先看一下代码,
public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; }
遍历找出下标之后调用 fastRemove(int index),代码如下,
private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work }
两种实现方式差不多,最后都是调用上面的数组部分元素的移动,以及最后一位引用让虚拟机自己释放元素的引用
插入元素
如下一段代码,
1 public static void main(String[] args) { 2 List<Integer> list = new ArrayList<Integer>(); 3 list.add(111); 4 list.add(222); 5 list.add(1,22222); 6 }
第5行表示,在第一个元素之后插入22222,
我们进到add(int index, E element),
1 public void add(int index, E element) { 2 rangeCheckForAdd(index); 3 4 ensureCapacityInternal(size + 1); // Increments modCount!! 5 System.arraycopy(elementData, index, elementData, index + 1, 6 size - index); 7 elementData[index] = element; 8 size++; 9 }
第2行,校验插入的位置是否在数组的大小范围内,否则跑出异常,
第4行,判断是否有必要扩容,和新增元素的扩容是一样方法,
第5行,插入的位置到最后的所有元素向后移动一位,
第6行,插入的位置的引用指向新元素,
下面我们ArrayList实现了Random接口,然鹅RandomAccess是一个空接口,javadoc中是这么描述的,
Marker interface used by <tt>List</tt> implementations to indicate that they support fast (generally constant time) random access.
翻译过来的意思是 这是一个标记接口,仅仅是一个标记,arrayList实现标记,表明它能快速的查询数据,
从上面几个方面,我们总结一下ArrayList的优缺点,
优点
1.新增一个元素,顺序新增,增加数组元素的一个引用而已,
2.数组查询非常快捷,
缺点
1.删除元素,会造成部分元素的移动,势必会造成性能的一定影响,
2.插入元素,会造成部分元素的移动,势必会造成性能的一定影响,
所以,在业务开发中,涉及到查询较多的,考虑ArrayList。
同样,想借鉴大神对于集合的四个关注点
1.是否允许为空,
2.元素是否允许重复,
3.元素的存储顺序与查找顺序是否一致,
4.是否线程安全,
ArrayList允许元素为空,允许重复,有序,非线程安全,
我们再回头看数组的定义
private transient Object[] elementData;
被transient关键字修饰,表示该数组不会被序列化,而是提供了writeObject(java.io.ObjectOutputStream s),javadoc有这么一句话,
Save the state of the <tt>ArrayList</tt> instance to a stream (that is, serialize it)
我们来看一下writeObject(java.io.ObjectOutputStream s),
1 private void writeObject(java.io.ObjectOutputStream s) 2 throws java.io.IOException{ 3 // Write out element count, and any hidden stuff 4 int expectedModCount = modCount; 5 s.defaultWriteObject(); 6 7 // Write out size as capacity for behavioural compatibility with clone() 8 s.writeInt(size); 9 10 // Write out all elements in the proper order. 11 for (int i=0; i<size; i++) { 12 s.writeObject(elementData[i]); 13 } 14 15 if (modCount != expectedModCount) { 16 throw new ConcurrentModificationException(); 17 } 18 }
第5行,对非transient的成员属性序列化,
第11~13行,对有数组中有值得元素逐个序列化。
这么做的好处,
1.大大缩短序列化的时间,
2.减少序列化后文件的大小。
以上是关于Java集合之ArrayList源码解析的主要内容,如果未能解决你的问题,请参考以下文章
Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例
Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例
Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例