ArrayList 底层学习
底层知识
底层使用的是数组的数据结构
属性
//默认的初始化容量
private static final int DEFAULT_CAPACITY = 10;
//一个空的Object数组的实例(当使用有参构造器public ArrayList(int initialCapacity)时候自定义容量为0时候赋值给elementData)
private static final Object[] EMPTY_ELEMENTDATA = {};
//和上面一样是个空数组实例(当使用默认无参构造器时赋值给elementData)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//真正的存放数组的数组
transient Object[] elementData;
//数组的长度
private int size;
构造器
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];//如果>0则直接立马给数组分配空间
} else if (initialCapacity == 0) {
//如果输入参数为0,那么就赋值一个空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
public ArrayList() {
//可在属性中查看
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
从这两个构造器中我们发现,如果我们给它一个整型参数且>0,那么会立马给elementData数组分配空间,如果为0则不管。但是如果调用无参构造器的时候我们发现他并没有分配空间,而是将它指向默认的空的elementData对象,但是并没有立马给他分配空间
newCapacity(int minCapacity)
//这个是将数组进行一个扩容操作,
private int newCapacity(int minCapacity) {
//保存旧数组的容量
int oldCapacity = elementData.length;
//新数组的大小为旧数组+旧数组右移一位的和,相当于是扩大为原来1.5倍(newCapacity为扩容后的容量)
int newCapacity = oldCapacity + (oldCapacity >> 1);
//下面判断为真的例子
/*
当我们使用默认构造器时候,我们的elementData对象是一个空数组,
那么oldCapacity = elementData.length = 0
那么 newCapacity = oldCapacity + (oldCapacity >> 1) = 0
minCapacity = size + 1 ,此时size = 0,minCapacity = 1
newCapacity - minCapacity = -1 <= 0
*/
if (newCapacity - minCapacity <= 0) {
//进入下面的判断分支说明使用的是默认构造器
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);
// overflow(内存溢出)
if (minCapacity < 0)
throw new OutOfMemoryError();
//如果使用有参构造器并且自定义capacity定义为0,就会使用下面的return,即返回1
//(非常不建议这样,因为这样会导致数组不停的扩容,影响性能)
return minCapacity;
}
//这是正常的扩容容量,
return (newCapacity - MAX_ARRAY_SIZE <= 0) //判断新容量不超过最大容量的三目运算
? newCapacity //为真
: hugeCapacity(minCapacity); //为假(分析可以看下面)
}
private static int hugeCapacity(int minCapacity) {
//内存已经用完了
if (minCapacity < 0)
throw new OutOfMemoryError();
//minCapacity已经超过定义的最大数组长度的三目运算
return (minCapacity > MAX_ARRAY_SIZE)
? Integer.MAX_VALUE //为真(设置为最大的Int的最大数值容量)
: MAX_ARRAY_SIZE; //为假(设置为定义的最大容量)
}
add()方法
//这个是扩容时调用的grow方法
private Object[] grow(int minCapacity) {
return elementData = Arrays.copyOf(elementData,//这里是将旧数组的信息复制到新数组中
newCapacity(minCapacity));
}
private Object[] grow() {
return grow(size + 1);
}
private void add(E e, Object[] elementData, int s) {
//这里的s传入的基本都是size
//当size的长度达到了数组的定义的长度时候就需要会进行扩容
if (s == elementData.length)
elementData = grow();
//这里将数据保存进数组中,并且将size + 1
elementData[s] = e;
size = s + 1;
}
//平时调用的最多的方法
public boolean add(E e) {
//这个就是继续数组被更改的次数
modCount++;
//调用私有方法add
Object[] elementData;
if ((s = size) == (elementData = this.elementData).length)
elementData = grow();
System.arraycopy(elementData, index,
elementData, index + 1,
s - index);
elementData[index] = element;
size = s + 1;
}
//-----------------------------下面为按下标进行一个插入操作---------------------------------------------
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/*
这个方法我们可以明确的发现
说明:只能插入在list的size以内的数据,相当于只能替换时候使用该方法,平时的插入还是以add(E e)为主
*/
public void add(int index, E element) {
//对该下标进行一个判断,看看是否超过size或者index < 0
rangeCheckForAdd(index);
//对数组修改操作 + 1
modCount++;
final int s;
Object[] elementData;
//和一个参数的add()方法一样,判断需不需要进行一个扩容操作
if ((s = size) == (elementData = this.elementData).length)
elementData = grow();
System.arraycopy(elementData, index,
elementData, index + 1,
s - index);
//将数据存储到数组中
elementData[index] = element;
//size + 1
size = s + 1;
}
Vector(线程安全)
底层也是使用的暴力加锁方法,直接使用synchronized修饰
如何选择线程安全的集合
1.可以选择线程安全的类,例如Vector,HashTable,ConcurrentHashMap
2.可以使用Collections.synchronized*(),这样的方法