ArrayList和LinkedList的源码学习,理解两者在插入、删除、和查找的性能差异
List的使用
List的子类
1). ArrayList
数据结构:数组
2). Vector
数据结构:数组
3). LinkedList
数据结构:循环双向链表
ArrayList 、Vector、LinkedList都来自AbstractList的实现,AbstratList直接实现了List接口并扩展自AbstactCollection。
一)、对ArrayList的 操作
== ArrayList的属性 ==
//默认容量
private static final int DEFAULT_CAPACITY = 10;
//空元素数据
private static final Object[] EMPTY_ELEMENTDATA = {};
//默认容量为空的空元素数据
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//底层结构
transient Object[] elementData;
//数组的大小
private int size;
== 创建ArrayList对象 ==
创建new ArrayList()时默认数组大小为 elementData = {}
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; //{}
}
== 对ArrayList进行add操作的源码剖析==
1). 首次添加元素时,给 elementData重新建立长度为10 的数组
2).添加元素时通过ensureCapacityInternal(size + 1)来判断内部容量是否需要扩容
I: 当内部容量需要扩容时,在原数组长度的基础上以1.5倍的规则进行扩展。
II: 通过System.arraycopy()将原数组的元素复制到新数组中。
3).将元素添加到尾部add(E e):
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(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);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
4).添加元素到指定位置add(int index, E element):
public void add(int index, E element) {
//范围检查
rangeCheckForAdd(index);
//是否进行扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
/**
以index为原数组elementData的起始位置,顺序获取size - index个元素,将这size - index个元素复制到以index+1为起始
位置的elementData数组中。
*/
System.arraycopy(elementData, index, elementData, index + 1,size - index);
elementData[index] = element;
size++;
}
添加元素的核心代码:
/**
以index为原数组elementData的起始位置,顺序获取size - index个元素,将这size - index个元素复制到以index+1为起始
位置的elementData数组中。
*/
System.arraycopy(elementData, index, elementData, index + 1,size - index);
== 删除指定位置的元素 remove(int index) ==
public class Test {
public static void main(String[] args) {
List list = new ArrayList();
list.add("a");
list.remove(0);
}
}
public E remove(int index) {
//范围查找
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
/**
将elementData数组的的元素从index+1,开始共复制numMove
个元素到elementData中从index开始为起始赋值位置
*/
System.arraycopy(elementData, index+1, elementData, index,numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
remove的核心代码:
/**
将elementData数组的的元素从index+1,开始共复制numMove
个元素到elementData中从index开始为起始赋值位置
*/
System.arraycopy(elementData, index+1, elementData, index,numMoved)
二)、对LinkedList进行操作
定义:一个LinkedList由多个表项连接而成,一个表项由3部分构成,前驱表项、内容、后驱表项。
== 向末尾添加元素add(E) ==
public class Test {
public static void main(String[] args) {
LinkedList list1 = new LinkedList();
list1.add("1");
}
}
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;
//判断链表是否为null,若链表为空则将当前新建表项设为第一个表项
if (l == null)
first = newNode;
//若最后一个表项不为空,则将该表项的next指针指向新的表项
else
l.next = newNode;
size++;
modCount++;
}
}
== 向指定位置插入元素add(int index, E e) ==
public class Test {
public static void main(String[] args) {
LinkedList<String> list1 = new LinkedList();
list1.add("1");
list1.add(0,"bb");
list1.add(0,"bb");
System.out.println(list1.size());//size = 3
}
}
public void add(int index, E element) {
//检查范围,判断需要插入的位置是否在size范围内
checkPositionIndex(index);
//末尾插入
if (index == size)
linkLast(element);
//在某个位置前插入
else
linkBefore(element, node(index));
}
//根据index查询对应的表项
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;
}
}
//改变链表的部分指向
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++;
}
结论:
1).当需要向List的末尾添加元素时,使用ArrayList的效率比LinkedList的效率
高,但总体速率相差不大。
原因:
i).使用LinkedList添加元素时每次都要new 一个Node对象,不间断的生成新
的对象占用了一定的系统资源。
ii).ArrayList只有在空间不足的情况下才会产生数组扩容和数组复制,所以
决定大部份的追加操作效率非常高。
2).操作List向指定位置添加元素时,若插入的位置在前半段或后半段部分使用
LinkedList进行插入,若插入位置在中间使用ArrayList进行插入性能较好,若
插入的数据在末尾 LinkedList和ArrayList的速率大致相同。
3). 当频繁对List进行删除和插入操作时使用LinkedList.
注:删除指定位置的元素原理和结论与在指定位置插入元素相同。
== 删除指定位置的元素remove(index) ==
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
//先找到对应的index对应的表项
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;
}
}
//修改链表的指向
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
三)、对List集合进行遍历操作
三种遍历方式:
List<String> list = new ArrayList();
List<String> list = new LinkedLisr();
//foEach;增强for循环
String temp = null;
for(String str : list){
temp = str;
}
//迭代器
for(Iterator it = list.Iterator() ; it.hasNext() ; ){
temp = it.next();
}
//for循环
for(int i = 0 ; i < list.size() ; i++){
temp = list.get(i)
}
结论:
1).使用迭代器遍历LinkedList和ArrayList列表时速度相同
2).使用forEach遍历LinkedList和ArrayList列表时速度相同
3). 迭代器遍历速度比forEach遍历的速度快
4).使用for循环时遍历list时ArrayList的速率远远的大于LinkedList。
原因:使用for循环遍历采用了随机访问机制。遍历LinkedList列表时,每次 get(int index),都要对列表进行一次遍历操作,查找index对应的表项,再取出表项对应的值,而ArrayList是基于数组结构的顺序存储,直接通过下标则可以获取元素。
遍列表的速率:
迭代器 > forEach > for循环
四)、为什么更简单的forEach遍历速率会低于迭代器遍历呢?
原因:通过反编译知道forEach的遍历是基于迭代器遍历而实现的
反编译的迭代器遍历:
for(Iterator it = list.Iterator() ; it.hasNext() ; ){
String s = it.next();
String s1 = s; //forEach遍历比迭代器遍历多了一步赋值操作
}
迭代器遍历:
for(Iterator it = list.Iterator() ; it.hasNext() ; ){
String s = it.next();
}
ArrayList和LinkedList的源码学习,理解两者在插入、删除、和查找的性能差异的更多相关文章
- [数据结构1.2-线性表] 动态数组ArrayList(.NET源码学习)
[数据结构1.2-线性表] 动态数组ArrayList(.NET源码学习) 在C#中,存在常见的九种集合类型:动态数组ArrayList.列表List.排序列表SortedList.哈希表HashTa ...
- [数据结构-线性表1.2] 链表与 LinkedList<T>(.NET 源码学习)
[数据结构-线性表1.2] 链表与 LinkedList<T> [注:本篇文章源码内容较少,分析度较浅,请酌情选择阅读] 关键词:链表(数据结构) C#中的链表(源码) 可空类 ...
- JDK源码学习LinkedList
LinkedList是List接口的子类,它底层数据结构是双向循环链表.LinkedList还实现了Deque接口(double-end-queue双端队列,线性collection,支持在两端插入和 ...
- JDK1.8源码学习-LinkedList
JDK1.8源码学习-LinkedList 目录 一.LinkedList简介 LinkedList是一个继承于AbstractSequentialList的双向链表,是可以在任意位置进行插入和移除操 ...
- JDK1.8源码学习-ArrayList
JDK1.8源码学习-ArrayList 目录 一.ArrayList简介 为了弥补普通数组无法自动扩容的不足,Java提供了集合类,其中ArrayList对数组进行了封装,使其可以自动的扩容或缩小长 ...
- Java集合专题总结(1):HashMap 和 HashTable 源码学习和面试总结
2017年的秋招彻底结束了,感觉Java上面的最常见的集合相关的问题就是hash--系列和一些常用并发集合和队列,堆等结合算法一起考察,不完全统计,本人经历:先后百度.唯品会.58同城.新浪微博.趣分 ...
- Spring 源码学习
spring最核心的理念是IOC,包括AOP也要屈居第二,那么IOC到底是什么呢,四个字,控制反转 一.什么是Ioc/DI? IoC 容器:最主要是完成了完成对象的创建和依赖的管理注入等等. 先从我们 ...
- JDK源码学习系列05----LinkedList
JDK源码学习系列05----LinkedList 1.LinkedList简介 LinkedList是基于双向链表实 ...
- Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签
写在前面 上文Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作中Spring对XML解析后创建了对应的Docum ...
随机推荐
- 自然语言处理(NLP)
苹果语音助手Siri的工作流程: 听 懂 思考 组织语言 回答 这其中每一步骤涉及的流程为: 语音识别 自然语言处理 - 语义分析 逻辑分析 - 结合业务场景与上下文 自然语言处理 - 分析结果生成自 ...
- 七、springBoot 简单优雅是实现文件上传和下载
前言 好久没有更新spring Boot 这个项目了.最近看了一下docker 的知识,后期打算将spring boot 和docker 结合起来.刚好最近有一个上传文件的工作呢,刚好就想起这个脚手架 ...
- 使用cordova + vue搭建混合app框架
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/zxj0904010228/article ...
- swift 手机号、邮箱、网址等正则表达式验证
看到一个不错的swift的 手机号.邮箱.网址等正则表达式验证,分享给大家. 支持swift3,经过修改后,亲测可用! import Foundation enum Validate { case e ...
- podman初试-和docker对比
podman初试-和docker对比 1,什么是docker? Docker 是一个开源的应用容器引擎,属于 Linux 容器的一种封装,Docker 提供简单易用的容器使用接口,让开发者可以打包他们 ...
- pyEcharts安装及使用指南
pyEcharts安装及使用指南 ECharts是一个纯Javascript的图表库,可以流畅的运行在PC和移动设备上,兼容当前绝大部分浏览器,底层依赖轻量级的Canvas类库ZRender,提供直观 ...
- 【Linux】【自学笔记】Linux下面docker安装mysql
写在前面: 捣腾继续,之前把一个SpringBoot的程序安装在docker上面,参考链接:https://www.cnblogs.com/aki-stones/p/2019-11-01-note.h ...
- 获得shell的几种姿势
windows提权 1.通过sqlmap连接mysql获取shell (1)直接连接数据库 sqlmap.py -d "mysql://root:123456@127.0.0.1:3306/ ...
- Weblogic wls9_async_response 反序列化远程命令执行漏洞(CVE-2019-2725)复现
一. 漏洞简介 漏洞编号和级别 CVE编号:CVE-2019-2725,危险级别:高危,CVSS分值:9.8. CNVD 编号:CNVD-C-2019-48814,CNVD对该漏洞的综合评级为 ...
- 大公司喜欢问的Java集合类面试题
Collection Collection是基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements).一些Collection允许相同的元素而另一 ...