ArrayList(一)源码分析
来源:互联网 发布:少儿编程哪家好 编辑:程序博客网 时间:2024/05/24 22:44
一、ArrayList概述
ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
ArrayList 实现了RandomAccess接口,即提供了随机访问功能。RandomAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。稍后,我们会比较List的“快速随机访问”和“通过Iterator迭代器访问”的效率。
ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。
ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输数据。
和Vector不同,ArrayList中的操作不是线程安全的!所以,建议在单线程中才使用ArrayList,而在多线程中可以选择Vector或者CopyOnWriteArrayList。
二、ArrayList实现
(1)属性
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{ private static final long serialVersionUID = 8683452581122892189L; /** * Default initial capacity. */ private static final int DEFAULT_CAPACITY = 10; /** * Shared empty array instance used for empty instances. */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * Shared empty array instance used for default sized empty instances. We * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when * first element is added. */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. Any * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA * will be expanded to DEFAULT_CAPACITY when the first element is added. */ transient Object[] elementData; // non-private to simplify nested class access /** * The size of the ArrayList (the number of elements it contains). * * @serial */ private int size;
(2)构造方法
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } }
(3)元素存储
a. set(int index, E element) 用指定的元素替换该列表中指定位置的元素。
public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; }
b. add(E e) 将指定元素追加到该列表的末尾。
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
c. add(int index, E element) 将指定元素插入该列表中的指定位置。将当前位置上的元素(如果有)和其他元素移到右边(将一个元素添加到它们的索引中)。
public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
d. addAll(Collection<? extends E> c) 将指定集合中的所有元素附加到该列表的末尾,以便由指定集合的迭代器返回它们。如果在运行过程中修改指定的集合,则此操作的行为是未定义的。(这意味着如果指定的集合是这个列表,而且这个列表是非空的,这个调用的行为是未定义的。)
public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; }
e. addAll(int index, Collection<? extends E> c) 将指定集合中的所有元素插入该列表,从指定位置开始。将当前位置上的元素(如果有)和其他元素移到右边(增加它们的索引)。新元素将出现在列表中,它们由指定集合的迭代器返回。
public boolean addAll(int index, Collection<? extends E> c) { rangeCheckForAdd(index); 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; }
(4)元素读取
get(int index)
public E get(int index) { rangeCheck(index); return elementData(index); }
(5)元素删除
ArrayList提供了两种删除方法,一种通过下标删除,一种通过指定对象删除。
a. remove(int index) : 将元素移到该列表中的指定位置。将后面的元素移到左边(从它们的索引中减去一个),并返回被移除的元素。
public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); 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 return oldValue; }
b. remove(Object o) : 从这个列表中删除指定元素的第一个出现,如果它存在的话。如果列表中不包含元素,那么它是不变的
public boolean remove(Object o) { if (o == null) { // 由于ArrayList中允许存放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; }
首先通过代码可以看到,当移除成功后返回true,否则返回false。remove(Object o)中通过遍历element寻找是否存在传入对象,一旦找到就调用fastRemove移除对象。为什么找到了元素就知道了index,不通过remove(index)来移除元素呢?因为fastRemove跳过了判断边界的处理,因为找到元素就相当于确定了index不会超过边界,而且fastRemove并不返回被移除的元素。下面是fastRemove的代码,基本和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; // clear to let GC do its work }
c. removeRange(int fromIndex, int toIndex) : 从这个列表中删除索引之间的所有元素
protected void removeRange(int fromIndex, int toIndex) { modCount++; int numMoved = size - toIndex; System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved); // clear to let GC do its work int newSize = size - (toIndex-fromIndex); for (int i = newSize; i < size; i++) { elementData[i] = null; } size = newSize; }
(6)调整数组容量
从上面介绍的向ArrayList中存储元素的代码中,我们看到,每当向数组中添加元素时,都要去检查添加后元素的个数是否会超出当前数组的长度,如果超出,数组将会进行扩容,以满足添加数据的需求。数组扩容通过一个公开的方法ensureCapacity(int minCapacity)来实现。在实际添加大量元素前,我也可以使用ensureCapacity来手动增加ArrayList实例的容量,以减少递增式再分配的数量。
public void ensureCapacity(int minCapacity) { int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) // any size if not default element table ? 0 // larger than default for default 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 == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(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) { // 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); }
MAX_ARRAY_SIZE
要分配的数组的最大大小。一些vm在数组中保留一些标题字。尝试分配更大的数组可能会导致OutOfMemoryError:请求的数组大小超过VM限制
要注意的是在jdk1.6中 每当数组满了,扩容是通过算术运算符运算,从jdk1.7开始,扩容是通过位运算来操作(默认扩展1.5倍),提高了效率。
从上述代码中可以看出,数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的1.5倍。这种操作的代价是很高的,因此在实际使用时,我们应该尽量避免数组容量的扩张。当我们可预知要保存的元素的多少时,要在构造ArrayList实例时,就指定其容量,以避免数组扩容的发生。或者根据实际需求,通过调用ensureCapacity方法来手动增加ArrayList实例的容量。
trimToSize() 将这个 ArrayList 实例的容量作为列表的当前大小。应用程序可以使用此操作来最小化 ArrayList 实例的存储。
public void trimToSize() { modCount++; if (size < elementData.length) { elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size); } }
由于elementData的长度会被拓展,size标记的是其中包含的元素的个数。所以会出现size很小但elementData.length很大的情况,将出现空间的浪费。trimToSize将返回一个新的数组给elementData,元素内容保持不变,length和size相同,节省空间。
(7)将列表转为静态数据
ArrayList 提供了两个 toArray 方法:
a. 调用Arrays.copyOf将返回一个数组,数组内容是size个elementData的元素,即拷贝elementData从0至size-1位置的元素到新数组并返回。
public Object[] toArray() { return Arrays.copyOf(elementData, size); }
b. 如果传入数组的长度小于size,返回一个新的数组,大小为size,类型与传入数组相同。所传入数组长度与size相等,则将elementData复制到传入数组中并返回传入的数组。若传入数组长度大于size,除了复制elementData外,还将把返回数组的第size个元素置为空。
@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; }
- ArrayList(一)源码分析
- 一、ArrayList源码分析
- JDK源码分析之ArrayList(一)
- Java集合源码分析系列-(一)ArrayList源码剖析
- ArrayList源码(一)
- 了解 ArrayList 源码(一)
- ArrayList源码解析(一)
- ArrayList源码分析(基于JDK1.6)
- ArrayList源码分析(基于JDK1.6)
- ArrayList源码分析(基于JDK1.6)
- ArrayList源码分析(基于JDK1.6)
- ArrayList源码分析(jdk1.8)
- ArrayList源码分析(Java&Android)
- ArrayList源码分析(JDK1.8)
- ArrayList部分源码分析(基于1.8)
- ArrayList源码分析(基于JDK8)
- ArrayList源码分析(JDK1.8)
- java集合(2):ArrayList源码分析
- Xmanager企业版中各软件功能简介
- 微信小程序开发之数据存储 参数传递 数据缓存
- [机器学习收藏] TensorFlow初学者必须了解的55个经典案例
- socket阻塞与非阻塞,同步与异步
- Python多线程
- ArrayList(一)源码分析
- springboot默认错误页面及静态资源
- java注解应用实例
- 【React Native】差量热更新(二)
- SElinux详解
- 欢迎使用CSDN-markdown编辑器
- 树状数组的单点更新,区间查询。
- C++动态内存管理
- dhtmlx5源码解析(一)全局方法分析 dhtmlxValidation&&浏览器类型