对于广大java程序员来说,ArrayList的使用是非常广泛的,但是发现很多工作了好几年的程序员不知道底层是啥。。。这我觉得对于以后的发展是非常不利的,因为java中的每种数据结构的设计都是非常完善的,学习了这种思想,在设计自己的容器是非常有帮助的。

一、ArrayList底层结构

ArrayList的底层其实就是一个数组,数组的劣势想必也不用多说,一旦创建,长度无法更改。而ArrayList则可以不停的add或是remove,也可以称之为动态数组。

二、类定义以及成员变量

1、类定义

//1、ArrayList<E>说明此类是支持泛型的
2、继承AbstractList不用多说了,抽象父类中有许多子类可以共用的方法
3、此类实现了RandomAccess接口,说明支持快速随机存取,在实现了该接口的话,那么使用普通的for循环来遍历,性能更高,例如arrayList。而没有实现该接口的话,使用Iterator来迭代,这样性能更高,例如linkedList。
所以这个标记性只是为了让我们知道我们用什么样的方式去获取数据性能更好。
4、Cloneable、Serializable接口则代表可以使用Object.Clone()方法以及可以被序列化
5、这地方还有一个问题,就是ArrayList继承了AbstractList,为啥还要去实现List接口。开发这个的作者已经说了,这是一个错误。。。链接地址:
https://stackoverflow.com/questions/2165204/why-does-linkedhashsete-extend-hashsete-and-implement-sete
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable

2、成员变量

//当对象序列化之后,jvm通过这玩意来比对类的版本
private static final long serialVersionUID = 8683452581122892189L;
//默认容量大小为10
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;
    //数组最大容量 
  private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

3、构造函数

    //没有给定初始容量时,默认给10个长度
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//长度>0则按照给定长度创建;长度为0,则为默认空对象数组;<0自然报错了
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);
}
}
//给定collection时,转换成数组并赋值给elementData
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
//每个集合的toarray()的实现方法不一样,所以需要判断一下,如果不是Object[].class类型,那么就需要使用
ArrayList中的方法去改造一下。并且Arrays.copyOf是浅拷贝,当数组中存放的为对象,并且对象中的属性值有
改变,会影响到之前的数组
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}

三、主要方法

ArrayList中提供的add()方法有四种:

1、add(E)

    //直接在list的最后一位添加此元素
解释几个变量:
1、size:当前底层真正存储元素的个数
2、DEFAULT_CAPACITY:10,默认的容量大小
3、elementData.length:底层数组的长度。比如初始化20长度的list,但是里面只存放了5个元素。length为20,size则是5.
public boolean add(E e) {
//确定内部容量是否够了,size是数组中数据的个数,因为要添加一个元素,所以size+1,先判断size+1的这个个数数组能否放得下,就在这个方法中去判断是否数组.length是否够用了。
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData,
minCapacity));
}
//如果当前数组为空,则取minCapacity与10之间大的数为数组容量。如果底层数组不为空,则取minCapacity
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//当elementData为空时,并且minCapacity<10时,直接扩容到10
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
//如果所需的数组容量已经大于当前底层数组的长度,那么需要扩容。
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//真正的扩容模块
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
//将老容量扩充1.5倍,>>1也就是右移一位,也就是/2,不明白位运算的可以看本人位运算的一篇文章https://www.cnblogs.com/alimayun/p/10693358.html
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果扩容1.5倍还是小于minCapacity,那么以minCapacity为准
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果大于数组最大容量
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//最大就是给Integer.MAX_VALUE,否则就是MAX_ARRAY_SIZE=Integer.MAX_VALUE - 8。主要作用就是防止溢出
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

对上面的整个流程总结一遍吧,用中文来说:

先看下流程图

  • 当执行add(E)时,首先将minCapasize=size+1;
  • 当底层的elementData为空时,取minCapasize和10之间的最大值;若elementData不为空,则取minCapasize,将最终的结果赋值给minCapasize;
  • 判断此时minCapasize是否大于elementData.length,如果大于就开始扩容;
  • 获取当前的elementData.length,并扩容1.5倍,扩容完之后与minCapasize比较,取二者的最大值;
  • 获取到二者最大值后,判断该值是否大于MAX_ARRAY_SIZE=Integer.MAX_VALUE - 8,如果大于,要么取Integer.MAX_VALUE,要么取MAX_ARRAY_SIZE;
  • 通过Arrays.copyOf(elementData, newCapacity)方法,创建一个新长度的数组;
  • 将元素E放到size++的索引处,并返回true,结束整个流程

2、add(int index, E element)

    public void add(int index, E element) {
//判断下index是否符合规矩
rangeCheckForAdd(index);
//这个方法前面已经花了很多精力介绍了
ensureCapacityInternal(size + 1);
//将elementData在插入位置后的所有元素往后面移一位。
     //api:
     //public static void arraycopy(Object src,int srcPos,Object dest,int destPos,int length)
     //src:源对象
//srcPos:源对象对象的起始位置
//dest:目标对象
//destPost:目标对象的起始位置
//length:从起始位置往后复制的长度
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
//检查index是否合法
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

3、addAll(Collection)、addAll(index, Collection)

    //其实大体上与add()方法差不多
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew);
//通过上面确定elementData的长度之后,通过System.arraycopy()将c全部添加到原arrayList的后面,可以理解成追加吧
//关于System.arraycopy(),Arrays.copyOf()底层也是调用的这个方法,这是个native方法,所以也不用去关注它是咋实现的吧
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
} public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index); Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount int numMoved = size - index;
//如果index小于size的话,说明ArrayList原元素有一部分需要被覆盖掉
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved); System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}

4、get()、set()

    public E get(int index) {
//检查索引是否超过范围,如果index为负数,则由底层数组报ArrayIndexOutOfBoundsException
rangeCheck(index); return elementData(index);
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
E elementData(int index) {
//如果index为负数,则由底层数组报ArrayIndexOutOfBoundsException
return (E) elementData[index];
}
    public E set(int index, E element) {
//跟get()一样
rangeCheck(index);
//取出对应index位置的元素
E oldValue = elementData(index);
//替换成新元素
elementData[index] = element;
//返回老元素
return oldValue;
}

5、remove(index)、remove(object)、clear()

    public E remove(int index) {
rangeCheck(index); modCount++;
E oldValue = elementData(index); int numMoved = size - index - 1;
//假如当前lsit是这样{0,1,2,3}删除第二个位置的,那么第三个位置的元素需要往左移。当numMoved>0,说明需要移动
if (numMoved > 0)
//0到index的元素是不需要动的,index+1之后的需要移动
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work return oldValue;
} public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
//以index为分界线,0-index的元素不用动,后面的往左移动
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
public void clear() {
modCount++; // clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null; size = 0;
}

6、size()、empty()、contains()、indexOf()、lastIndexOf()、clone()

public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
//判断是否包含某个对象
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
//这方法很简单,没啥说的
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
//跟indexOf的区别就是一个从前面往后匹配,一个从后往前匹配
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
//克隆一个新的ArrayList
public Object clone() {
try {
//若想调用super.clone(),必须要实现cloneable接口
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}

总结:ArrayList是动态数组,优化了数组不能更改长度的缺点,而且继承了数组访问快的优点。缺点就是删除数据时(尤其删除index靠前位置的数据)比较慢。

删除操作时,需要将后面的元素移动空白位置,不慢就怪了。

另外提供了add、get、set等方法来操作数据。另外感兴趣的小伙伴可以自己看下ArrayList的迭代器Itr以及ListItr,这个在AbstractList中有介绍。

走进JDK(六)------ArrayList的更多相关文章

  1. 调试过程中发现按f5无法走进jdk源码

    debug 模式 ,在fis=new FileInputStream(file); 行打断点 调试过程中发现按f5无法走进jdk源码 package com.lzl.spring.test; impo ...

  2. 走进JDK(十)------HashMap

    有人说HashMap是jdk中最难的类,重要性不用多说了,敲过代码的应该都懂,那么一起啃下这个硬骨头吧!一.哈希表在了解HashMap之前,先看看啥是哈希表,首先回顾下数组以及链表数组:采用一段连续的 ...

  3. 走进JDK(八)------AbstractSet

    说完了list,再说说colletion另外一个重要的子集set,set里不允许有重复数据,但是不是无序的.先看下set的整个架构吧: 一.类定义 public abstract class Abst ...

  4. 走进JDK(二)------String

    本文基于java8. 基本概念: Jvm 内存中 String 的表示是采用 unicode 编码 UTF-8 是 Unicode 的实现方式之一 一.String定义 public final cl ...

  5. 走进JDK(七)------LinkedList

    要学习LinkedList,首先得了解链表结构.上篇介绍ArrayList的文章中介绍了底层是数组结构,查询快的问题,但是删除时,需要将删除位置后面的元素全部左移,因此效率比较低. 链表则是这种机制: ...

  6. 走进JDK(五)------AbstractList

    接下来的一段时间重点介绍java.util这个包中的内容,这个包厉害了,包含了collection与map,提供了集合.队列.映射等实现.一张图了解java中的集合类: AbstractList 一. ...

  7. 走进JDK(一)------Object

    阅读JDK源码也是一件非常重要的事情,尤其是使用频率最高的一些类,通过源码可以清晰的清楚其内部机制. 如何阅读jdk源码(基于java8)? 首先找到本地电脑中的jdk安装路径,例如我的就是E:\jd ...

  8. Java集合(六)--ArrayList、LinkedList和Vector对比

    在前两篇博客,学习了ArrayList和LinkedList的源码,地址在这: Java集合(五)--LinkedList源码解读 Java集合(四)--基于JDK1.8的ArrayList源码解读 ...

  9. 【JDK】ArrayList集合 源码阅读

    这是博主第二次读ArrayList 源码,第一次是在很久之前了,当时读起来有些费劲,记得那时候HashMap的源码还是哈希表+链表的数据结构. 时隔多年,再次阅读起来ArrayList感觉还蛮简单的, ...

随机推荐

  1. Linux集群之keepalive+Nginx

    集群从功能实现上分高可用和负载均衡: 高可用集群,即“HA"集群,也常称作“双机热备”. 当提供服务的机器宕机,备胎将接替继续提供服务: 实现高可用的开源软件有:heartbeat.keep ...

  2. Java笔试面试题整理第五波

    转载至:http://blog.csdn.net/shakespeare001/article/details/51321498 作者:山代王(开心阳) 本系列整理Java相关的笔试面试知识点,其他几 ...

  3. Bash常用快捷键及其作用

    在 Bash 中有非常多的快捷键,如果可以熟练地使用这些快捷键,可有效地提高我们的工作效率.只是快捷键相对较多,不太好记忆,这就要多加练习和使用.这些快捷键如表 1 所示. 表 1 Bash 常用快捷 ...

  4. leetcode3

    public class Solution { public int LengthOfLongestSubstring(string s) { var dic = new Dictionary< ...

  5. ExecutorService——shutdown方法和awaitTermination方法

    ExecutorService的关闭shutdown和awaitTermination为接口ExecutorService定义的两个方法,一般情况配合使用来关闭线程池. 方法简介shutdown方法: ...

  6. zabbix安装脚本

    #!/bin/bash # #安装zabbix源.aliyun YUM源 #curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyu ...

  7. 如何关闭wps热点,如何关闭wpscenter,如何关闭我的wps

    用wps已经快十年了,最开始的时候速度快,非常好用,甩office几条街,但最近这几年随着wps胃口越来越大,各种在线功能不断推出,植入广告越来越多,逐渐让人失去欢喜. 通过各种网帖的经验,我把网上流 ...

  8. SSM商城项目(五)

    1.   学习计划 1.前台系统搭建 2.商城首页展示 3.Cms系统的实现 a)         内容分类管理 b)         内容管理 4.前台内容动态展示 2.   商城首页展示 2.1. ...

  9. VideoView 监听视频格式不支持时的错误。

    视频播放格式不支持的处理https://www.cnblogs.com/ygj0930/p/7737209.html 不处理的情况下,默认会有弹框提示:不支持该视频格式. mVideoView.set ...

  10. python--第十三天总结(html ,css 语法)

    概述 HTML是英文Hyper Text Mark-up Language(超文本标记语言)的缩写,他是一种制作万维网页面标准语言(标记).相当于定义统一的一套规则,大家都来遵守他,这样就可以让浏览器 ...