ArrayList是一个多线程不安全的基于数组实现的集合结构
1、基本参数
private static final int DEFAULT_CAPACITY = 10;
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private static final Object[] EMPTY_ELEMENTDATA = {};
private transient Object[] elementData;
默认容量为10,最大容量为:Integer.MAX_VALUE - 8
elementData:数组对象,用来当作容器
EMPTY_ELEMENTDATA:空数组,容量为0
2、构造方法:
有参构造方法:
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);
}
}
若传入的initialCapacity大于0,则初始化一个Object类型的数组,并用这个initialCapacity作为它的初始容量;若initialCapacity为0则构造一个空数组;若小于0就抛出异常。
无参构造方法:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
它会初始化一个空的数组
从上面我们可以看到,ArrayList初始化时除非我们给它赋初值,否则它会创建一个空的数组并且动态的增加数组容量
3、常用方法:
**public E get(int index)
public E set(int index, E element)
public boolean add(E e)
public void add(int index, E element)
public E remove(int index)
public boolean remove(Object o)
**
插入元素:
set方法:
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
获取位置i,并向其中插入元素,若i不在数组的size范围之内则抛出异常,否则用新值替换旧值并返回旧值
与add方法相关的一系列方法:
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
/*刚初始化并且未给数组赋初值*/
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
add为直接添加一个元素。
若是刚初始化并且未给数组赋初值则会为数组赋默认初值DEFAULT_CAPACITY=10,然后插入当前元素
每当数组已满时就开始扩容
扩容:
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
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);
}
新容量为oldCapacity +oldCapacity /2,变成原来的1.5倍大小并将原数组所有元素拷贝到新数组中
另一个add方法:
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
在指定位置新增一个元素,并将这个位置往后所有元素都右移一位,而不是用新元素去替换旧元素
获取元素:
get方法:
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
获取位置i上的元素,若i不再size范围内则抛出异常,这里注意,若容量为10而里面的元素只有5个,则arrayList.get(i>5)会抛出异常而不是null
实例:
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
public class T {
@Test
public void tt() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
List list = new ArrayList();
/*for(int i=0;i<1;i++) {
list.add(1);
}*/
Field field = list.getClass().getDeclaredField("elementData");
field.setAccessible(true);
Object[] elementData = (Object[]) field.get(list);
System.out.println("ArrayList容量: " + list.size());
System.out.println("----------分割线---------");
System.out.println("内部数组容量: " + elementData.length);
System.out.println("----------分割线----------");
System.out.print("元素值为:" + " ");
for(Object i : elementData)
System.out.print(i + " ");
}
}
这里我们通过反射拿到对应的ArrayList内部private变量和private方法,开始并不插入元素,然后打印看结果:
ArrayList容量: 0
----------分割线---------
内部数组容量: 0
----------分割线----------
元素值为:
可以看到,ArrayList和内部数组容量:都为0
然后我们打开上述代码中for循环的注释,并向里面插入一个元素,结果如下:
ArrayList容量: 1
----------分割线---------
内部数组容量: 10
----------分割线----------
元素值为: 1 null null null null null null null null null
可以看到,ArrayList的容量为1,内部数组elementData会自动扩容为10
接下来我们插入10个元素,结果如下:
ArrayList容量: 10
----------分割线---------
内部数组容量: 10
----------分割线----------
元素值为: 1 1 1 1 1 1 1 1 1 1
ArrayList容量没变,elementData容量没变,接下来插入11个元素,结果:
ArrayList容量: 11
----------分割线---------
内部数组容量: 15
----------分割线----------
元素值为: 1 1 1 1 1 1 1 1 1 1 1 null null null null
可以看到,ArrayList的容量和自己添加进去的元素个数一样,而内部数组elementData容量变成了原来的1.5倍,15。接下来插入15个元素:
ArrayList容量: 15
----------分割线---------
内部数组容量: 15
----------分割线----------
元素值为: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
内、外数组都满了,并未扩容
接下来16个:
ArrayList容量: 16
----------分割线---------
内部数组容量: 22
----------分割线----------
元素值为: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 null null null null null
好了,这下明白了,内部数组在一开始时容量为0,等到我们插入1个元素后,自动扩容到10,并且它总是会等到快要溢出的时候才会进行扩容,扩容后的大小为之前容量的1.5倍,而ArrayList容量(size)记录的是你放到数组中元素的个数
remove方法:
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
}
这里就不贴代码了,remove方法主要就是靠上述代码来进行数组的删除。在这里,删除的时候会要求我们传入一个参数,它表示了我们所要删除的元素在数组中的索引。然后ArrayList会将索引数组后面的元素向前挪,并且覆盖这个位置来达到删除
在这里又有一个问题了:
ArrayList循环遍历并删除元素会出现错误吗?
我们用例子来说明:
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
public class T {
@Test
public void tt(){
List list = new ArrayList();
list.add("a");
list.add("b");
list.add("d");
list.add("d");
list.add("e");
list.add("f");
System.out.println(list);
for(int j=0;j<=list.size()-1;j++) {
if(list.get(j).equals("d"))
list.remove(list.get(j));
}
System.out.println(list);
}
}
这里,向ArrayList中添加了6个字符串,并且再一次循环中试图删除其中重复的元素d。下面是结果:
[a, b, d, d, e, f] 这一行是数组刚赋值过后的结果
[a, b, d, e, f] 这一行是数组删除后的结果
可以发现,元素“d”并没有被完全删除。因为每次调用remove方法后,被删除的元素之后所有的元素会向前挪动一位,也就是说这里第一次删除过后,后面一个“d”把删除的“d”的位置给覆盖掉了。下一次进行remove时,就不会再次遍历这个遍历过的位置,导致无法重复删除。
解决办法:删除时从后往前删除,就不会有这种情况了
数组越界的问题:
发生越界时的数组下标分别为10、15、22、33、49和73,由此推断越界异常都发生在数组扩容之时。
这里假设A和B两个线程要向集合中添加元素,首先呢,集合中已经存在了9个元素,正常情况下要是再向里面插入2个元素,它就会扩容,扩容后的容量为15
首先,线程A开始执行add()方法,在执行ensureCapacityInternal(size + 1)时,发现集合没有满,故数组没有扩容,然后线程A被阻塞。接下来线程B进入add()方法,执行ensureCapacityInternal(size + 1),由于前一个线程并没有添加元素,现在容量任然是9,依然不需要扩容,所以该线程就开始添加元素,size++,变为10,此时集合已满。而刚刚被阻塞的线程A开始执行,因为它之前判断不需要扩容,所以就直接向集合中插入第11个元素。这时就发生了数组下标越界异常!
遍历:
1、for循环
List list = new ArrayList();
list .add(1);
list .add(2);
list .add(3);
list .add(4);
for(int i=0;i<list.size();i++)
System.out.println(l.get(i));
推荐使用此方法
2、iterator
Iterator iterator = l.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
或者
for(Iterator it2 = list.iterator();it2.hasNext();){
System.out.println(it2.next());
}
3、增强for
for(String tmp:list){
System.out.println(tmp);
}
总结:ArrayList是一个数组容器,底层由动态数组来进行实现,每当容量已满时就开始扩容,扩容时会创建一个新数组容量为之前的1.5倍,并且会将所有元素拷贝到新数组中。