简单介绍
1、有下标,查询效率高
2、数组增删改方法涉及数组拷贝,效率低
3、方法未加锁及其他同步操作.线程不安全
4、使用频率很高,理解较为简单
创建
注意:它的初始化并不是在构造方法,而是在add方法里
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
1、无参构造
/**
* 共享的空数组实例,用于默认大小的空实例。
*我们将其与EMPTY_ELEMENTDATA区别开来,以了解添加第一个元素时需要多少空间
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* Constructs an empty list with an initial capacity of ten.
* 构造一个初始容量为10的空列表。(感觉这句注释并不准确。ArrayList在创建,未指定空间的话,默认是创 建一个空数组)
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
2、有参构造
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//传入值==0,则使用EMPTY_ELEMENTDATA,区分无参构造DEFAULTCAPACITY_EMPTY_ELEMENTDATA
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
3、传入某种类型的元素列表
//传入一个包含指定*集合元素的列表,然后迭代保证其元素的顺序
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
添加
1、add(E e)
//不带下标的添加,默认插入末尾
//调用上面的公共方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
2、添加的公共方法类(扩容相关)
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//得到最小扩容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//如果数组==空的数组,则比较默认容量与添加元素之和现在数组的长度+1,得较大的数返回
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
//判断是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);//扩容方法
}
//要分配的数组的最大大小。 一些虚拟机在数组中保留一些标题字。尝试分配更大的阵列可能会导致
//OutOfMemoryError:请求的阵列大小超出VM限制
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//ArrayList的扩容方法(重点!!!!)
//增加容量,以确保它至少可以容纳最小容量参数指定的*个元素。
//@param minCapacity所需的最小容量
private void grow(int minCapacity) {
// overflow-conscious code
//定义未添加数组的长度为旧值
int oldCapacity = elementData.length;
//定义旧值,jdk8版本之后位移运算1位后的值,也就是1.5倍的值为新值
//oldCapacity >> 1 == oldCapacity除以2
//注意区分下jdk的版本
int newCapacity = oldCapacity + (oldCapacity >> 1);
//新容量是否小于最小容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) `hugeCapacity()` 方法
//来比较 minCapacity 和 MAX_ARRAY_SIZE,
//如果minCapacity大于最大容量,则新容量则为`Integer.MAX_VALUE`,否则,
//新容量大小则为 MAX_ARRAY_SIZE 即为 `Integer.MAX_VALUE - 8`。
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
3、带下标的添加
public void add(int index, E element) {
//判断是否越界
rangeCheckForAdd(index);
//调用2的公共方法
ensureCapacityInternal(size + 1); // Increments modCount!!
//复制数组(也是arraylist不适合大规模数据增删改的原因)
//参数解析省略了,很简单
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
4、addAll(list)
首先将参数集合转为数组(list.toArray),然后和add(element)一样,然后源码使用了System.arraycopy进行数组拷贝。
5、addAll(index,list)
一样先判断索引是否越界,然后将参数集合转为数组,然后一样和add(element)一样,然后计算要移动的个数size-index,移动参数集合长度。如果需要移动的个数大于0,就使用System.arraycopy进行数组拷贝。移动完后再次调用System.arraycopy将参数数据添加进去。
删除
//删除对应下标元素,后续元素左移。涉及了数组拷贝
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;
}
Set(index,e)
一样校验索引是否越界,然后修改对应index的数据,返回旧的值。
Get(index)
一样校验索引是否越界,然后根据index,获取集合中的数据。
list里的toString方法
ArrayList继承AbstractList->继承AbstractCollection,
底层,先用迭代器判断有没有元素,没有就返回[],有就先用StringBuider拼接[,然后调用迭代器取值拼接,有元素拼接,空格,最后拼接],然后把整个缓冲区转为字符串。
迭代器遍历
Iterator先默认设置光标为0,然后判断光标是否等位集合大小,然后校验预期修改(expectedModCount)和实际修改(ModCount)是否相同,然后就会把光标+1,取出光标未+1时候的数组元素。
当remove的元素是倒数第二个的时候,使用list的remove不会出现报错异常,因为hashNext()下标=size,返回false
使用迭代器的remove的时候会(预期修改)expectedModCount=ModCount(实际修改)
Contains
底层就是一个一个遍历过去,找到相等的,没有找到就返回-1>0返回false,说明没找到。
isEmpty
根据size==0判断是否有元素。
总结
1.ArrayList频繁扩容导致添加性能急剧下降,如何处理?
通过构造方法指定初始容量的空列表,但是这样也会造成资源浪费。
2.ArrayList插入和删除元素一定比LinkedList慢么?不一定
ArrayList删除是数组的拷贝。
LinkedList是遍历链表从头或者尾巴开始,然后再删除节点。
3、ArrayList是线程不安全的(private成员变量会不安全、局部变量安全)。因为可能同时加到同一个位置。
1、加同步代码块,synchronized(this){}
2、lock锁
3 、同步方法synchronized
4.使用new Vector()类 add方法里有(synchronized)
5.推荐:Collections的synchronized()方法
6.读和写都安全的集合CopyOnwriteArrayList底层add采用的是(ReentrantLock)(读写分离,读和写不同的容器)
类似的还有CopyOnwriteArraySet它new CopyOnwriteArrayList
4、ArrayList和LinkList的区别
1.ArrayList是数组 LinkList是链表
2.随机访问get与set方面比ArrayList优于LinkList。
3.新增和删除操作,LinedList比较占优势
4.LinkedList比ArrayList更占内存,因为LinkedList的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。
5、ArrayList扩容过程?
就是先通过最小容量大于数组长度进入grow方法
1.得到当前的ArrayList的容量(oldCapacity)。
2.计算除扩容后的新容量(newCapacity),其值(oldCapacity + (oldCapacity >> 1))约是oldCapacity 的1.5倍。
3.当newCapacity小于所需最小容量,那么将所需最小容量赋值给newCapacity。
4.newCapacity大于ArrayList的所允许的最大容量,处理。
5.进行数据的复制,完成向ArrayList实例添加元素操作。