数据结构:用实例分析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; //数组大小 ...
随机推荐
- C++内存管理学习笔记(4)
/****************************************************************/ /* 学习是合作和分享式的! /* Auth ...
- springmvc 校验--JSR
1.使用JSR规范是简单使用的,如果使用hibernate校验则需要在工程中添加hibernate-validate.jar,以及其他依赖的jar包. 2,在mvc配置文件中使用<mvc:ann ...
- 小姐姐教你定制一个Logstash Java Filter
Logstash是用来收集数据,解析处理数据,最终输出数据到存储组件的处理引擎.数据处理流程为: Logstash Java Filter 就是基于Logstash的Filter扩展API开发一个用J ...
- jconsole+idea监控+(jvisualvm 本地内存分析)
1.idea启动配置 添加以下内容 -Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote -Dcom.sun.mana ...
- 00001- layui 表格的默认工具栏添加自定义按钮
首先定义table: var tableIns = table.render({ elem:'#businessUserListTable' ,url: ctx+'/business/business ...
- IDEA插件记录
IDEA个性化设置 1. 开发工具 Free MyBatis plugin 作用:可以快速的在mybatis 的mapper 文件和xml文件中快速切换 Lombok 作用:为POJO类添加@Data ...
- Spring 自动装配 byType
自动装配 byType,这种模式由属性类型指定自动装配. Spring 容器看作 beans,在 XML 配置文件中 beans 的 autowire 属性设置为 byType.然后,如果它的 typ ...
- SQL server数据库的密码策略与登录失败锁定策略
SQL server数据库本身没有密码复杂度策略设置,它是使用Windows操作系统的校验函数来校验账户密码的,所以查看SQL server数据库密码复杂度需要结合操作系统本地安全策略的密码策略来看. ...
- Spring笔记 - 组件注册
@Bean:类注入容器 xml方式: <bean id="person" class="com.hrh.bean.Person"> <prop ...
- Redis学习笔记(3)
一.Redis的事务(transaction) 1. 事务概念 本质:本质是一组命令的集合,所有的命令按照顺序一次性.串行化地执行,不允许其他命令的加入.Redis通过MULTI.EXEC.WATCH ...