Learn && Live
虚度年华浮萍于世,勤学善思至死不渝
前言
Hey,欢迎阅读Connor学Java系列,这个系列记录了我的Java基础知识学习、复盘过程,欢迎各位大佬阅读斧正!原创不易,转载请注明出处:http://t.csdn.cn/pSqcY,话不多说我们马上开始!
1.Array和ArrayList的区别?什么时候更适合用Array?
(1)Array可容纳基本数据类型和对象,而ArrayList只能容纳对象
(2)Array大小是固定的,必须在声明时指定,而ArrayList大小是可变的
什么时候更适合用Array
(1)列表的大小确定,大部分情况下仅存储和遍历数据
(2)存储基本数据类型,相比集合需要自动装箱,节省性能
(3)多维数组
2.ArrayList实现RandomAccess接口有何作用?为何LinkedList没实现这个接口
public class ArrayList<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
public static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key) {
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
return Collections.indexedBinarySearch(list, key);
else
return Collections.iteratorBinarySearch(list, key);
}
private static <T> int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key) {
int low = 0;
int high = list.size()-1;
while (low <= high) {
int mid = (low + high) >>> 1;
Comparable<? super T> midVal = list.get(mid);
int cmp = midVal.compareTo(key);
if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found
}
private static <T> int iteratorBinarySearch(List<? extends Comparable<? super T>> list, T key) {
int low = 0;
int high = list.size()-1;
ListIterator<? extends Comparable<? super T>> i = list.listIterator();
while (low <= high) {
int mid = (low + high) >>> 1;
Comparable<? super T> midVal = get(i, mid);
int cmp = midVal.compareTo(key);
if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found
}
RandomAccess接口只是一个标志接口,只要List集合实现这个接口,就能支持快速随机访问
通过查看Collections类中的binarySearch方法,可以看出,判断List是否实现RandomAccess接口来分别调用indexedBinarySearch(list, key)或IteratorBinarySearch(list, key),实现Random接口的List集合采用for循环遍历,而未实现这个接口的则采用迭代器遍历,即ArrayList一般采用for循环遍历,而LinkedList一般采用迭代器遍历
因此我们在实际做项目时,应该考虑到List集合的不同子类应采用不同的遍历方式,从而提高性能
3.ArrayList和LinkedList的区别?
ArrayList源码
// JDK7
public class ArrayList<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
private static final long serialVersionUID = 8683452581122892189L;
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
// Object[]数组实现
private transient Object[] elementData;
private int size;
// 默认大小为10
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
// 空ArrayList
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;
}
...
}
LinkedLIst源码
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
transient int size = 0;
// 链表表项
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
/**
* 链表开头
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* 链表结尾
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
/**
* 添加表项,插入到链表的尾部
*
*/
public boolean add(E e) {
linkLast(e);
return true;
}
...
}
区别
(1)ArrayList和LinkedList的差别主要来自于两者采用的数据结构不同。
- ArrayList是基于数组实现的,默认初始化大小为10,大小不足时会调用grow方法扩容
- LinkedList是基于双向链表实现的,节点Node包含Item、prev、next,整个链表包含first、last,分别指向链表的头部、尾部
(2)ArrayList和LinkedList实现的接口不同。
- ArrayList实现了RandomAccess接口,支持快速随机访问,使用for循环遍历
- 而LinkedList没有实现,使用迭代器遍历。LinkedList实现了Deque接口,因此LinkedList可以作为双向队列、栈和List集合使用
(3)ArrayList和LinkedList增删查效率不同。
- ArrayList是基于索引的数据结构,使用索引在数组中搜索速度很快,可以直接返回指定index处的元素。但插入、删除数据的开销是很大的,因为这需要移动和复制原数组
- LinkedList在搜索时需要逐个遍历找到相应的位置,再返回内容。但在插入、删除数据时,不需要像ArrayList一样,不需要改变数组的大小,不需要移动和复制原数组
(4)ArrayList和LinkedList所需内存大小不同。
- ArrayList的每个索引的位置是实际的数据,所需内存较小
- LinkedList中的每个节点中存储的包含实际的数据、前驱和后驱,需要更多的内存
ArrayList的增删未必比LinkedList慢
(1)如果增删都在末尾操作,每次调用remove()和add(),此时ArrayList不需要移动和复制数组,如果数据量有百万级时,速度比LinkedList要快
(2)如果删除操作的位置在中间。由于LinkedList的消耗主要是在遍历上,ArrayList的消耗主要是在移动和复制上(arrayCopy),而LinkedList的遍历速度要慢于ArrayList的复制移动速度。故数据量在百万级时,还是ArrayList较快
补充:transient关键词的作用
被transient修饰的变量不参与序列化和反序列化。当一个对象被序列化的时候,transient型变量的值不包括在序列化的表示中,然而非transient型的变量是被包括进去的。
4.ArrayList的扩容机制?
先看源码:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// (1)
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;
}
(1)当使用add方法时首先调用ensureCapacityInternal方法,传入当前大小size + 1,检查是否需要扩容
(2)注释(1)处,newCapacity = oldCapacity + (oldCapacity >> 1),扩容为原大小的1.5倍,如果还不够,就是用它指定要扩容的大小minCapacity,然后判断minCapacity是否大于MAX_ARRAY_SIZE(Integer.MAX_VALUE - 8),如果大于,就取MAX_ARRAY_SIZE
(3)确定好新的长度后,即可调用Arrays.copyOf将原数组中的所有数据复制到扩容后的数组中