第三章 JAVA集合之ArrayList源码浅析
2016-09-09 21:48
471 查看
一.概括
ArrayList集合是我们工作中最常用的集合之一。ArrayList等同于一个动态的数组,动态的数组顾名思义就是可以自动扩容的数组,而不需要我们手动的去调整数组的大小。ArrayList是对数组进行了封装,而且还对增加了一些对这个数组进行操作的方法。二.ArrayList源码解析
1.ArrayList实现接口和属性
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8683452581122892189L; /** * ArrayList用来存取数值的数组, * 对ArrayList的操作其实就是对这个数据的操作 */ private transient Object[] elementData; /** * 代表ArrayList存储元素的个数,注意size不等于elementData的长度 * * @serial */ private int size;ArrayList实现了RandomAccess接口,RandomAccess接口里面是没有任何方法的,实现RandomAccess支持快速随机访问,这样ArrayList使用for循环遍历元素要比使用迭代器遍历元素要快。ArrayList实现了Cloneable接口,ArrayList支持浅拷贝。ArrayList实现了Serializable接口,Serializable支持序列化。
2.ArrayList构造方法
/** *带有初始值的构造方法, *initialCapacity这个值是设置数组的大小 * */ public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; } /** * 不带参数的构造方法, * ArrayList在初始化的时候会给数组的长度设为10 */ public ArrayList() { this(10); } /** * 带有集合参数的构造方法 * ArrayList会将集合类型的参数转变成数组 * 再将数组中的元素复制到新的数组中, * 再让elementData指向新的数据 */ public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); size = elementData.length; // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); }
3.ArrayList的其他常用方法
3.1ArrayList扩容方法:ensureCapacity/** * * * 预先设置ArrayList的容量大小,如果传入的参数minCapacity小于 * ArrayList中elementData数组的长度就不做改变 * 如果minCapacity参数大于elementData数组的长度,就会对 * 将elementData的大小设置为原来的1.5倍,从新设置的数组长度如果还小于参数minCapacity * 那么数组长度就直接变为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 static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; private void grow(int minCapacity) { int oldCapacity = elementData.length; //新的数组长度是原数组长度的1.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }<em> </em>
在要对ArrayList进行插入操作的时候,一般先要调用ensureCapacity方法,确定ArrayList是否能需要扩容。
3.2ArrayList的添加和删除方法
/** * 向ArrayList添加元素,在添加元素之前会先判断ArrayList是否需要扩容 * 元素e将被添加在ArrayList的最末尾的位置 */ public boolean add(E e) { //将会用size + 1和elementData.length比较大小,如果size + 1大于elementData.length //将会对数组elementData进行扩容 ensureCapacityInternal(size + 1); //这句话可以分解为elementData[size] = e;size++这两步 elementData[size++] = e; return true; } /** *在指定位置添加元素 */ public void add(int index, E element) { //判断index是否小于0或大于size,如果满足将抛出异常 rangeCheckForAdd(index); //判断ArrayList是否需要扩容 ensureCapacityInternal(size + 1); /* *将数组elementData从下标index开始的元素,长度为size - index(即index到size之间的元素)复制到 *数组elementData以index+1开始的位置,再将element放在数组index的位置 */ System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; } /** *按下标移除元素 */ public E remove(int index) { //先判断index是否大于等于size,如果大于等于将报异常 rangeCheck(index); modCount++; //防止数组index下标位置所指向的内存在移动元素的时候被占用 E oldValue = elementData(index); //需要在elementData数组中移动元素的长度 int numMoved = size - index - 1; if (numMoved > 0) //将elementData从下标index+1开始的元素到,长度为numMoved,移除到elementData的下标为index开始的地方 //这样就能将elementData中下标为index的元素覆盖掉 System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // Let gc do its work return oldValue; } /** * 按元素内容移除 * 先找到元素在数组中的下标,再按下标移除 * */ public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { /* *按下标移除元素,为什么不直接调用remove,而要调用fastRemove方法 *fastRemove方法和remove方法比起来少了边界的判断和旧元素的赋值,更快速 */ fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } /* * 快速移除元素,是一个私有方法,和remove方法比起来少了对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; // Let gc do its work } /** * 清空ArrayList,将elementData中的所有元素设置为null * */ public void clear() { modCount++; // Let gc do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; } /** * 向ArrayList增加一个集合 * 先将集合转变为数组,在将数组中的元素复制到elementData中 */ public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); //将a中所有的元素复制到elementData中size之后的位置 System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; }ArrayList中元素的添加和删除操作都得依赖 System的arraycopy方法, System.arraycopy(a, 0, elementData, size, numNew);的意思是将数组a中从下标0开始,到0+numNew=numNew之间的元素移动到elementData数组中,但存放的位置是在size之后的,这样说应该能懂这个方法是干什么的了,这个操作是比较花时间的,所以对于添加和删除操作比较的频繁的时候,LinkedList是要好于ArrayList的,所以平时不要一味的依赖ArrayList。
3.3ArrayList一些简易方法
/** * *获取ArrayList的大小,即获取size属性就可以了 */ public int size() { return size; } /** * 判断ArrayList是否为空 */ public boolean isEmpty() { return size == 0; } /** *判断ArrayList是否包含某一个元素 */ public boolean contains(Object o) { return indexOf(o) >= 0; } /** * 返回元素在ArrayList中的首位置, * 如果ArrayList中没有这个元素,就返回-1 */ public int indexOf(Object o) { if (o == null) { for (int i = 0; i < size; i++) if (elementData[i]==null) return i; } else { for (int i = 0; i < size; i++) if (o.equals(elementData[i])) return i; } return -1; } /** * 返回元素在ArrayList中的最后出现的位置, * 如果ArrayList中没有这个元素,就返回-1 */ 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; } /** * 对ArrayList进行浅拷贝 */ public Object clone() { try { @SuppressWarnings("unchecked") ArrayList<E> v = (ArrayList<E>) super.clone(); v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { throw new InternalError(); } } /** * 将ArrayList转变为数组 * */ public Object[] toArray() { return Arrays.copyOf(elementData, size); } /** * 将ArrayList转变为参数数组a并返回 */ @SuppressWarnings("unchecked") public <T> T[] toArray(T[] a) { if (a.length < size) // Make a new array of a's runtime type, but my contents: return (T[]) Arrays.copyOf(elementData, size, a.getClass()); System.arraycopy(elementData, 0, a, 0, size); if (a.length > size) a[size] = null; return a; } // Positional Access Operations @SuppressWarnings("unchecked") E elementData(int index) { return (E) elementData[index]; } /** *按下标返回ArrayList中的元素 */ public E get(int index) { //检查index是否大于等于size,如果大于等于将报数组越界异常 rangeCheck(index); return elementData(index); } /** *按下标设置元素 */ public E set(int index, E element) { //检查index是否大于等于size,如果大于等于将报数组越界异常 rangeCheck(index); E oldValue = elementData(index); //下标index将指向新的元素 elementData[index] = element; return oldValue; }
还有一些其他的方法就不在这里一一列出来了,大家感兴趣的可以自己去看看
三.总结
1.如果在声明ArrayList的时候不设初始值,那么ArrayList会默认设置一个容量为10的数组,但是ArrayList的大小还是为0的2.ArrayList可以看成是一个动态的数组,相比较与数组来说,ArrayList可以用ensureCapacityInternal方法自动扩容,在向ArrayList添加元素的时候,ArrayList会先检测现在数组的容量是否足够,若不够,ArrayList会将数组的容量扩大为原来的2.5倍,如果还不够,就用传进来的参数作为数组的容量。如果我们在知道存储元素多少的时候,尽量给ArrayList设定一个初始容量,这样就可以减少ArrayList的自动扩容,减少数组元素的移动来提高程序的性能。
3.ArrayList在增加或删除元素的时候,都需要将数组里面的元素移动到另一个数组中,这是非常耗时间的,所以遇到需要对元素进行频繁的删除和添加的集合时,这时候选用LinkedList要比ArrayList好很多,如果遇到在集合中查找元素比较多的操作时,ArrayList又是一个不错的选择,因为ArrayList直接可以用下标就可以获取到元素。
4.在研读ArrayList源码的时候要注意ensureCapacityInternal扩容方法和System.arraycopy(original, 0, copy, 0,length)方法。
参考文章:http://www.cnblogs.com/ITtangtang/p/3948555.html
相关文章推荐
- Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例
- 【Java集合源码剖析】ArrayList源码剖析
- 从源码分析java集合【ArrayList】
- 【Java集合源码剖析】ArrayList源码剖析
- Java的ArrayList源码浅析
- java_集合体系之ArrayList详解、源码及示例——03
- java 集合ArrayList及LinkList源码分析
- 【转】Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例
- java核心之集合框架——ArrayList源码分析
- Java集合系列之ArrayList源码分析
- 【Java集合源码剖析】ArrayList源码剖析
- Java集合源码学习(8)_List接口的实现_CopyOnWriteArrayList
- 【Java集合源码剖析】ArrayList源码剖析
- Java集合之ArrayList源码分析
- java 集合ArrayList及LinkList源码分析
- 【Java集合源码剖析】ArrayList源码剖析
- java集合(ArrayList、vector、HashMap、HashTable)源码剖析
- Java集合源码学习(6)_List接口的实现_ArrayList_Vector
- 【Java集合源码剖析】ArrayList源码剖析
- 【Java集合源码剖析】ArrayList源码剖析