java的list读写性能_数据结构:用实例分析ArrayList与LinkedList的读写性能

背景

ArrayList与LinkedList是Java编程中经常会用到的两种基本数据结构,在书本上一般会说明以下两个特点:

对于需要快速随机访问元素,应该使用ArrayList。

对于需要快速插入,删除元素,应该使用LinkedList。

该文通过实际的例子分析这两种数据的读写性能。

ArrayList

ArrayList是实现了基于动态数组的数据结构:

private static final int DEFAULT_CAPACITY = 10;

...

transient Object[] elementData;

...

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);

}

}

LinkedList

LinkedList是基于链表的数据结构。

private static class Node {

E item;

Node next;

Node prev;

Node(Node prev, E element, Node next) {

this.item = element;

this.next = next;

this.prev = prev;

}

}

...

transient Node first;

transient Node last;

...

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++;

}

实例分析

通过对两个数据结构分别增加、插入、遍历进行读写性能分析

1、增加数据

public class ArrayListAndLinkList {

public final static int COUNT=100000;

public static void main(String[] args) {

// ArrayList插入

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");

Long start = System.currentTimeMillis();

System.out.println("ArrayList插入开始时间:" + sdf.format(start));

ArrayList arrayList = new ArrayList<>();

for (int i = 0; i < COUNT; i++) {

arrayList.add(i);

}

Long end = System.currentTimeMillis();

System.out.println("ArrayList插入结束时间:" + sdf.format(end));

System.out.println("ArrayList插入" + (end - start) + "毫秒");

// LinkedList插入

start = System.currentTimeMillis();

System.out.println("LinkedList插入开始时间:" + sdf.format(start));

LinkedList linkedList = new LinkedList<>();

for (int i = 0; i < COUNT; i++) {

linkedList.add(i);

}

end = System.currentTimeMillis();

System.out.println("LinkedList插入结束时间:" + sdf.format(end));

System.out.println("LinkedList插入结束时间" + (end - start) + "毫秒");

}

}

输出如下:

两者写入的性能相差不大!

0f4f20df532ec84ea81fd5bcbc11c0cc.png

2、插入数据

在原有增加的数据上,在index:100的位置上再插入10万条数据。

public class ArrayListAndLinkList {

public final static int COUNT=100000;

public static void main(String[] args) {

// ArrayList插入

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");

Long start = System.currentTimeMillis();

System.out.println("ArrayList插入开始时间:" + sdf.format(start));

ArrayList arrayList = new ArrayList<>();

for (int i = 0; i < COUNT; i++) {

arrayList.add(i);

}

for (int i = 0; i < COUNT; i++) {

arrayList.add(100,i);

}

Long end = System.currentTimeMillis();

System.out.println("ArrayList插入结束时间:" + sdf.format(end));

System.out.println("ArrayList插入" + (end - start) + "毫秒");

// LinkedList插入

start = System.currentTimeMillis();

System.out.println("LinkedList插入开始时间:" + sdf.format(start));

LinkedList linkedList = new LinkedList<>();

for (int i = 0; i < COUNT; i++) {

linkedList.add(i);

}

for (int i = 0; i < COUNT; i++) {

linkedList.add(100,i);

}

end = System.currentTimeMillis();

System.out.println("LinkedList插入结束时间:" + sdf.format(end));

System.out.println("LinkedList插入结束时间" + (end - start) + "毫秒");

}

}

输出如下:

ArrayList的性能明显比LinkedList的性能差了很多。

e72516723c8e602888cff3761eaefbfd.png

看下原因:

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++;

}

ArrayList的插入原理:在index位置上插入后,在index后续的数据上需要做逐一复制。

9164a5d585d090d2f10654ba0775685e.png

LinkedList的插入源码:

public void add(int index, E element) {

checkPositionIndex(index);

if (index == size)

linkLast(element);

else

linkBefore(element, node(index));

}

...

void linkBefore(E e, Node succ) {

// assert succ != null;

final Node pred = succ.prev;

final Node newNode = new Node<>(pred, e, succ);

succ.prev = newNode;

if (pred == null)

first = newNode;

else

pred.next = newNode;

size++;

modCount++;

}

LinkedList的插入原理:在原来相互链接的两个节点(Node)断开,把新的结点插入到这两个节点中间,根本不存在复制这个过程。

003967855a70387d0dd17dc1cfe73269.png

3、遍历数据

在增加和插入的基础上,利用get方法进行遍历。

public class ArrayListAndLinkList {

public final static int COUNT=100000;

public static void main(String[] args) {

// ArrayList插入

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");

Long start = System.currentTimeMillis();

System.out.println("ArrayList插入开始时间:" + sdf.format(start));

ArrayList arrayList = new ArrayList<>();

for (int i = 0; i < COUNT; i++) {

arrayList.add(i);

}

for (int i = 0; i < COUNT; i++) {

arrayList.add(100,i);

}

Long end = System.currentTimeMillis();

System.out.println("ArrayList插入结束时间:" + sdf.format(end));

System.out.println("ArrayList插入" + (end - start) + "毫秒");

// LinkedList插入

start = System.currentTimeMillis();

System.out.println("LinkedList插入开始时间:" + sdf.format(start));

LinkedList linkedList = new LinkedList<>();

for (int i = 0; i < COUNT; i++) {

linkedList.add(i);

}

for (int i = 0; i < COUNT; i++) {

linkedList.add(100,i);

}

end = System.currentTimeMillis();

System.out.println("LinkedList插入结束时间:" + sdf.format(end));

System.out.println("LinkedList插入结束时间" + (end - start) + "毫秒");

// ArrayList遍历

start = System.currentTimeMillis();

System.out.println("ArrayList遍历开始时间:" + sdf.format(start));

for (int i = 0; i < 2*COUNT; i++) {

arrayList.get(i);

}

end = System.currentTimeMillis();

System.out.println("ArrayList遍历开始时间:" + sdf.format(end));

System.out.println("ArrayList遍历开始时间" + (end - start) + "毫秒");

// LinkedList遍历

start = System.currentTimeMillis();

System.out.println("LinkedList遍历开始时间:" + sdf.format(start));

for (int i = 0; i < 2*COUNT; i++) {

linkedList.get(i);

}

end = System.currentTimeMillis();

System.out.println("LinkedList遍历开始时间:" + sdf.format(end));

System.out.println("LinkedList遍历开始时间" + (end - start) + "毫秒");

}

}

输出如下:

fc0a2e1e6ba11ef0e7dc755c0f611580.png

两者的差异巨大:

我们看一下LInkedList的get方法:从头遍历或从尾部遍历结点

public E get(int index) {

checkElementIndex(index);

return node(index).item;

}

...

Node node(int index) {

// assert isElementIndex(index);

if (index < (size >> 1)) {

Node x = first;

for (int i = 0; i < index; i++)

x = x.next;

return x;

} else {

Node x = last;

for (int i = size - 1; i > index; i--)

x = x.prev;

return x;

}

}

3.1、LinkedList遍历改进

我们采用迭代器对LinkedList的遍历进行改进:

...

// LinkedList遍历

start = System.currentTimeMillis();

System.out.println("LinkedList遍历开始时间:" + sdf.format(start));

Iterator iterator = linkedList.iterator();

while(iterator.hasNext()){

iterator.next();

}

end = System.currentTimeMillis();

System.out.println("LinkedList遍历开始时间:" + sdf.format(end));

System.out.println("LinkedList遍历开始时间" + (end - start) + "毫秒");

再看下结果:

两者的遍历性能接近。

5d4e62634e95c4602e1479bad0bc7cf6.png

总结

List使用首选ArrayList。对于个别插入删除非常多的可以使用LinkedList。

LinkedList,遍历建议使用Iterator迭代器,尤其是数据量较大时LinkedList避免使用get遍历。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值