ArrayList 动态数组,解决java数组的动态扩容的问题。这里面绝对是用数组来实现。
1、数组的类型是什么?
object[]
2、ArrayList的数组的最大极限是多少?
// 扩容的方法
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) throw new OutOfMemoryError();
// 扩容的最大边界值
// 1:防止栈溢出和越界 2:提前让你达到最大,减少扩容一次机会
// 这个时候你数量在集合中以及存储到极限值,任何一次数组迁移和复制都可能引发内存溢出。
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
初始化长度又是多少呢?
private static final int DEFAULT_CAPACITY = 10;
ArrayList的无参构造函数
无论new ArrayList() 还是new HashMap() 这种无参数的构造函数,都采用是一种:延迟加载的机制来完成初始化
如果是jdk1.8之前,确实 this.elementData = new Object[initialCapacity]; 并初始化10长度的数组
答案:为什么要这样做呢?怕你定义new ArrayList()不用,这10长度的数组空间就白白浪费在哪里
什么时候初始化?
在add()方法的时候
当然你也可以使用,有长度的构造函数
ArrayList arrayList = new ArrayList(10);
3、如何进行动态扩容的呢?扩容时机是什么?
add()时候,扩容的长度是index=10的时候进行一次扩容15,下次是index=16的时候,再下次是index = 24
1、底层是一个全局的数组结构进行元素的存储
2、在实现构造函数的,进行对象数组的初始化,初始化了一个并且指定定长的扩容。默认长度是:10
3、当调用add()方法添加元素的时候,如果超过固定长度10,就会调用grow方法进行扩容
ArrayList源码中的数组扩容的疑问
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
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);
}
这里的int newCapacity = oldCapacity + (oldCapacity >> 1);这句怎么理解?
------解决方案--------------------
int newCapacity = oldCapacity + (oldCapacity >> 1)
相当于int newCapacity = oldCapacity + (oldCapacity / 2)
也就是说newCapacity = oldCapacity * (1.5)
扩容50%的意思
4、关于数组的最大容量问题
如果newCapacity - MAX_ARRAY_SIZE > 0
就会调用hugeCapacity
, 判断传入参数minCapacity
的大小,当 minCapacity > MAX_ARRAY_SIZE
时, ArrayList
的最大容量就会设置为Integer.MAX_VALUE
。
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
5、关于删除元素
源代码实现:
public E remove(int index) {
rangeCheck(index);//先判断数组是否越界
modCount++;
E oldValue = elementData(index);
//处理数据
int numMoved = size - index - 1;
//remove方法是将原数组的指定下标位置后面的值复制好然后再覆盖原有的指定下标位置,再将最后的一个置为空方便gc
调用的system.arraytcopy
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
代码解释:
Object src : 原数组
int srcPos : 从元数据的起始位置开始
Object dest : 目标数组
int destPos : 目标数组的开始起始位置
int length : 要copy的数组的长度
如果一个arraylist集合有0,1,2,3,4,5的数据
然后 remove(3)从原数组的下标为4就是4开始,复制两个长度也就是4和5复制这两个,
接着 从目标数组开始(这里也是当前数组)的下标为3这里将其覆盖也就是变成0,1,2,4,5,5,
最后将最后一位置为null就变成0,1,2,4,5,null
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work,null等GC回收
return oldValue;
}
Arraylist和Linkedlist的区别
Arraylist
底层是基于动态数组
根据下标访问数组元素的效率高,向尾部添加元素的效率高
删除数组中元素以及向数组中间添加数据的效率低,因为需要移动数组
最坏情况是删除第一个元素,那就要把第2-第n个元素都往前移动一位
之所以称之为动态数组,是因为Arraylist在数组容量超过上限后可以扩容(JDK1.8之后数组扩容后是之前的1.5倍)
Arraylist源码中最大的数组容量是Integer.MAX_VALUE-8,对于空出的8位,目前解释是 :①存储Headerwords;②避免一些机器内存溢出,减少出错几率,所以少分配③最大还是能支持到Integer.MAX_VALUE(当Integer.MAX_VALUE-8依旧无法满足需求时)
Linkedlist
Linkedlist基于链表的动态数组
数据添加删除效率高,只需要改变指针指向即可,但是访问数据的平均效率低,需要对链表进行遍历。
总结
1、对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。
对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。
2、各自效率问题:
ArrayList是线性表(数组)
get()直接读取第几个下标,复杂度0(1)
add(E)添加元素,直接在后面添加,复杂度O ( 1 )
add(index, E)添加元素,在第几个元素后面插入,后面的元素需要向后移动,复杂度O (n)
remove ( )删除元素,后面的元素需要逐个移动,复杂度0O(n)
LinkedList是链表的操作
get()获取第几个元素,依次遍历,复杂度O(n)
add(E)添加到末尾,复杂度O(1)
add(index, E)添加第几个元素后,需要先查找到第几个元素,直接指针指向操作,复杂度O(n)
remove ( )删除元素,直接指针指向操作,复杂度O(1)