java集合分析(7):ArrayList
来源:互联网 发布:电脑装linux系统 编辑:程序博客网 时间:2024/06/11 11:35
在前面中我们分析了几篇有关集合的类,这一篇我们来看看在我们开发当中经常用到集合类就是ArrayList.
什么是ArrayList
ArrayList 是一个数组队列,相当于动态数组。与Java中的数组相比,它的容量能动态增长。它继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。
ArrayList继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能.
ArrayList实现了Cloneable接口,即覆盖了函数clone(),能被克隆.
ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。
和Vector不同,ArrayList中的操作不是线程安全的。所以,建议在单线程中才使用ArrayList,而在多线程中可以选择Vector或者CopyOnWriteArrayList。
RandomAccess
ArrayList实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问.
public interface RandomAccess {}
RandomAccess 是一个空的接口,它用来标识某个类是否支持 随机访问(随机访问,相对比“按顺序访问”)。一个支持随机访问的类明显可以使用更加高效的算法。
本篇中ArrayList支持随机访问, 它的数据结构使得 get(), set(), add()等方法的时间复杂度都是更有效率;
而我们知道 LinkedList结构是链表,它不支持随机访问,只能按序访问,因此在一些操作添加删除上性能略逊一筹。
通常在操作一个 List 对象时,通常会判断是否支持随机访问,也就是* 是否为 RandomAccess 的实例*,从而使用不同的算法。
比如遍历,实现了 RandomAccess 的集合使用 get():
for (int i=0, n=list.size(); i < n; i++) list.get(i);
比用迭代器更快:
for (Iterator i=list.iterator(); i.hasNext(); ) i.next();
实现了 RandomAccess 接口的类有:
ArrayList, AttributeList, CopyOnWriteArrayList, Vector, Stack 等
ArrayList 的成员变量
1.底层数据结构,数组:
transient Object[] elementData;
由于数组类型为 Object,所以允许添加 null 。
transient 关键字
- Java的Serializable提供了一种序列化对象实例的机制。当序列化对象时,可能有一个特殊的对象数据成员,我们不想用Serializable机制来保存它。为了在一个特定对象的一个域上关闭Serializable,可以在这个域前加上关键字transient。
- transient是Java语言的关键字,用来表示一个域不是该对象序列化的一部分。当一个对象被序列化的时候,transient型变量的值不包括在序列化的表示中,然而非transient型的变量是被包括进去的。
2.默认容量
private static final int DEFAULT_CAPACITY = 10;
3.空的对象数组
private static final Object[] EMPTY_ELEMENTDATA = {};
4.元素数量
private int size;
构造方法
1.带有容量initialCapacity的构造方法
public ArrayList(int initialCapacity) { super(); // 如果初始化时ArrayList小于0 if (initialCapacity < 0) // 则抛出IllegalArgumentException异常 throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); //指定容量,创建elementData对象数组 this.elementData = new Object[initialCapacity];}
2.不带参数的构造方法
public ArrayList() { super(); //将空数组赋值给elementData this.elementData = EMPTY_ELEMENTDATA; }
3.带参数Collection的构造方法
//直接创建和指定集合一样内容的ArrayList 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) //使用 Arrays.copy 方法copy创建一个 Object 数组 elementData = Arrays.copyOf(elementData, size, Object[].class); }
ArrayList 的关键方法
1.添加元素
//添加元素 public boolean add(E e) { //对数组的容量进行扩容 ensureCapacityInternal(size + 1); // Increments modCount!! // 将e赋值给elementData的size+1的位置。 elementData[size++] = e; return true; } //在ArrayList的指定位置添加元素element public void add(int index, E element) { // 判断index是否越界 if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); //对数组的容量进行扩容 ensureCapacityInternal(size + 1); // Increments modCount!! // 将elementData从index开始的size-index个元素复制到index+1至size+1的位置(即index开始的元素都向后移动一个位置) System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; } //添加一个集合 public boolean addAll(Collection<? extends E> c) { //将c转换为数组a Object[] a = c.toArray(); int numNew = a.length; //对数组的容量进行扩容 ensureCapacityInternal(size + numNew); // Increments modCount //将a的第0位开始拷贝至elementData的size位开始,拷贝长度为numNew System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; } //在ArrayList的指定位置添加元素集合c public boolean addAll(int index, Collection<? extends E> c) { // 判断index是否越界 if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); //将c转换为数组a Object[] a = c.toArray(); int numNew = a.length; //对数组的容量进行扩容 ensureCapacityInternal(size + numNew); // Increments modCount int numMoved = size - index; if (numMoved > 0) System.arraycopy(elementData, index, elementData, index + numNew, numMoved); System.arraycopy(a, 0, elementData, index, numNew); size += numNew; return numNew != 0; }
System.arraycopy()的作用是实现数组之间的复制;
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
其中:src表示源数组,srcPos表示源数组要复制的起始位置,desc表示目标数组,length表示要复制的长度。
2.删除元素
//删除ArrayList指定位置的元素 public E remove(int index) { // 判断index是否越界 if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); modCount++; // 读取旧值 E oldValue = (E) elementData[index]; // 获取index位置开始到最后一个位置的个数 int numMoved = size - index - 1; if (numMoved > 0) // 将elementData的第index+1位置开始拷贝至elementData的index位开始,拷贝长度为numMoved System.arraycopy(elementData, index+1, elementData, index, numMoved); //原数组中最后一个元素删掉 elementData[--size] = null; // clear to let GC do its work return oldValue; } //在ArrayList中移除对象为O的元素 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; } //内部方法,“快速删除”,就是把重复的代码移到一个方法里 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 } //删除所有元素 public boolean removeAll(Collection<?> c) { return batchRemove(c, false); } //移除c中的所有元素 public boolean removeAll(Collection<?> c) { return batchRemove(c, false); } //保留c中所有的元素 public boolean retainAll(Collection<?> c) { return batchRemove(c, true); } //删除或者保留指定集合中的元素 private boolean batchRemove(Collection<?> c, boolean complement) { final Object[] elementData = this.elementData; int r = 0, w = 0; boolean modified = false; try { for (; r < size; r++) if (c.contains(elementData[r]) == complement) elementData[w++] = elementData[r]; } finally { // Preserve behavioral compatibility with AbstractCollection, // even if c.contains() throws. if (r != size) { //发生了异常,直接把 r 后面的复制到 w 后面 System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; } if (w != size) { // clear to let GC do its work // 清除多余的元素 for (int i = w; i < size; i++) elementData[i] = null; modCount += size - w; size = w; modified = true; } } return modified; } //清除所有元素 public void clear() { modCount++; // clear to let GC do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }
3.查询,修改,是否包含元素
//获取指定位置的元素 public E get(int index) { //判断index是否越界 if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); // 返回ArrayList的elementData数组index位置的元素 return (E) elementData[index]; } //修改指定位置的元素 public E set(int index, E element) { //判断index是否越界 if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); //旧值 E oldValue = (E) elementData[index]; //将element赋值给elementData[index] elementData[index] = element; return oldValue; } //是否包含对象o public boolean contains(Object o) { return indexOf(o) >= 0; } //对象o在ArrayList中的下标位置,如果存在返回位置i,不存在返回-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; }
4.数组扩容
public void ensureCapacity(int minCapacity) { //不是默认的数组,说明已经添加了元素 int minExpand = (elementData != EMPTY_ELEMENTDATA) // any size if real element table ? 0 // larger than default for empty table. It's already supposed to be // at default size. : DEFAULT_CAPACITY; //当前元素个数比默认容量大 if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); } } //得到最小扩容量 private void ensureCapacityInternal(int minCapacity) { if (elementData == EMPTY_ELEMENTDATA) { //最小容量取默认容量和当前元素个数的最大值 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } //判断是否需要扩容 private void ensureExplicitCapacity(int minCapacity) { // 将“修改统计数”+1,该变量主要是用来实现fail-fast机制的 modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) //扩容 grow(minCapacity); } /** * 数组缓冲区最大存储容量 * - 一些 VM 会在一个数组中存储某些数据--->为什么要减去 8 的原因 * - 尝试分配这个最大存储容量,可能会导致 OutOfMemoryError(当该值 > VM 的限制时) */ // MAX_VALUE为231-1,MAX_ARRAY_SIZE 就是获取Java中int的最大限制,以防止越界 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; private void grow(int minCapacity) { // overflow-conscious code // 获取到ArrayList中elementData数组的长度 int oldCapacity = elementData.length; //扩容至原来的1.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1); //如果当前容量还没达到 1.5 倍旧容量,就使用当前容量 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: // 调用Arrays.copyOf方法复制一个长度为newCapacity的elementData数组并返回 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; }
5.数组转换:
public Object[] toArray() { return Arrays.copyOf(elementData, size); } //将ArrayList里面的元素赋值到一个数组中去 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; }
6.subList(int fromIndex, int toIndex)
public List<E> subList(int fromIndex, int toIndex) { subListRangeCheck(fromIndex, toIndex, size); return new SubList(this, offset, fromIndex, toIndex); }
7.trimToSize()
public void trimToSize() { modCount++; if (size < elementData.length) { elementData = Arrays.copyOf(elementData, size); } }
由于elementData的长度会被拓展,size标记的是其中包含的元素的个数。所以会出现size很小但elementData.length很大的情况,将出现空间的浪费。trimToSize将返回一个新的数组给elementData,元素内容保持不变,length很size相同,节省空间。
ArrayList的三种遍历
随机访问,通过索引值去遍历
由于ArrayList实现了RandomAccess接口,它支持通过索引值去随机访问元素。
ArrayList<Integer> array = new ArrayList<Integer>(); array.add(1); array.add(2); array.add(1, 3); for(int i = 0; i < array.size(); i++){ System.out.println(array.get(i)); if (i == 1) { array.add(1); } }
打印结果: 1 3 2 1
通过迭代器遍历
Iterator<Integer> it = array.iterator(); while(it.hasNext()){ //array.add(4); 会导致modCount发生变化,从而导致next方法的checkModCount抛出异常 int value = it.next(); //array.remove(new Integer(value));同样会导致modCount发生变化 it.remove();//但是可以这样,因为Iterator的remove方法删除元素之后对modCount又进行了复原 System.out.println(value); } System.out.println(array.size());
打印结果:1 3 2 0
使用forEach循环(其实本质就是利用的Iterator循环)先是it.hasNext(),然后在将value = it.next(),所以执行这段代码时,会先打印第一个值,在进行第二轮循环的value = it.next()时抛出异常.
for(Integer value : array){ array.add(4);//发生ConcurrentModificationException错误 //array.remove(value);同上 System.out.println(value); }
下面我们来比较这3种遍历方式的效率
public class Test { /** * @param args */ public static void main(String[] args) { // 通过Iterator遍历ArrayList iterator(getArrayListData()); // 通过快速随机访问遍历ArrayList for1(getArrayListData()); // 通过forEach循环遍历ArrayList for2(getArrayListData()); } private static ArrayList getArrayListData() { ArrayList<Integer> llist = new ArrayList(); for (int i = 0; i < 1000000; i++) llist.add(i); return llist; } /** * 通过迭代器遍历ArrayList */ private static void iterator(ArrayList<Integer> list) { // 记录开始时间 long start = System.currentTimeMillis(); Iterator iter = list.iterator(); while (iter.hasNext()) { iter.next(); } // 记录结束时间 long end = System.currentTimeMillis(); long interval = end - start; System.out.println("迭代器遍历:" + interval + " ms"); } /** * 通过快速随机访问遍历LinkedList */ private static void for1(ArrayList<Integer> list) { // 记录开始时间 long start = System.currentTimeMillis(); int size = list.size(); for (int i = 0; i < size; i++) { list.get(i); } // 记录结束时间 long end = System.currentTimeMillis(); long interval = end - start; System.out.println("快速随机访问:" + interval + " ms"); } /** * 通过forEach循环来遍历LinkedList */ private static void for2(ArrayList<Integer> list) { // 记录开始时间 long start = System.currentTimeMillis(); for (Integer integ : list){ } // 记录结束时间 long end = System.currentTimeMillis(); long interval = end - start; System.out.println("forEach循环:" + interval + " ms"); }}
运行结果:
通过上面运行结果可以看到,遍历ArrayList获取元素值时,使用随机访问(即,通过索引序号访问)效率最高,而使用迭代器遍历效率最低。
总结:
ArrayList在每次增加元素(可能是1个,也可能是一组)时,都要调用ensureCapacity方法来确保足够的容量。当容量不足以容纳当前的元素个数时,就设置新的容量为旧的容量的1.5倍,如果设置后的新容量还不够,则直接新容量设置为传入的参数(也就是所需的容量),而后用Arrays.copyof()方法将元素拷贝到新的数组,当容量不够时,每次增加元素,都要将原来的元素拷贝到一个新的数组中,非常之耗时,也因此建议在事先能确定元素数量的情况下,才使用ArrayList,否则建议使用LinkedList。
ArrayList基于数组实现,可以通过下标索引直接查找到指定位置的元素,因此查找效率高,但每次插入或删除元素,就要大量地移动元素,插入删除元素的效率低。
ArrayList 不是线程同步的,所以在并发访问时,如果在迭代的同时有其他线程修改了 ArrayList, 会发生 ConcurrentModificationException 异常错误,也就是fail-fast。
参考:
http://blog.csdn.net/ljcITworld/article/details/52041836
http://blog.csdn.net/ljcITworld/article/details/52041836
- java集合分析(7):ArrayList
- java ArrayList集合分析
- Java集合-ArrayList源码分析
- 【java集合】ArrayList源码分析
- 【Java集合框架源码分析(JDK1.7)】-ArrayList源码分析
- ArrayList、LinkedList、Vector分析--Java集合分析
- java 集合ArrayList及LinkList源码分析
- Java集合框架源码分析之ArrayList
- java 集合ArrayList及LinkList源码分析
- java集合深入分析一(ArrayList)
- 从源码分析java集合【ArrayList】
- Java集合系列之ArrayList源码分析
- java 集合ArrayList及LinkList源码分析
- java集合03--ArrayList源码分析
- java集合03--ArrayList源码分析
- Java集合源码分析之ArrayList
- java 集合ArrayList及LinkList源码分析
- java集合类源码分析 ArrayList
- 自定义vue全局组件use的使用
- Window 安装两个版本JDK,配置其中一个
- 控制服务器处理请求的数量(高并发)-防止用户重复点击导致多次请求
- android 使用Robotium自动化测试
- MySQL查询:查询一个表中类别字段中Max()最大值对应的记录
- java集合分析(7):ArrayList
- 随记
- 安卓开发中Banner添加头布局结合PullToRefresh实现上拉下拉
- wk2168驱动调试(二)
- android 应用覆盖安装显示程序未安装
- 如何按行政区划下载谷歌卫星地图并裁剪
- 条款10:令operator=返回一个reference to *this
- 教程篇(5.4) NSE4 10. Web 过滤 ❀ 飞塔 (Fortinet) 网络安全专家
- nodejs 生成uid(唯一标识符) —— node uuid模块