集合篇(一)——ArrayList
ArrayList是一个在内存中连续分配的Object数组,
优点:在遍历元素和随机访问元素的效率高,因为是数组,访问时只需要根据下标进行查找
就行,
缺点:在添加和删除元素时,就需要进行移动大量元素,在java中是利用Arrays.copyOf()
方法以及System.arraycopy
方法来进行移动,开销大
下面是ArrayList的源码
(一):重要属性
//修改次数
protected transient int modCount = 0;
//链表大小
private int size;
//数组最大长度
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//默认创建一个空集合
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];
//存放元素的数组
transient Object[] elementData;
底层是利用Object数组来存储的,在1.8中会利用一个空的Object数组来当做初始化,提供size来记录链表大小,提供了size()来获取这个size
(二)构造方法
//ArrayList空参构造
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//单参传入容量
public ArrayList(int initialCapacity) {
//如果容量大于0
if (initialCapacity > 0) {
//进行new一个Object数组
this.elementData = new Object[initialCapacity];
//如果容量为0,直接赋值为默认空的Object数组
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
//因为此时容量小于0了,所以抛出违规的容量提示
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
这里只先介绍常用的无参构造器以及单参传入大小构造器,无参构造器是将Object数组指向了我们默认的空Object数组,而单参则是判断我们的容量并且根据容量来选择分支
(三)常见方法
ensureCapacityInternal():判断是否需要进行扩容
private void ensureCapacityInternal(int minCapacity) {
//如果是默认的空Object数组,代表还没进行初始化,直接比较最大值,让较大的那个来作为容量
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//判断是不是需要进行扩容,
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
//修改次数++
modCount++;
// 如果我们需要的容量大于了我们的Object数组容量
if (minCapacity - elementData.length > 0)
//调用grow方法来进行扩容
grow(minCapacity);
}
grow():扩容,重要
private void grow(int minCapacity) {
// 首先获取到旧容量
int oldCapacity = elementData.length;
//获取到1.5倍的旧容量,新容量=旧容量+旧容量/2 (PS:这边的 ‘>>‘ 代表是右移1位,位运算,代表除以2)
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果新容量还是比我们需要的容量小
if (newCapacity - minCapacity < 0)
//直接让新容量=我们需要的容量
newCapacity = minCapacity;
//如果新容量大于了最大的数组长度
if (newCapacity - MAX_ARRAY_SIZE > 0)
//调用方法,如果是大于最大的容量就返回Integer.Max_value
newCapacity = hugeCapacity(minCapacity);
// 调用Arrays.copyOf()方法来进行复制数组元素,将原来的元素放入到我们新创建的数组中
//这就是扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
add(E e):添加元素
public boolean add(E e) {
//上面已经做了基本解析,不做讨论
ensureCapacityInternal(size + 1); // Increments modCount!!
//这里就是将我们的元素赋值给Object数组
elementData[size++] = e;
//返回true,代表添加成功
return true;
}
add(int index,E e):在指定的位置添加元素
public void add(int index, E element) {
//
rangeCheckForAdd(index);
//判断是否需要进行扩容,上面已经讨论过
ensureCapacityInternal(size + 1); // Increments modCount!!
//复制元素,主要就是将中间目的index处的地方空出来,将0-index不变,index-size移动到(index+1)-(size+1)处
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//直接赋值
elementData[index] = element;
//长度++
size++;
}
get(int index):获取指定位置的元素
public E get(int index) {
//首先是检查下标是否违法
rangeCheck(index);
//直接返回指定下标的元素
return elementData(index);
}
set(int index,E element):设置元素
public E set(int index, E element) {
//检查范围,判断下标是否有问题
rangeCheck(index);
//先获取原来的元素,毕竟方法返回值是原数组下标元素
E oldValue = elementData(index);
//直接覆盖
elementData[index] = element;
//返回原数组元素
return oldValue;
}
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来进行复制
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//元素前移了以后,最后一个没改变,需要把它变成空的
elementData[--size] = null; // clear to let GC do its work
//返回旧值
return oldValue;
}
以上就是ArrayList的部分源码了,总结就是底层采用Object数组,然后扩容是1.5倍,是采用的位运算操作,然后因为是操作数组,所以每次修改操作都需要进行检查下标的范围,很多地方用到了Array.copyof()以及System.arraycopy()方法
以上是个人见解,可能有误,勿喷