ArrayList:
特点:
基于动态数组,连续内存存储,数组通过角标进行随机访问的时间复杂度为O(1),CPU的内部缓存结构会缓存连续的内存片段,可以大幅降低读取内存的性能开销。因为数组中存放到数据类型一样,所以数据占据的大小相同(偏移量)
扩容机制:因为数组长度固定,超出长度存数据时需要新建数组,每次扩容为原先数组长度的1.5倍,然后将老数组的数据拷贝到新数组,如果不是尾部插入数据还会涉及到元素的移动(往后复制一份,插入新元素),使用尾插法并指定初始容量可以极大提升性能、甚至超过linkedList (需要创建大量的node对象)
问题:ArrayList是有序的,插入数据的顺序和遍历是数据是一致的,为什么还说在数组内部插入数据慢呢?
因为:当指定位置插入时,后面的数据会逐个往后移动
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<String>();
arrayList.add("hello1");
arrayList.add("hello2");
arrayList.add("hello3");
arrayList.add(3,"hello4");
arrayList.add(3,"hello5");
arrayList.add(3,"caibi");
Iterator<String> ite=arrayList.iterator();
while(ite.hasNext())//判断下一个元素之后有值
{
System.out.println(ite.next());
}
}
/*
结果:
hello1
hello2
hello3
caibi
hello5
hello4
ArrayList插入默认为尾插法,慢的原因还有在数组中间删除一个元素时,后面的元素会逐个前移,而且进行扩容的时候也很慢,这才是是ArrayList在更新慢的原因。
当创建ArrayList数组时,可以指定初始数组长度:
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
未指定是默认如下:
JDK1.7时默认动态数组的长度是10
JDK1.8时默认动态数组的长度为0,当执行第一次添加操作时扩容为10(懒加载);
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
对于DEFAULTCAPACITY_EMPTY_ELEMENTDATA的源码解释:
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
LinkedList:
特点:
基于链表,可以存储在分散的内存中,适合做数据插入及删除操作,插入的时间复杂度几乎为O(1),不适合查询:需要逐一遍历遍历LinkedList必须使用iterator不能使用for循环,因为每次for循环体内通过get()取得某一 元素时都需要对list重新进行遍历,性能消耗极大。另外不要试图使用indexOf等返回元素索引,并利用其进行遍历,使用indexIOf对list进行 了遍历,当结果为空时会遍历整个列表。
indexOf源码如下:
public int indexOf(Object o) {
int index = 0;
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}
为什么不让使用for循环遍历LinkedList呢
举例:
public static void main(String[] args) {
List<Integer> list = new LinkedList<Integer>();
for (int i = 0; i < 10; i++) {
Random r = new Random();
Integer tmp = r.nextInt();
list.add(tmp);
}
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
for (Integer i : list) {
System.out.println(i);
}
Iterator<Integer> iter = list.iterator();
while (iter.hasNext()) {
System.out.println(iter.next());
}
}
因为for需要内get方法的问题:
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
get方法调用了node方法
node方法每次都会进行一次全局遍历,性能非常低:
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
原因就在两个for循环里面,以前者为例:
1、get(0),直接拿到0位的Node0的地址,拿到Node0里面的数据
2、get(1),直接拿到0位的Node0的地址,从0位的Node0中找到下一个1位的Node1的地址,找到Node1,拿到Node1里面的数据
3、get(2),直接拿到0位的Node0的地址,从0位的Node0中找到下一个1位的Node1的地址,找到Node1,从1位的Node1中找到下一个2位的Node2的地址,找到Node2,拿到Node2里面的数据。
......
所以尽量不要用