说说JDK中的List-ArrayList、Vector、LinkedList
为方便开发人员,JDK提供了一套主要数据结构的实现,比如List、Map等。今儿说说List接口。
List接口的一些列实现中,最常用最重要的就是这三个:ArrayList、Vector、LinkedList。
JDK中这三个类的定义:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
从这三个类定义就可以看出一些信息:
- 三个都直接实现了AbstractList这个抽象类
- ArrayList和Vector都实现了RandomAccess接口,而LinkedList没有,这是什么意思呢?在JDK中,RandomAccess接口是一个空接口,所以它没有实际意义,就是一个标记,标记这个类支持快速随机访问,所以,arrayList和vector是支持随机访问的,但是LinkedList不支持
- serializbale接口表名,他们都支持序列化
下面详细说说这三个List实现。
这三个里面,ArrayList和Vector使用了数组的实现,相当于封装了对数组的操作。这也正是他们能够支持快速随机访问的原因,多说一句,JDK中所有基于数组实现的数据结构都能够支持快速随机访问。
ArrayList和Vector的实现上几乎都使用了相同的算法,他们的主要区别就是ArrayList没有对任何一个方法做同步,所以不是线程安全的;而Vector中大部分方法都做了线程同步,是线程安全的。
LinkedList使用的是双向循环链表的数据结构。由于是基于链表的,所以是没法实现随机访问的,只能顺序访问,这也正式它没有实现RandomAccess接口的原因。
正式由于ArrayList、Vector和LinkedList所采用的数据结构不同,注定他们适用的是完全不同的场景。
通过阅读这几个类的源码,我们可以看到他们实现的不同。ArrayList和Vector基本一样,我们就拿ArrayList和LinkedList做对比。
在末尾增加一个元素
ArrayList中的add方法实现如下:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
这个方法做两件事情,首先确保数组空间足够大,然后在数组末尾增加元素并且通过后++使得完成size+1。
从这个代码可以看出,如果数组空间足够大,那么只是数组的add操作就是O(1)的性能,非常高效。
在看看ensureCapacityInternal这个方法的实现:
private void ensureCapacityInternal(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
} private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
可以看出,如果数组空间不够,那么这个方法就会做数组扩容和数组复制操作,看第11行,JDK利用移位运算符进行扩容计算,>>1右移一位表示除2,所以newCapacity就是扩容为原来的1.5倍。
PS:这里的代码都是JDK1.7中的实现,JDK1.7对1.6的很多代码做了优化,比如上面这段扩容代码,在JDK1.6中第11行是直接除2,显然,移位运算要更高效。
在看看LinkedList中的add方法:
public boolean add(E e) {
linkLast(e);
return true;
} void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
从这段add代码可以看出,LinkedList由于使用了链表,所以不需要进行扩容,直接把元素加到链表最后,把新元素的前驱指向之前的last元素,并把last元素指向新元素就ok。这也是一个O(1)的性能。
测试一下:
public static void main(String[] args) {
// TODO Auto-generated method stub
long begin = System.currentTimeMillis(); // List<Object> list = new ArrayList<Object>();
List<Object> list = new LinkedList<Object>();
Object obj = new Object();
for(int i=0; i<50000; i++){
list.add(obj);
} long end = System.currentTimeMillis();
long time = end - begin;
System.out.println(time+""); }
分别对ArrayList和LinkedList做末尾add操作,循环50000次,ArrayList耗时6ms,而LinkedList耗时8ms,这是由于LinkedList在add时候需要更多的对象创建和赋值操作。
在任意位置插入元素
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++;
}
这段代码,首先先检查数组容量,容量不够先扩容,然后把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++;
}
这段代码,取到原先index处节点的前驱,变成新节点的前驱 ,同时把原先index变成新节点的后驱,这样就完成了新节点的插入。这个就是链表的优势,不存在数据复制操作,性能和在最后插入是一样的。
测试一种极端情况,每次都在最前端插入元素:
public static void main(String[] args) {
// TODO Auto-generated method stub
long begin = System.currentTimeMillis(); // List<Object> list = new ArrayList<Object>();
List<Object> list = new LinkedList<Object>();
Object obj = new Object();
for(int i=0; i<50000; i++){
list.add(0,obj);
} long end = System.currentTimeMillis();
long time = end - begin;
System.out.println(time+""); }
测试结果是:ArrayList耗时1400ms,而LinkedList只耗时12ms。可以看出,在随机插入的时候,两者的性能差异就很明显了。
小结一下,从上面的源码剖析和测试结果可以看出这三种List实现的一些典型适用场景,如果经常对数组做随机插入操作,特别是插入的比较靠前,那么LinkedList的性能优势就非常明显,而如果都只是末尾插入,则ArrayList更占据优势,如果需要线程安全,则非Vector莫属。
说说JDK中的List-ArrayList、Vector、LinkedList的更多相关文章
- ArrayList Vector LinkedList(一)
ArrayList Vector LinkedList 区别与用法 ArrayList 和Vector是采用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,都允许直接序号索引元素, ...
- paip.提升性能---list,arraylist,vector,linkedlist,map的选用..
paip.提升性能---list,arraylist,vector,linkedlist,map的选用.. arraylist,vector基本一样,但是,vector线程安全的. 作者Attilax ...
- 请说出ArrayList,Vector, LinkedList的存储性能和特性
请说出ArrayList,Vector, LinkedList的存储性能和特性 解答:ArrayList和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都 ...
- 集合中list、ArrayList、LinkedList、Vector的区别、Collection接口的共性方法以及数据结构的总结
List (链表|线性表) 特点: 接口,可存放重复元素,元素存取是有序的,允许在指定位置插入元素,并通过索引来访问元素 1.创建一个用指定可视行数初始化的新滚动列表.默认情况下,不允许进行多项选择. ...
- ArrayList Vector LinkedList 区别与用法
转载自: http://www.cnblogs.com/mgod/archive/2007/08/05/844011.html 最近用到了,所以依然是转载 ArrayList 和Vector是采用数组 ...
- Arraylist Vector Linkedlist区别和用法 (转)
ArrayList 和Vector是采用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,都允许直接序号索引元素,但是插入数据要设计到数组元素移动等内存操作,所以索引数据快插入数据慢 ...
- Java ArrayList Vector LinkedList Stack Hashtable等的差别与用法(转)
ArrayList 和Vector是采取数组体式格式存储数据,此数组元素数大于实际存储的数据以便增长和插入元素,都容许直接序号索引元素,然则插入数据要设计到数组元素移动等内存操纵,所以索引数据快插入数 ...
- 【转】ARRAYLIST VECTOR LINKEDLIST 区别与用法
转自:http://www.cnblogs.com/mgod/archive/2007/08/05/844011.html ArrayList 和Vector是采用数组方式存储数据,此数组元素数大于实 ...
- ArrayList,Vector,LinkedList
在java.util包中定义的类集框架其核心的组成接口有如下:·Collection接口:负责保存单值的最大父接口 |-List子接口:允许保存重复元素,数据的保存顺序就是数据的增加顺序: |-Set ...
- java集合(ArrayList,Vector,LinkedList,HashSet,TreeSet的功能详解)
说起集合,我们会潜意识里想到另外一个与之相近的名词——数组,OK!两者确实有相似之处,但也正是这点才是我们应该注意的地方,下面简单列出了两者的区别(具体功能的不同学习这篇文章后就会明白了): 数组 长 ...
随机推荐
- python 帮助和导入路径
1 help('**'),查看函数用法,其中**为内置的函数 help(**.**),模块.函数,查看非内置函数 2 先添加路径到sys,再导入 import sys;sys.path.append( ...
- 深度优先搜索入门:POJ1164城堡问题(递归、用栈模拟递归)
将问题的各状态之间的转移关系描述为一个图,则深度优先搜索遍历整个图的框架为:Dfs(v) {if( v 访问过)return;将v标记为访问过;对和v相邻的每个点u: Dfs(u);}int main ...
- HihoCoder 1075 开锁魔法III(概率DP+组合)
描述 一日,崔克茜来到小马镇表演魔法. 其中有一个节目是开锁咒:舞台上有 n 个盒子,每个盒子中有一把钥匙,对于每个盒子而言有且仅有一把钥匙能打开它.初始时,崔克茜将会随机地选择 k 个盒子用魔法将它 ...
- threeJs中旋转位移等操作坐标系
1.场景中的立方体.圆柱等三维模型的位置属性.平移方法参照Scene对象的世界坐标系 2.场景中立方体.圆柱等的三维模型的角度属性.旋转方法参照模型自身的模型坐标系
- Tornado之自定义异步非阻塞的服务器和客户端
一.自定义的异步非阻塞的客户端 #!/usr/bin/env python # -*- coding: utf8 -*- # __Author: "Skiler Hao" # da ...
- ALTERA的FPGA命名规则
DIP中文解释:双列直插式封装.插装型封装之一,引脚从封装两侧引出,封装材料有塑料和陶瓷两种.DIP是最普及的插装型封装,应用范围包括标准逻辑IC,存贮器LSI,微机电路等. PLCC中 ...
- nginx ngscript 简单使用
备注: 默认没有集成到nginx包里,需要单独安装(推荐使用动态模块的方式进行安装) 1. 安装 wget https://nginx.org/download/nginx-1.13.11.tar.g ...
- cocostudio ui编辑器 使用心得
1 c++包含路径 2九宫格设置 cocostudio ui编辑器设置九宫格x,y,w,h是从图片左上角开始测量,然后调整尺寸就行了. 2. 如果点了自适应 panel会在加载json的时候被设置 ...
- 用oradebug short_stack及strace -p分析oracle进程是否dead或出现故障
1,可以采用oradebug或者strace -p跟踪后台或前台进程是否dead或hang住2,如果进程出现故障,必会在对应的TRC文件写入最新信息,基于此可以获取非常重要的信息进一步分析与诊断 ...
- hdu 1724 Ellipse——辛普森积分
题目:http://acm.hdu.edu.cn/showproblem.php?pid=1724 #include<cstdio> #include<cstring> #in ...