数据结构:用实例分析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> {
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;
}
}
...
transient Node<E> first;
transient Node<E> last;
...
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> 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<Integer> 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<Integer> 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) + "毫秒");
}
}
输出如下:
两者写入的性能相差不大!
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<Integer> 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<Integer> 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的性能差了很多。
看下原因:
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后续的数据上需要做逐一复制。
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<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
LinkedList的插入原理:在原来相互链接的两个节点(Node)断开,把新的结点插入到这两个节点中间,根本不存在复制这个过程。
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<Integer> 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<Integer> 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) + "毫秒");
}
}
输出如下:
两者的差异巨大:
我们看一下LInkedList的get方法:从头遍历或从尾部遍历结点
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
...
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;
}
}
3.1、LinkedList遍历改进
我们采用迭代器对LinkedList的遍历进行改进:
...
// LinkedList遍历
start = System.currentTimeMillis();
System.out.println("LinkedList遍历开始时间:" + sdf.format(start));
Iterator<Integer> iterator = linkedList.iterator();
while(iterator.hasNext()){
iterator.next();
}
end = System.currentTimeMillis();
System.out.println("LinkedList遍历开始时间:" + sdf.format(end));
System.out.println("LinkedList遍历开始时间" + (end - start) + "毫秒");
再看下结果:
两者的遍历性能接近。
总结
- List使用首选ArrayList。对于个别插入删除非常多的可以使用LinkedList。
- LinkedList,遍历建议使用Iterator迭代器,尤其是数据量较大时LinkedList避免使用get遍历。
数据结构:用实例分析ArrayList与LinkedList的读写性能的更多相关文章
- 请说出ArrayList,Vector, LinkedList的存储性能和特性
请说出ArrayList,Vector, LinkedList的存储性能和特性 解答:ArrayList和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都 ...
- [源码分析]ArrayList和LinkedList如何实现的?我看你还有机会!
文章已经收录在 Github.com/niumoo/JavaNotes ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教. 欢迎关注我的公众号,文章每周更新. 前言 说真的,在 Jav ...
- Arraylist、Linkedlist遍历方式性能分析
本文主要介绍ArrayList和LinkedList这两种list的常用循环遍历方式,各种方式的性能分析.熟悉java的知道,常用的list的遍历方式有以下几种: 1.for-each List< ...
- 说出ArrayList,Vector, LinkedList的存储性能和特性
ArrayList和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插 ...
- 【Java面试题】37 说出ArrayList,Vector, LinkedList的存储性能和特性
ArrayList和Vector都是使用数组方式存储数据,此 数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插 ...
- ArrayList,Vector, LinkedList的存储性能和特性
ArrayList和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入 ...
- ArrayList,Vector, LinkedList 的存储性能和特性
ArrayList 和Vector他们底层的实现都是一样的,都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内 ...
- Java中arraylist和linkedlist源代码分析与性能比較
Java中arraylist和linkedlist源代码分析与性能比較 1,简单介绍 在java开发中比較经常使用的数据结构是arraylist和linkedlist,本文主要从源代码角度分析arra ...
- 【Java】 ArrayList和LinkedList实现(简单手写)以及分析它们的区别
一.手写ArrayList public class ArrayList { private Object[] elementData; //底层数组 private int size; //数组大小 ...
随机推荐
- 加密base64
package com.lzkj.csp.bas.util; @SuppressWarnings("restriction") public class Base64Utils { ...
- Dynamics 365 联系人Contact的快速创建窗体,如何知道父窗体是哪个实体,通过window.top.parent.Xrm.Page.getUrl()可以知道父窗体的URL
Dynamics 365 联系人Contact的快速创建窗体,如何知道父窗体是哪个实体?相信有人会遇到过这种头疼的问题,我这里分享一种方式: 在contact快速创建窗体的onload时间执行如下代码 ...
- git推送代码问题之:ERROR: [abcdefg] missing Change-Id in commit message footer
一.问题: 在日常的工作中,使用git推送代码时会出现以下报错,“missing Change-Id in commit message” : qinjiaxi:$ git push origin H ...
- filebeat-kafka:WARN producer/broker/0 maximum request accumulated, waiting for space
You need to configure 3 things: Brokers Filebeat kafka output Consumer Here a example (change paths ...
- 萌新学习SpringMVC
前言 只有光头才能变强. 文本已收录至我的GitHub精选文章,欢迎Star:https://github.com/ZhongFuCheng3y/3y 这篇SpringMVC被催了很久了,这阵子由于做 ...
- vue的slot
1.明确一点:分发内容是在父作用域内编译: 2.slot作为备用内容的条件:宿主元素为空且父元素没有要分发的内容. 3.具名slot:<slot name="XXX"> ...
- Paxos made simple 翻译尝试
[这篇论文我翻译下来,首先感觉还是不好懂,很多地方结论的得出不够清楚,需要读者自己思考其中的原因.要理解Paxos算法,个人建议先搜索下介绍算法的中文文章,大致了解下Paxos算法要做什么,然后就再读 ...
- 王艳 201771010127《面向对象程序设计(java)》第十一周学习总结
一:理论部分. 1.数据结构:分为a.线性数据结构,如线性表.栈.队列.串.数组和文件. b.非线性数据结构,如树和图. 1)所有数据元素在同一个线性表中必须是相同的数据类型. 线性表按其存储结构可分 ...
- IDEA启动springboot项目找不到application.yml配置文件
idea启动项目时读取不到application-pro.yml文件,但是配置文件都在resource目录下: 解决:target/classes 目录是IDEA的classpath目录,项目编译后配 ...
- hdu6007 spfa+完全背包
题意:给你M,N,K,代表你有M点法力值,N个物品,K个制造方式 接下来N行,如果以1开头则代表既能卖又能合成,0代表只能卖. 然后K行,每行第一个数是要合成的东西,第二个数代表有几对,每对第一个数是 ...