实验
jdk版本1.8,测试平台mbp2016
在数据尾部插入数据
测试代码
package com.lly.springtest1.collection;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.LinkedList;
/**
* @ClassName ICollection
* @Description TODO
* @Author lly
* @Date 2019/3/5 9:29 AM
* @Version 1.0
**/
@Slf4j
public class ICollection {
public static void test() {
LinkedList links = new LinkedList<>();
int len = 6553631;
log.info(len + "");
long btime = System.currentTimeMillis();
for (int i = 0; i < len; i++) {
links.add("sss");
}
long etime = System.currentTimeMillis();
log.info("耗时:{},大小:{}", etime - btime, links.size());
long btime1 = System.currentTimeMillis();
ArrayList arrays = new ArrayList<>();
for (int i = 0; i < len; i++) {
arrays.add("sss");
}
long etime1 = System.currentTimeMillis();
log.info("耗时:{},大小:{}", etime1 - btime1, arrays.size());
}
public static void main(String[] args) {
ICollection.test();
}
}
复制代码
测试结果
此时我们的数量级别是百万级别,我们惊讶的发现ArrayList插入效率要比LinkedList快接近20倍,为什么?why?我们明明记得在学习java集合的时候,明确的知道是ArrayList查询快,增删慢的,LinkedList的特细则与之相反的,可是现实测试却跟定义不一样呢,那我们在多做点测试,改变数量级别看看十万,万,千级别的测试结果,我们改变插入集合的数据量大小测试。首先降低到十万级别 发现测试的插入效率已经很接近了
万级别 这时候我们发现和十万级别的测试结果差不多,还是ArrayList插入快一点
接着我们降低到千级别 此时两者的效率差不多,还是没有体现出书中定义的2者关于插入效率的问题,问题还是没有得到解决,那么我们思考一下,上面的实验我们都是默认插入的末尾的,是否跟我么插入的顺序有关系吗,我们下面测试一下将数据插入的头部
在数组头部插入数据
package com.lly.springtest1.collection;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.LinkedList;
/**
* @ClassName ICollection
* @Description TODO
* @Author lly
* @Date 2019/3/5 9:29 AM
* @Version 1.0
**/
@Slf4j
public class ICollection {
public static void test() {
LinkedList links = new LinkedList<>();
int len = 100000;
log.info(len + "");
long btime = System.currentTimeMillis();
for (int i = 0; i < len; i++) {
links.addFirst("sss");
}
long etime = System.currentTimeMillis();
log.info("耗时:{},LinkedList大小:{}", etime - btime, links.size());
long btime1 = System.currentTimeMillis();
ArrayList arrays = new ArrayList<>();
for (int i = 0; i < len; i++) {
arrays.add(0,"sss");
}
long etime1 = System.currentTimeMillis();
log.info("耗时:{},ArrayList大小:{}", etime1 - btime1, arrays.size());
}
public static void main(String[] args) {
ICollection.test();
}
}
复制代码
此时我们发现,在万级别LinkedList的插入性能就看出来了
此时我们发现,在十万级别LinkedList的插入性能是ArrayList的100+倍,那么我们下面再测试一下在数组中间部分插入数据
在数组中间插入数据
package com.lly.springtest1.collection;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.LinkedList;
/**
* @ClassName ICollection
* @Description TODO
* @Author lly
* @Date 2019/3/5 9:29 AM
* @Version 1.0
**/
@Slf4j
public class ICollection {
public static void test() {
LinkedList links = new LinkedList<>();
int len = 10000;
log.info(len + "");
long btime = System.currentTimeMillis();
for (int i = 0; i < len; i++) {
links.add(links.size() / 2, "sss");
}
long etime = System.currentTimeMillis();
log.info("耗时:{},LinkedList大小:{}", etime - btime, links.size());
long btime1 = System.currentTimeMillis();
ArrayList arrays = new ArrayList<>();
for (int i = 0; i < len; i++) {
arrays.add(arrays.size() / 2, "sss");
}
long etime1 = System.currentTimeMillis();
log.info("耗时:{},ArrayList大小:{}", etime1 - btime1, arrays.size());
}
public static void main(String[] args) {
ICollection.test();
}
}
复制代码
数据量万级别Linkedlist插入效率比ArrayList慢20倍
数据量十万级别Linkedlist插入效率比ArrayList慢160+倍下面让我系统测试一下其他数量级数据,并且加上随机插入测试
public static void test(int listSize, int type) {
LinkedList links = new LinkedList<>();
log.info("数组长度:{},插入方式{}", listSize, type);
long btime = System.currentTimeMillis();
for (int i = 0; i < listSize; i++) {
switch (type) {
case 0:
links.addFirst("测试数据");
break;
case 1:
links.add(links.size() / 2, "测试数据");
break;
case 2:
links.addLast("测试数据");
break;
default:
Random random = new Random();
int rd = random.nextInt(links.size()+1);
links.add(rd, "测试数据");
}
}
long etime = System.currentTimeMillis();
log.info("耗时:{}ms,LinkedList大小:{}", etime - btime, links.size());
long btime1 = System.currentTimeMillis();
ArrayList arrays = new ArrayList<>();
for (int i = 0; i < listSize; i++) {
switch (type) {
case 0:
arrays.add(0, "测试数据");
break;
case 1:
arrays.add(arrays.size() / 2, "测试数据");
break;
case 2:
arrays.add("测试数据");
break;
default:
Random random = new Random();
int rd = random.nextInt(arrays.size()+1);
arrays.add(rd, "测试数据");
}
}
long etime1 = System.currentTimeMillis();
log.info("耗时:{}ms,ArrayList大小:{}", etime1 - btime1, arrays.size());
}
复制代码
测试结果
-
在集合头部插入实验数据
-
在集合中间插入实验数据
-
在集合尾部插入实验数据
-
在集合随机位置插入数据
源码分析
在指定位置插入
LinkedList源码
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
复制代码
- 判断是否超过链表长度,超过则抛错误
- 如果是插入最后的位置直接使用linkLast方法而不必去遍历查询对应位置
- node方法寻找index所指向的Node,首先判断index是否大于size/2,大于则从末尾往前找,小于则从0开始往后找
- 找到之后就是new一个node对象,设置指针的问题
LinkedList:性能主要在于遍历链表查找index
ArrayList源码
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
复制代码
- rangeCheckForAdd判断index是否合法
- ensureCapacityInternal判断是否需要扩容
- arraycopy数组复制,从index开始把后面的数组元素全部复制到相对后一位的位置,该方法是native方法而且是连续内存复制问题,因此性能影响也没想象中的大
- elementData将element赋值给数组index元素
ArrayList:影响ArrayList性能的主要因素是扩容和数组复制,但是当size很大时数组扩容影响就会变小,那么此时的效率就会提升,此时如果在中间部分插入数据时候,我们要插入的位置为i,数组长度是n,那么就要变动i之后的n-i的数据。
在头部部插入
LinkedList源码
public void addFirst(E e) {
linkFirst(e);
}
private void linkFirst(E e) {
final Node f = first;
final Node newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
复制代码
可以看到LinkedList直接在头部时候不必遍历,所以效率很高,体现了LinkedList的插入效率高的特性
ArrayList源码解释同(指定位置插入)
在尾部插入
LinkedList源码
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node l = last;
final Node newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
复制代码
LinkedList:用传入的值new一个Node对象,然后尾指针指向该新的Node
ArrayList源码
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
复制代码
ArrayList:如果超出容量需要扩容,不需扩容时直接数组元素赋值
结论:当数据量越来越大时,ArrayList比LinkedList快
原因:当数据量大时,ArrayList每次扩容都能得到很大的新空间,解决了前期频繁扩容的劣势,而LinkedList虽然有尾指针,但是每次add都要将对象new成一个Node(而ArrayList直接数组对应位置元素赋值)
结论
数据量\插入位置 | 头部 | 中间 | 尾部 | 随机 |
---|---|---|---|---|
百 | 效率持平 | 效率持平 | 效率持平 | 效率持平 |
千 | LinkedList插入快 | 效率持平 | 效率持平 | ArrayList插入快 |
万 | LinkedList插入快 | ArrayList插入快 | 效率持平 | ArrayList插入快 |
十万 | LinkedList插入快 | ArrayList插入快 | ArrayList插入快 | ArrayList插入快 |
百万 | LinkedList插入快 | ArrayList插入快 | ArrayList插入快 | ArrayList插入快 |
- 在尾部插入数据,数据量较小时LinkedList比较快,因为ArrayList要频繁扩容,当数据量大时ArrayList比较快,因为ArrayList扩容是当前容量*1.5,大容量扩容一次就能提供很多空间,当ArrayList不需扩容时效率明显比LinkedList高,因为直接数组元素赋值不需new Node
- 在首部插入数据,LinkedList较快,因为LinkedList遍历插入位置花费时间很小,而ArrayList需要将原数组所有元素进行一次System.arraycopy
- 插入位置越往中间,LinkedList效率越低,因为它遍历获取插入位置是从两头往中间搜,index越往中间遍历越久,因此ArrayList的插入效率可能会比LinkedList高
- 插入位置越往后,ArrayList效率越高,因为数组需要复制后移的数据少了,那么System.arraycopy就快了,因此在首部插入数据LinkedList效率比ArrayList高,尾部插入数据ArrayList效率比LinkedList高
- inkedList可以实现队列,栈等数据结构,这是它的优势