深入了解ArrayList。
环境:
- eclipse 2019-03 (4.11.0)
- jdk-12.0.1
eclipse中查看源码的方法:按住Ctrl键,鼠标左键单击代码(更详细请百度)。
容器:在Java中,“集合”有另外的用途,所以ArrayList、HashMap等皆称为容器类,其创建的一个对象就是一个容器。
博文中涉及到的源码:
- 类声明
- 字段
- 构造器
- size(),isEmpty(),contains(),indexof(),lastIndexOf(),toArray()
- 获得元素get()
- 修改元素set()
- 添加元素add()
- 删除元素remove()
- 去除所有空trimToSize()
- 扩容机制
- 清空所有元素clear()
- 追加一个数组addAll()
- 移除元素集合removeAll()
- 遍历元素iterator()
- 自定义序列化readObject(),writeObject()
- 重写equals()和hashCode()
简介
ArrayList是一个基于数组结构的List,与数组的最大区别在于其可以实现动态扩容。
ArrayList源码分析
1、类声明
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{...}
源码分析:
ArrayList继承了抽象类AbstractList的同时还实现了接口List。AbstractList类声明:
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>{...}
可以看到AbstractList也实现了List接口,和HashMap等一样,这样的做法原因公认度最高的有两种:1)、添加List接口是为了Class类的getInterfaces这个方法可以直接返回List接口。2)、这就是写法上的错误,并没有什么深意,这是得票最高的答案,回答者称曾问过此段代码的设计者Josh Bloch。
ArrayList实现了接口RandomAccess。RandomAccess声明:
public interface RandomAccess {}
查看源码可以看到RandomAccess只是一个标识接口,实现该接口的作用是能支持快速随机访问,具体可以看看ArrayList集合实现RandomAccess接口有何作用?为何LinkedList集合却没实现这接口?
实现Cloneable接口(标识接口),该接口不包含任何方法,实现它仅仅是为了指示可以使用Object类中的clone()方法来进行克隆。
public interface Cloneable {}
实现Serializable接口(标识接口),表示该类可以进行序列化。该接口表示所有的非transient和非static字段都可以被序列化。如果要实现transient字段与static字段的序列化保存,必须声明writeObject和readObject方法
public interface Serializable {}
2、字段
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData; // non-private to simplify nested class access
private int size;
源码分析:
DEFAULT_CAPACITY:数组的默认大小。
EMPTY_ELEMENTDATA :空数组。
DEFAULTCAPACITY_EMPTY_ELEMENTDATA:空数组。在调用无参构造器创建ArrayList对象时使用。
elementData:数组,用于存储元素。
size:int类型,用于记录已存储的元素数量。
除了以上的字段之外,ArrayList中还有两个字段是必须要知道的:
protected transient int modCount = 0; private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//2147483631
第一个modCount:该字段不是在ArrayList中声明的,而是其父类AbstractList中的字段,用于记录ArrayList结构性变化的次数(涉及到结构变化的操作如add(),remove()等)。由于此字段的存在,在遍历删除元素的时候有很多注意事项,更多可以看看ArrayList.remove()的正确用法(Java随笔)
第二个MAX_ARRAY_SIZE :该字段、、、算是一个标准吧。一般情况下,数组的最大容量就是该值,但是如果要分配的数组容量大于了该值,就会使用Integer.MAX_VALUE作为数组容量。两者相差8。
3、构造器
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() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// defend against c.toArray (incorrectly) not returning Object[]
// (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
源码分析:ArrayList有三个构造器
ArrayList(int initialCapacity):该构造函数要求传入一个int值,作为数组的初始大小。
ArrayList():无参构造器,创建一个空数组。
ArrayList(Collection<? extends E> c):根据已有的Collection容器对象创建新的ArrayList对象。
4、size(),isEmpty(),contains(),indexof(),lastIndexOf(),toArray()
public int size() {
return size;//返回已有元素数量
}
public boolean isEmpty() {
return size == 0;//返回true或false,判断容器是否没有存储数据
}
public boolean contains(Object o) {
return indexOf(o) >= 0;//返回true或false,判断容器是否包含指定元素
}
public int indexOf(Object o) {
return indexOfRange(o, 0, size);//根据元素值获得元素在容器数组中的位置索引,从前往后
}
/*根据元素值获得元素在容器数组中的位置索引分两步:
*1、指定元素为null,应该使用“==”进行判断
*2、指定元素不为null,应使用“equals()”进行判断
*/
int indexOfRange(Object o, int start, int end) {
Object[] es = elementData;
if (o == null) {
for (int i = start; i < end; i++) {
if (es[i] == null) {
return i;
}
}
} else {
for (int i = start; i < end; i++) {
if (o.equals(es[i])) {
return i;
}
}
}
return -1;
}
public int lastIndexOf(Object o) {//根据元素值获得元素在容器数组中的位置索引,从后往前
return lastIndexOfRange(o, 0, size);
}
int lastIndexOfRange(Object o, int start, int end) {
Object[] es = elementData;
if (o == null) {
for (int i = end - 1; i >= start; i--) {
if (es[i] == null) {
return i;
}
}
} else {
for (int i = end - 1; i >= start; i--) {
if (o.equals(es[i])) {
return i;
}
}
}
return -1;
}
public Object clone() {//重写Object类中的clone方法,浅克隆
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
public Object[] toArray() {//返回一个新数组,数组中包含容器中所用元素。
return Arrays.copyOf(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)//若指定数组太大,将size位置设置为null
a[size] = null;
return a;
}
补充:ArrayList中很多地方都用到了Arrays.copyOf()或System.arraycopy()进行数组的复制,两者具体的区别在
- Arrays.copyOf()返回一个新数组
- System.arraycopy()没有返回值,是将数据复制到给定的数组中
Arrays.copyOf()和System.arraycopy()的源码分析可以看看Arrays.copyOf()&System.arraycopy()
5、获得元素get()
public E get(int index) {
Objects.checkIndex(index, size);//Object中的静态方法,用于判断index范围
return elementData(index);
}
源码分析:get(int index)根据位置索引获得元素。
判断index时,若(index<0||index>=size)则会抛出异常outOfBoundsCheckIndex。这里限制index的上限为size是非常有必要的,因为所有元素的索引值都在size以内,而size是小于数组长度的,若没有限制index的上限为size,则获取的值将是null值而不会报出任何异常。
elementData()返回数组elementData中索引index处的元素。该方法是用于获取元素,在ArrayList源码中所有获取数组中的元素都是调用该方法,而不是直接操作数组。
@SuppressWarnings("unchecked") E elementData(int index) { return (E) elementData[index]; }
这里有一个设计问题,为什么不将判断索引大小的语句Objects.checkIndex(index, size);放于elementData()方法中,而是写进了get()方法?(接下来的set(),remove()等方法中皆是如此)为何要将该语句在每一个方法中都写一遍,而不是直接写在elementData()中。写个模仿ArrayList获取元素的程序来分析:
import java.util.Objects; public class Run { int[] datas={10,20}; int elementData(int index) { return datas[index]; } public int get(int index) { Objects.checkIndex(index, 2); return elementData(index); } //main public static void main(String[] args) { Run run=new Run(); System.out.println(run.get(-1)); } }
这是ArrayList中的写法,将索引设置为-1,运行该程序,报错信息为
将Objects.checkIndex(index, 2);放入elementData()中后再运行程序,报错信息为
对比一下两个错误信息,不同之处在于修改后的程序报错信息多了一句at com.reflect.test.Run.elementData(Run.java:11),指向错误来源为该方法中,但是elementData()方法的权限声明并不是public的,意味着我们并不希望客户端知道elementData()这个方法的存在,而报错信息却表明了该方法,违背了封装原则。所以应该将判断index大小的语句放置在我们希望能被客户端知道的方法中。
6、修改元素set()
public E set(int index, E element) {
Objects.checkIndex(index, size);//检查index大小是否符合要求
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
源码分析:set()方法修改数据,返回原数据
第一步依然是判断指定位置索引index大小是否在0~size-1中,若(index<0||index>=size)则会抛出异常outOfBoundsCheckIndex。
接着获取原数据,设置新数据,返回原数据,程序结束。
7、添加元素add()
private void add(E e, Object[] elementData, int s) {//重载方法,方法私有,供内部方法调用
if (s == elementData.length)
elementData = grow();//若元素个数s已经达到了数组的大小,调用grow()扩容
elementData[s] = e;
size = s + 1;
}
public boolean add(E e) {//追加元素
modCount++;//记录结构改变次数
add(e, elementData, size);//调用重载方法
return true;
}
public void add(int index, E element) {//在指定位置添加元素
rangeCheckForAdd(index);//若(index > size || index < 0)则抛出IndexOutOfBoundsException(outOfBoundsMsg(index))异常。
modCount++;//记录结构改变次数
final int s;
Object[] elementData;
if ((s = size) == (elementData = this.elementData).length)
elementData = grow();//若size已经达到了数组的大小,调用grow()扩容
System.arraycopy(elementData, index,
elementData, index + 1,
s - index);//从插入位置index开始,后面的元素异常往后移
elementData[index] = element;
size = s + 1;
}
源码分析:ArrayList向客户端代码提供了两个可访问的添加元素方法。
add(E e):在数组最后一个元素后面添加元素。调用了重载方法add(E e, Object[] elementData, int s)。返回true。
add(int index, E element):在指定位置添加元素。使用了System.arraycopy()来实现数据的移动。
8、删除元素remove()
public E remove(int index) {//根据索引删除元素,删除成功返回原数据
Objects.checkIndex(index, size);//检查index是否符合要求
final Object[] es = elementData;
@SuppressWarnings("unchecked") E oldValue = (E) es[index];
fastRemove(es, index);//调用方法,实现快速移除
return oldValue;//返回原数据
}
public boolean remove(Object o) {//删除指定元素,删除成功返回true,失败返回false
final Object[] es = elementData;
final int size = this.size;
int i = 0;
found: {
if (o == null) {
for (; i < size; i++)
if (es[i] == null)
break found;//跳出found域
} else {
for (; i < size; i++)
if (o.equals(es[i]))
break found;//跳出found域
}
return false;
}
fastRemove(es, i);//调用方法,实现快速移除
return true;
}
private void fastRemove(Object[] es, int i) {//快速删除元素
modCount++;
final int newSize;
if ((newSize = size - 1) > i)
System.arraycopy(es, i + 1, es, i, newSize - i);
es[size = newSize] = null;//非常有必要的一步。置空最后一位,方便GC快速回收。
}
源码分析:ArrayList提供了两个可访问的remove()方法
remove(int index):在指定位置移除元素。移除成功,返回被移除的元素值
remove(Object o):移除指定元素。移除成功返回true,失败返回false
快速删除元素是利用System.arraycopy()来实现的,本地方法比循环移动元素更快。
9、去除所有空trimToSize()
public void trimToSize() {//将数组大小重新定义为size,使得数组中没有null空值
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
补充:trimToSize()使用了Arrays.copyOf()来实现重新设置数组大小,得到的是一个新的数组对象。
10、扩容机制
public void ensureCapacity(int minCapacity) {//确定数组容量,
if (minCapacity > elementData.length
&& !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
&& minCapacity <= DEFAULT_CAPACITY)) {
modCount++;//结构将改变,记录数加一
grow(minCapacity);//调用grow()方法
}
}
private Object[] grow(int minCapacity) {//返回扩容后的新数组,主要用于addAll()
return elementData = Arrays.copyOf(elementData,
newCapacity(minCapacity));
}
private Object[] grow() {//主要用于add()
return grow(size + 1);
}
private int newCapacity(int minCapacity) {//计算获得新数组的容量
// overflow-conscious code
int oldCapacity = elementData.length;//原数组容量
int newCapacity = oldCapacity + (oldCapacity >> 1);//计算后的新容量,原来的3/2
if (newCapacity - minCapacity <= 0) {//若自定义的新容量更大
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return minCapacity;//问题,问题,问题????
}
return (newCapacity - MAX_ARRAY_SIZE <= 0)//判断新容量是否超过了MAX_ARRAY_SIZE
? newCapacity
: hugeCapacity(minCapacity);
}
private static int hugeCapacity(int minCapacity) {//最大容量判断
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE)
? Integer.MAX_VALUE
: MAX_ARRAY_SIZE;//两者相差8
}
源码分析:ArrayList扩容机制实现步骤为
- 计算扩容后的大小(多数时候是扩容为原容量的3/2倍)。
- 判断扩容后的大小是否满足要求,做出相应处理。
- 使用Arrays.copyOf()进行扩容,返回新数组。
问题,在方法 newCapacity(int minCapacity)中
此处直接返回了minCapacity,而没有与MAX_ARRAY_SIZE进行比较。由于minCapacity可能存在很大的情况,于是会出现的问题是最终的数组容量远大于MAX_ARRAY_SIZE。在以前的版本中,ArrayList关于扩容的源码在涉及到这一步时是这样写得
至于为什么现在的版本中不这样写了,俺也不知道,俺也莫法问。
11、清空所有元素clear()
public void clear() {//清空所有元素
modCount++;//记录结构改变
final Object[] es = elementData;
for (int to = size, i = size = 0; i < to; i++)
es[i] = null;//循环清空所有元素
}
12、追加一个数组addAll()
public boolean addAll(Collection<? extends E> c) {//追加另一个数组
Object[] a = c.toArray();
modCount++;//记录结构改变
int numNew = a.length;
if (numNew == 0)
return false;
Object[] elementData;
final int s;
if (numNew > (elementData = this.elementData).length - (s = size))
elementData = grow(s + numNew);//扩容
System.arraycopy(a, 0, elementData, s, numNew);
size = s + numNew;
return true;
}
public boolean addAll(int index, Collection<? extends E> c) {//在指定位置开始插入一个数组
rangeCheckForAdd(index);
Object[] a = c.toArray();
modCount++;
int numNew = a.length;
if (numNew == 0)
return false;
Object[] elementData;
final int s;
if (numNew > (elementData = this.elementData).length - (s = size))
elementData = grow(s + numNew);//扩容
int numMoved = s - index;
if (numMoved > 0)
System.arraycopy(elementData, index,
elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size = s + numNew;
return true;
}
13、移除元素集合removeAll()
public boolean removeAll(Collection<?> c) {//移除集合c与当前集合的所有交集
return batchRemove(c, false, 0, size);
}
boolean batchRemove(Collection<?> c, boolean complement,
final int from, final int end) {
Objects.requireNonNull(c);
final Object[] es = elementData;
int r;
// Optimize for initial run of survivors
for (r = from;; r++) {
if (r == end)
return false;
if (c.contains(es[r]) != complement)
break;
}
int w = r++;
try {
for (Object e; r < end; r++)
if (c.contains(e = es[r]) == complement)
es[w++] = e;
} catch (Throwable ex) {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
System.arraycopy(es, r, es, w, end - r);
w += end - r;
throw ex;
} finally {
modCount += end - w;
shiftTailOverGap(es, w, end);//整理数据,clear to let GC do its work
}
return true;
}
源码分析:removeAll()和一样,都是通过batchRemove()来实现对Collection集合与当前容器操作的。
在batchRemove()中通过contains()找出Collection集合与当前集合的交集。contains()中用到了equest()来判断数据是否相同。
在batchRemove()中通过shiftTailOverGap()来清除当前容器中与Collection集合的所有交集。
private void shiftTailOverGap(Object[] es, int lo, int hi) {//clear to let GC do its work System.arraycopy(es, hi, es, lo, size - hi); for (int to = size, i = (size -= hi - lo); i < to; i++) es[i] = null; }
14、遍历元素iterator()
public Iterator<E> iterator() {//返回迭代器对象,封装在容器内部的迭代器
return new Itr();
}
private class Itr implements Iterator<E> {//迭代器实现类,主要了解hasNext(),next()
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
// prevent creating a synthetic constructor
Itr() {}
public boolean hasNext() {//判断容器中是否还有没有被访问到的元素
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {//获取下一个元素
checkForComodification();//用于检查容器结构改变次数
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;//ArrayList.this是ArrayList对象自身引用。这种用法在Java编程思想第193页有提到。
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int size = ArrayList.this.size;
int i = cursor;
if (i < size) {
final Object[] es = elementData;
if (i >= es.length)
throw new ConcurrentModificationException();
for (; i < size && modCount == expectedModCount; i++)
action.accept(elementAt(es, i));
// update once at end to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
}
final void checkForComodification() {//检查结构的改变次数
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
源码分析:关于迭代的使用,最常用的方法是hasNext()和next()
hasNext():判断是否还有下一个元素
next():获取下一个元素
每次执行next()时,都会有一个关于modCount的检查,若检查的结果是false,则表示列表数据进行了结构性的变化,会抛出ConcurrentModificationException异常。
final void checkForComodification() {//检查结构的改变次数 if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
15、自定义序列化readObject(),writeObject()
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();//默认序列化
// Write out size as capacity for behavioral compatibility with clone()
s.writeInt(size);//write与read一一对应
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// like clone(), allocate array based upon size not capacity
SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Object[].class, size);
Object[] elements = new Object[size];
// Read in all elements in the proper order.
for (int i = 0; i < size; i++) {
elements[i] = s.readObject();
}
elementData = elements;
} else if (size == 0) {
elementData = EMPTY_ELEMENTDATA;
} else {
throw new java.io.InvalidObjectException("Invalid size: " + size);
}
}
源码分析:writeObject(java.io.ObjectOutputStream s)方法和readObject(java.io.ObjectInputStream s)方法是实现序列化的两个重要方法,声明这两个方法的目的是为了实现自己可控制的序列化
序列化时,ObjectOutputStream会采用反射查找Serializable实现类内部是否声明了这两个方法,若有,则调用该方法。否则将采用默认的序列化进程。
16、重写equals()和hashCode()
//equals()
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof List)) {
return false;
}
final int expectedModCount = modCount;
// ArrayList can be subclassed and given arbitrary behavior, but we can
// still deal with the common case where o is ArrayList precisely
boolean equal = (o.getClass() == ArrayList.class)
? equalsArrayList((ArrayList<?>) o)
: equalsRange((List<?>) o, 0, size);
checkForComodification(expectedModCount);
return equal;
}
boolean equalsRange(List<?> other, int from, int to) {
final Object[] es = elementData;
if (to > es.length) {
throw new ConcurrentModificationException();
}
var oit = other.iterator();
for (; from < to; from++) {
if (!oit.hasNext() || !Objects.equals(es[from], oit.next())) {
return false;
}
}
return !oit.hasNext();
}
private boolean equalsArrayList(ArrayList<?> other) {
final int otherModCount = other.modCount;
final int s = size;
boolean equal;
if (equal = (s == other.size)) {
final Object[] otherEs = other.elementData;
final Object[] es = elementData;
if (s > es.length || s > otherEs.length) {
throw new ConcurrentModificationException();
}
for (int i = 0; i < s; i++) {
if (!Objects.equals(es[i], otherEs[i])) {
equal = false;
break;
}
}
}
other.checkForComodification(otherModCount);
return equal;
}
private void checkForComodification(final int expectedModCount) {
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
//hashCode()
public int hashCode() {
int expectedModCount = modCount;
int hash = hashCodeRange(0, size);
checkForComodification(expectedModCount);
return hash;
}
int hashCodeRange(int from, int to) {
final Object[] es = elementData;
if (to > es.length) {
throw new ConcurrentModificationException();
}
int hashCode = 1;
for (int i = from; i < to; i++) {
Object e = es[i];
hashCode = 31 * hashCode + (e == null ? 0 : e.hashCode());
}
return hashCode;
}
源码分析:
hashCode():基本上所有的hashCode都是一样的算法,ArrayList中使用了容器中所有元素的hashCode()值来计算ArrayList对象的hashCode值。
值得注意的是,这两个方法中都会检测modCount值是否被改变。如果该值被改变,则表示容器发生结构性改变,会抛出ConcurrentModificationException异常。
总结
ArrayList的实质就是一个数组,只不过数组的大小不能随意更改,而ArrayList可以。
- ArrayList扩容的代价是很大的,扩容机制的核心是创建一个大小适合的新数组对象,然后将原数组中的所有数据复制到新数组中,涉及到大量数据的转移。
- 由于扩容代价大,因此在创建ArrayList对象的时候,如果能明确自己的需求,应该给定一个容器大小的初始值,避免扩容机制的不断触发。
虽然ArrayList的实质就是一个数组,但并不意味着我们应该在数组和ArrayList之间优先使用ArrayList。
- 当存储的元素是基本类型时,应该综合Java的运行时存储机制进行考虑
- 列表存储基本类型数据时,所有的数据都是存储在栈中,访问速度快且占用内存小。
- 包括HashMap,ArrayList在内的所有的容器,都不能直接存储基本类型数据,所有的基本数据类型都会被自动包装成对应的包装器类。
- 在用ArrayList存储基本类型数据时,由于自动包装机制,每一个基本类型数据都会被包装为一个对象,即所有的数据都会以对象的形式存储在堆中。(当然不是所有的对象都存储在堆中Java对象的创建及存储位置)