ArrayList介绍
List接口继承了集合类中的基本接口Collection接口。list存储有序的,可重复的数据,也被称之为动态数组。而ArayList类就是list接口的实现类之一。
来一个简单的图示:
语法格式:
ArrayList<数据类型> 变量名 = new ArrayList<数据类型>();
“<数据类型>”表示泛型,限定了存储在集合中元素的数据类型,可以自行定义。
主要特点:
ArrayList作为list接口的主要实现类,是线程不安全的,但是效率高。
ArrayList类的常用方法及源码理解
ArrayList在底层使用Object[] elementData数组存储。
transient Object[] elementData;
1.构造方法
- public ArrayList()
空参构造方法
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
这里的DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个Object类型的数组,值被赋值了一个{},这样的好处就是当ArrayList对象被实例化之后,并没有第一时间将数组实例化。在某些场景中节省了一定时间内的内存。
- public ArrayList(int initialCapacity)
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);
}
}
如果参数 > 0 则底层创建一个给定长度的数组。如果等于零,则与空参构造器无异,小于零就会抛出IllegalArgumentException类型的异常。如果确定数据的长度的话,可以直接使用带参的构造器,就会更少的用到扩容操作,效率就要高一些。
2.size() 方法
public int size() {
return size;
}
在每一个实例化的ArrayList对象中都有一个私有的size成员变量,用来记录数组的列表的长度。
3.add(Object element)
向列表的尾部添加指定的元素
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
由于底层使用数组实现,所以每次添加元素的时候都需要判断,size+1是否大于数组的长度,如果大于的话,就需要对数组进行扩容操作。因为底层会自动扩容,而不需要人为的去判断数组的容量是否够用。相较于LinkedList底层用双向链表,ArrayList可谓是名副其实的动态数组了。
4.get(int index)
返回列表中指定位置的元素,index从0开始
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
首先检查下标index的大小,如果大于数组长度的话,就会抛出我们熟悉的IndexOutOfBoundsException下标越界异常。然后返回的方式和数组一样,通过下标返回数组中元素return (E) elementData[index];
5.clear()
从列表中移除所有元素
public void clear() {
modCount++;
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
modCount是一个transient类型(即不会被序列化)的整型数据,用于记录这个列表结构被修改的次数,然后遍历底层数组,将所有元素都赋null,同时将size赋值为0
6.isEmpty()
判断列表是否包含元素,不包含元素则返回 true,否则返回false
public boolean isEmpty() {
return size == 0;
}
源码就是判断Size是否等于零
7.remove(int index)
移除列表中指定索引位置(从0开始)的元素,并返回被删元素,同时后面元素向前移动
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;
}
首先检查下标是否越界,然后记录列表改变的modCount++,记录下当前下标对应的数组值,然后求出从index到数组末尾共有多少个元素,如果>0(即当前下标对应的不是数组最后一个元素),则通过System中提供了一个native的静态方法arraycopy()将数组后面部分,往前复制。
8.remove(Object o)
移除集合中第一次出现的指定元素,移除成功返回true,否则返回false
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;
}
首先判断参数是否为空,如果为空的话,遍历数组,当遇到第一个为空的元素时,就通过Index下标,和上述一样通过下标将为空的元素移除。
然后如果不为空的话,同样遍历数组 ,使用Object类中的equals方法,也就是== 来判断是否相等,如果相等的话一样通过下标Index,将数组重新整理。
注意:两种情况的for循环都是满足 条件直接return,所以是找第一个。
9.contains(Object o)
如果列表包含指定的元素,则返回 true
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
for()循环遍历数组,然后判断参数是否和数组中的某个元素相等,如果相等返回下标,否则返回-1
10.add(int index, Object 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++;
}
第一步判断index是否在0到size之间,不是的话抛出异常,第二步,判断size+1之后是否超出了底层数组长度的最大值,如果超出则需要进行数组的扩容,然后就是利用System中提供的native的静态方法arraycopy()把从index到size的内容全部往数组后复制,最后将index位置填入新增的数据,Size++。
11.set(int i, Object element)
将索引 i 位置元素替换为元素element并返回被替换的元素
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
第一步检查下标是否越界,第二步将数组中本来index位置初的元素保存,第三步替换,第四步返回,和我们自己写的数组元素替换没啥区别。
扩容操作
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);
}
扩容步骤:
1.记录下底层数组的长度
2.设置一个新的长度,为原来的1.5倍。
3. 如果扩容之后的大小与需要的大小相比,还是小了,就直接拿需要的长度
4.如果需要的容量比底层设置的阈值,就会把新的长度设置为整形的最大值,如果还超过的话,就报error了
5.根据新的长度创建一个数组,然后把旧的数组中的数据copy入新的数组