源码分析二(ArrayList与LinkedList的区别)
一:首先看一下ArrayList类的结构体系:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
继承AbstractList抽象类,实现List接口、序列化接口等。
它的底层维护了一个Object[]数组,或者可以说它的底层数据结构是数组
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer.
*/
private transient Object[] elementData;
而LinkedList结构体系:
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
也是实现了List接口,但是它的底层数据结构表示数组,而是链式结构,内部维护一个相当于指针的Entry类
private transient Entry<E> header = new Entry<E>(null, null, null);
private transient int size = 0;
private static class Entry<E> {
E element;
Entry<E> next;
Entry<E> previous;
Entry(E element, Entry<E> next, Entry<E> previous) {
this.element = element;
this.next = next;
this.previous = previous;
}
}
这里暂且称呼它为指针,这个指针维护了三个成员变量,第一个是元素、后面两个是当前元素的前一个指针和后一个指针。
二:ArrayList读取数据效率高于LinkedList
直接上源码:
public E get(int index) {
RangeCheck(index);
return (E) elementData[index];
}
很简单,第一个校验参数,第二步根据索引直接到数组中查询元素返回,所以ArrayList读取数据的效率是固定的
校验参数的源码也贴一下,如下:
private void RangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: "+size);
}
如果指定索引的值大于等于集合的长度,那么会抛出角标越界异常。(注意:数组的最后一个元素index=size-1)
而LinkedList读取元素的方式:
public E get(int index) {
return entry(index).element;
}
根据指定index获取指针,因为指针中维护元素信息,那么咱们就来看一下指针的获取方式:
private Entry<E> entry(int index) {
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+size);
Entry<E> e = header;
if (index < (size >> 1)) {
for (int i = 0; i <= index; i++)
e = e.next;
} else {
for (int i = size; i > index; i--)
e = e.previous;
}
return e;
}
首先第一步还是参数校验,如果索引小于0或者大于等于size,抛空指针异常,然后从header指针开始寻找目标
这里注意一下,为了提高查找效率,有个判断size>>1 就是右移两位(除以2),意思就是索引的位置如果在前半段
就从前往后找,如果index的位置是后半段,就从后往前找,直到找到index索引位置结束,返回指针。因为指针中
只维护了它的前一个指针和后一个指针,所以只能一个一个的往前(后)找,不能绕过,所以效率自然就低于ArrayList。
三:ArrayList操作数据(增加或者删除)效率低于LinkedList
首先看ArrayList的add():
public boolean add(E e) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
如果仅仅只是add,那么很简单,第一步扩容,然后将新增的元素放到最后一个元素的后面,返回true。
但是如果将元素插入到指定index位置时,就有些麻烦了:
public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: "+size);
ensureCapacity(size+1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
第一步还是参数校验,索引位置不能小于0或者大于集合中元素数量,第二步扩容,然后将index位置包括以后元素
整体向后移动一个位,这样就把数组中index位置给空出来了,于是将element放到index为,集合中元素数量加一,
这里有移动数据的操作,所以效率比较低。
再来看一看ArrayList的remove操作,与add(int index,E element)的操作大致类似:
public E remove(int index) {
RangeCheck(index);
modCount++;
E oldValue = (E) elementData[index];
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // Let gc do its work
return oldValue;
}
第一步参数校验,index不能超过size,第二步获取index位置元素,暂且存储,然后将index+1以及后面的元素整体
向前移动一位,这样index位置的元素就被index+1给替换了,而size-1位置元素和size-2相等,需要置为null,等待垃圾回收器处理,
然后将index位置元素返回,这个过程又涉及到移动数组中的数据,所以效率比较低。
来看看LinkedList是怎么处理插入和删除的:
public void add(int index, E element) {
addBefore(element, (index==size ? header : entry(index)));
}
add(int index,E element)调用了addBefore这个方法,如果index==size,那么指针就是head,如果不是就会根据index
查找对应的指针,意思就是要将元素插入到哪个位置。
private Entry<E> addBefore(E e, Entry<E> entry) {
Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
newEntry.previous.next = newEntry;
newEntry.next.previous = newEntry;
size++;
modCount++;
return newEntry;
}
首先创建并初始化指针newEntry,将元素之e,以及它的前后指针维护到创建的指针对象中,然后与前后指针连接,就
相当于握手,集合中元素数量size自增,将指针元素返回。过程很简单,没有涉及到移动数据,只是将新增的元素与前后
元素握手就可以了,所以效率高于ArrayList。
再来看一看删除操作:
public E remove(int index) {
return remove(entry(index));
}
remove(int index)有个重载的方法入参为指针Entry,方法如下:
private E remove(Entry<E> e) {
if (e == header)
throw new NoSuchElementException();
E result = e.element;
e.previous.next = e.next;
e.next.previous = e.previous;
e.next = e.previous = null;
e.element = null;
size--;
modCount++;
return result;
}
首先暂存要删除的元素,然后将元素的后一个指针与前一个指针握手,再将该指针维护的前后位置元素置空,
然后返回要删除的元素,完成操作,过程没有涉及数据的移动,所以效率高于ArrayList。
源码分析二(ArrayList与LinkedList的区别)的更多相关文章
- 集合源码分析[3]-ArrayList 源码分析
历史文章: Collection 源码分析 AbstractList 源码分析 介绍 ArrayList是一个数组队列,相当于动态数组,与Java的数组对比,他的容量可以动态改变. 继承关系 Arra ...
- 框架-springmvc源码分析(二)
框架-springmvc源码分析(二) 参考: http://www.cnblogs.com/leftthen/p/5207787.html http://www.cnblogs.com/leftth ...
- 十、Spring之BeanFactory源码分析(二)
Spring之BeanFactory源码分析(二) 前言 在前面我们简单的分析了BeanFactory的结构,ListableBeanFactory,HierarchicalBeanFactory,A ...
- java读源码 之 list源码分析(ArrayList)---JDK1.8
java基础 之 list源码分析(ArrayList) ArrayList: 继承关系分析: public class ArrayList<E> extends AbstractList ...
- Fresco 源码分析(二) Fresco客户端与服务端交互(1) 解决遗留的Q1问题
4.2 Fresco客户端与服务端的交互(一) 解决Q1问题 从这篇博客开始,我们开始讨论客户端与服务端是如何交互的,这个交互的入口,我们从Q1问题入手(博客按照这样的问题入手,是因为当时我也是从这里 ...
- 【集合框架】JDK1.8源码分析之ArrayList详解(一)
[集合框架]JDK1.8源码分析之ArrayList详解(一) 一. 从ArrayList字表面推测 ArrayList类的命名是由Array和List单词组合而成,Array的中文意思是数组,Lis ...
- Tomcat源码分析二:先看看Tomcat的整体架构
Tomcat源码分析二:先看看Tomcat的整体架构 Tomcat架构图 我们先来看一张比较经典的Tomcat架构图: 从这张图中,我们可以看出Tomcat中含有Server.Service.Conn ...
- Vue源码分析(二) : Vue实例挂载
Vue源码分析(二) : Vue实例挂载 author: @TiffanysBear 实例挂载主要是 $mount 方法的实现,在 src/platforms/web/entry-runtime-wi ...
- 多线程之美8一 AbstractQueuedSynchronizer源码分析<二>
目录 AQS的源码分析 该篇主要分析AQS的ConditionObject,是AQS的内部类,实现等待通知机制. 1.条件队列 条件队列与AQS中的同步队列有所不同,结构图如下: 两者区别: 1.链表 ...
随机推荐
- 【进阶修炼】——改善C#程序质量(1)
这是一个大纲形式的概要,以便自己可以花较少的时间反复阅读.在开发中,多加注意这些有用的建议,让自己成为一个更优秀的程序员.内容主要来自<编写高质量代码-改善C#程序的157个建议>(陆敏技 ...
- module.inc 模块
/** *加载所有已经在系统表被启用的模块. *@参数$bootstrap *是否加载在“引导模式”缓存页面加载的模块只减少集.见bootstrap.inc文件. *@return *如果$ ...
- Docker run 命令的使用方法
[编者的话]在Docker中,run应该是用户使用最多的命令了,很多读者反馈不是很明白run命令的用法,而且相关的书籍.中文资料中对run命令的描述也不是非常完整,所以DockerOne组织翻译了Do ...
- Excel相同内容如何设置相同的背景色
有这样一个需求就是实现EXCEL的相同内容的背景色相同.并且内容不同的时候达到隔行变色的效果,记录下实现的效果,如果大家有什么更好的办法请给我指点一下.具体操作如下: 首先将是比较的列"20 ...
- 【转】jmeter 如何将上一个请求的结果作为下一个请求的参数——使用正则提取器
1.简介 Apache JMeter是Apache组织开发的基于Java的压力测试工具.用于对软件做压力测试,它最初被设计用于Web应用测试但后来扩展到其他测试领域. 它可以用于测试静态和动态资源例如 ...
- C# .NET - Sql Bulk Insert from multiple delimited Textfile using c#.net
SqlBulkCopy.WriteToServer has 4 overloads:SqlBulkCopy.WriteToServer (DataRow[]) Copies all rows f ...
- Self20171218_TestNG+Maven+IDEA环境搭建
前言: 主要进行TestNG测试环境的搭建 所需环境: 1.IDEA UItimate 2.JDK 3.Maven 一.创建工程 File –>new –>Project–>next ...
- mac下配置android环境变量
下面我将一下mac环境下的配置步骤: 1.在本地目录(home directory)中创建文件.bash_profile2.在文件中写入以下内容:export PATH=${PATH}:/Users/ ...
- hibernate+pageBean实现分页dao层功能代码
今天闲来无事,摆弄了一下分页,突然发现很多代码长时间不用就生梳了,虽然有些基础,但没有一篇整合的.这里还是简单示例,主要是以后自己翻着看不用百度找啊找找不到一篇想要的 1.PageBean实体类,一页 ...
- Eclipse/MyEclipse上配置Spring环境
在MyEclipse上配置Spring环境 myeclipse其实已经集成Spring的开发环境,我们只需在新建的项目上添加spring的配置环境就可以 新建一个java项目 选中创建好的项目之后,在 ...