文章目录
ArrayList
ArrayList的继承实现关系图
1、ArrayList中的属性
ArrayList 底层数据结构就是⼀个 Object[] 数组,根据扩容机制可实现动态增长,因此也叫动态数组。
// ArrayList集合默认容量-->数组长度10
private static final int DEFAULT_CAPACITY = 10;
// 空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// ArrayList底层存储数据的结构Object[]数组
transient Object[] elementData; // non-private to simplify nested class access
// size是ArrayList集合中的数据个数
private int size;
2、ArrayList中的构造方法
当调用空参构造方法实例化一个ArrayList时、第⼀次添加元素(调⽤ add() ⽅法)时会根据扩容机制初始化其容量为10 。
// 空参构造方法
public ArrayList() {
// 给当前elementData赋值空数组,创建数组,但是长度为0
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 带参构造方法,initialCapacity初始容量,是给数组开辟空间
public ArrayList(int initialCapacity) {
// initialCapacity大于0
if (initialCapacity > 0) {
// 创建数组并开辟initialCapacity容量的空间大小
this.elementData = new Object[initialCapacity];
// initialCapacity等于0
} else if (initialCapacity == 0) {
// 给当前elementData赋值空数组,创建数组,但是长度为0
this.elementData = EMPTY_ELEMENTDATA;
} else {
// initialCapacity小于0则抛出异常
throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}
}
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();//将集合转化为数组
if ((size = elementData.length) != 0) {
// 判断数组的Class对象等不等于object[]的Class对象
if (elementData.getClass() != Object[].class)
//进行数组的复制
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
可以创建指定大小的数组,因为ArrayList 容量使⽤完后,会“⾃动”创建容量更⼤的数组,并将原数组中所有元素拷⻉过去,这会导致效率过低,可以使⽤构造⽅法 ArrayList (int capacity) 或 ensureCapacity(int capacity) 提供⼀个初始化容量,避免刚开始就⼀直扩容,造成效率较低
3、ArrayList的扩容机制
// 添加数据方法
public boolean add(E e) {
// 确保内部容量-->确保数组的长度是否可以继续添加数据
ensureCapacityInternal(size + 1); // Increments modCount!!
// 向数组中添加数据
elementData[size++] = e;// size++加加在后先赋值再运算
// 默认返回true
return true;
}
// 确保内部容量方法
private void ensureCapacityInternal(int minCapacity) {
// 判断elementData是否是空数组
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 如果是则更改最小容量,使用Math数学类的max方法获取两个值得最大值
//因为一开始是空数组,所以minCapacity是DEFAULT_CAPACITY.
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 确保显式容量方法,将DEFAULT_CAPACITY传进去
ensureExplicitCapacity(minCapacity);
}
// 确保显式容量方法
private void ensureExplicitCapacity(int minCapacity) {
// 修改次数
modCount++;
// 判断最小容量减去数组容量是否大于0如果大于则代表数组不能再添加数据了
// DEFAULT_CAPACITY-elementData.length>0
if (minCapacity - elementData.length > 0)
// 扩容方法,传进去的是DEFAULT_CAPACITY
grow(minCapacity);
}
// 数组的最大容量-->2147483647-8=2147483639
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
// 扩容方法
private void grow(int minCapacity) {
// 获取数组长度赋值给oldCapacity旧容量
int oldCapacity = elementData.length;
// 计算新容量-->使用旧容量+旧容量右移1位(等同于除于2)-->原容量的1.5倍
//初始ArrayList的oldCapacity 为0
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 扩容后的新容量还比需要的最小容量小,那么将minCapacity赋给newCapacity
//newCapacity - DEFAULT_CAPACITY < 0,newCapacity = DEFAULT_CAPACITY
if (newCapacity - minCapacity < 0)
// 将最小容量赋值给新容量
newCapacity = minCapacity;
// 最大长度2147483639
if (newCapacity - MAX_ARRAY_SIZE > 0)
// 如果超出最大长度2147483639则会用另外的方式扩容
newCapacity = hugeCapacity(minCapacity);
// 最终使用Arrays的copyOf方法扩容,创建新数组并将旧数组数据赋值给新数组,以及替换elementData数据
//经过第一次add()方法,ArrayList的容量变为10.
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
ArrayList优点:
- 存取有序、可重复可为null,动态数组,可改变数组大小
- 查询效率⾼
ArrayList缺点:
- 扩容会造成效率较低(可以通过指定初始化容量,在⼀定程度上对其进⾏改࠺)
- 另外数组⽆法存储⼤数据量(因为很ᵙ找到⼀块很⼤的连续的内存空间)
- 向 ArrayList 中添加/删除元素(add(int index)),需要移动元素,效率较低
LinkedList
LinkedList继承实现关系图
1、LinkedList中的属性
LinkedList中就三个有用属性:size、first、last
LinkedList 底层数据结构是⼀个双向链表,优点是增/删效率⾼,缺点是查询效率较低。
Linked中的构造方法
就两个构造方法,一个空参、一个有参。
linkedlist中的add方法与get方法:
// 添加数据方法
public boolean add(E e) {
// 向链表中最后一个添加数据
linkLast(e);
// 始终返回true
return true;
}
// 向链表中最后一个添加数据
void linkLast(E e) {
// 将LinkedList集合中的最后一个节点对象取出赋值给临时节点对象l
final Node<E> l = last;
// 创建新节点对象参数是(prev=l,item=e,next=null)
final Node<E> newNode = new Node<>(l, e, null);
// 将新创建的节点对象赋值给last,只要创建一个节点对象那么他肯定是最后一个节点(尾节点)
last = newNode;
// 判断原尾结点是否为null
if (l == null)
// 将新建节点对象赋值给first第一个节点对象(首节点)
first = newNode;
else
// 将l尾结点的next指向新建的节点对象(指向下一个节点对象)
l.next = newNode;
// 每次添加数据时size自增一,用于记录LinkedList集合的数据有多少
size++;
// 统计次数
modCount++;
}
// 通过下标索引获取元素
public E get(int index) {
// 检查元素下标索引是否合法
checkElementIndex(index);
// node方法就是获取下标索引数据
return node(index).item;
}
LinkedList底层使用链表结构(双链表)实现,双链表的有点是插入和删除数据速度快,但是查询速度慢(不可以指定下标索引查找)
双链表中所有的数据只要是查询就必须从头到尾的遍历一遍,所以会导致速度非常慢
LinkedList链表结构
总结
LinkedList与ArrayList区别:
相同点:
- 二者都是 List 接口的实现类,具有元素可重复,有序(存取顺序)特点;
- 二者都是线程不安全,效率高;
不同点:
- 数据结构:ArrayList底层数据结构是动态数组,LinkedList底层数据结构是双向链表;
- 随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。
- 增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高, 因为 ArrayList 增删操作要影响数组内的其他数据的下标。
综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList