1.ArrayList 和 LinkedList优缺点
ArrayList:
优点:ArrayList 是实现了基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查询操作效率会比较高(在内存里是连着放的)。
缺点:因为地址连续,ArrayList 要移动数据,所以插入和删除操作效率比较低
LinkedList:
优点:LinkedList 基于双向链表的数据结构,地址是任意的,所以在开辟内存空间的时候不需要等一个连续的地址。
对于新增和删除操作,LinkedList 比较占优势。LinkedList 适用于要头尾操作或插入指定位置的场景。
缺点:因为 LinkedList 要移动指针,所以查询操作性能比较低。
2.ArrayList 和 LinkedList 的区别
1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
2.对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。
3.对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。
3.ArrayList 和 LinkedList的add(从尾部插入)方法
LinkedList:
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
LinkedList插入方法较简单,如果当前集合为空,就把新元素当成头节点,否则就让原来的尾节点指向新节点,size是集合元素数量/集合长度,modCount是集合总共修改次数。
ArrayList:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
ArrayList的插入方法分两步,首先确保内部容量(判断是否需要,如果需要扩容则扩容,否则不扩容),然后让将新元素赋值给集合最后一个元素的下一个元素。
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
没啥好说的,先看最里面的
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private static final int DEFAULT_CAPACITY = 10;
第一个重点来了,如果集合还没有元素(空集合),就返回新容量和默认容量中最大的那一个,否则直接返回新容量,所以ArrayList第一次方法扩容之后,容量为10,但是初始容量仍是0,这两点要区分开来。
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
也没啥好说的,直接看里面的扩容方法。overflow-conscious code下边再解释。
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);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
oldCapacity >> 1和oldCapacity /2是等价的(不考虑原始容量为负数且不能被2整除),所以ArrayList第二次及之后每次扩容是原始的1.5倍。hugeCapacity方法就是如果扩容后新容量接近了集合最大容量但没超出(Integer最大数-8),再给扩到Integer最大。Arrays.copyOf方法不多展开了,里面主要是调用native方法将原数组内容复制到新数组。
这里解释一下overflow-conscious code,先说结果,因为集合原始容量如果接近了Integer的最大值,扩容后就会溢出,newCapacity就会变成负数,newCapacity - minCapacity(极小负数-极大正数超过Integer下限)结果也会溢出变成正数,此时newCapacity为负,扩容失败。
4.ArrayList和LinkedList使用效率对比
查询效率肯定是ArrayList快,因为ArrayList是根据集合下标查询,LinkedList需要遍历。
但是插入效率就不一定了。
首先,如果是随机插入,肯定是LinkedList快,ArrayList慢,因为ArrayList需要移动元素。
但是实际业务场景中很多情况是使用add方法进行尾插入,尾插入的话两者效率看上去是一样的,但是也有不同。
LinkedList尾插就是每次new一个节点,然后插入。
ArrayList则多一个个扩容的判断,但是不需要new。
在数据量少的时候,ArrayList需要频繁扩容,插入效率肯定是LinkedList高,但是如果数据量大了,ArrayList只需要偶尔扩容,大部分情况是不需要扩容直接赋值,而LinkedList需要一直new一个新的节点,这时候就是ArrayList快,但是这样的扩容机制也造成了一定的空间浪费。
5.使用场景对比
当需要对数据进行对随机访问的时候,选用 ArrayList。
当需要对数据进行多次增加删除修改时,采用 LinkedList。
如果容量固定,并且只会添加到尾部,不会引起扩容,优先采用 ArrayList。
大多数情况下,使用ArrayList足矣,我所做的项目业务场景下List主要是用来承载Mysql查出来的列表数据,然后返回给前端或者foreach遍历或者stream的foreach遍历,将其中的元素取出进行处理后形成新的集合。这种情况下肯定是ArrayList,因为都是尾部插入且容量固定。