我们在学习这一块内容时需要注意的一个问题是 集合中存放的依然是对象的引用而不是对象本身。

List接口扩展了Collection并声明存储一系列元素的类集的特性。使用一个基于零的下标,元素可以通过它们在列表中的位置被插入和访问。一个列表可以包含重复元素。List在集合中是一个比较重要的知识点也是在开发中最常用的。

我们都知道ArrayList是由数组实现的,但是和数组有很大区别的是随着向ArrayList中不断添加元素,其容量也自动增长,而数组声明好之后其容量就不会改变。想要探明其中的究竟探析其中的原理十分重要,今天重新看了一下这块的源代码(JDK1.8.0_92)感觉很有收获,所以在此记录和分享。

1.Arraylist类中的属性

 public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L; /**
*默认初始容量
*/
private static final int DEFAULT_CAPACITY = 10; /**
*被用于空实例的共享空数组实例
*/
private static final Object[] EMPTY_ELEMENTDATA = {}; /**
* Object[]类型的数组,保存了添加到ArrayList中的元素。ArrayList的容量是该Object[]类型数组的长度
* 当第一个元素被添加时,任何空ArrayList中的elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA将会被
* 扩充到DEFAULT_CAPACITY(默认容量)。
*/
private transient Object[] elementData; /**
* ArrayList的大小(其实就是size()方法返回的那个值)
*
* @serial
*/
private int size; ...... }

类属性

在这里需要注意的有几点:

DEFAULT_CAPACITY 这个变量指的是ArrayList默认的容量,其实刚刚初始化一个Arraylist其容量是0,当添加一个之后容量就变成了10,在jdk1.6版本的时候还不是这么处理的。接下来会一一介绍。

elementData 这个变量是一个数组,在JDK1.8.0_92的源代码的注解中很清晰的说明了这个数组是用来缓存ArrayList里的数据(这里的数据指的是对象的引用),ArrayList的大小取决于这个缓存数组的长度,还指明了一点就是在初始化的时候这个缓存数组的是一个空数组当第一次添加的时候会把这个缓存数组的长度扩展为上面的DEFAULT_CAPACITY也就是10。

size 这个变量就指的是缓存数组的大小也就是ArrayList的长度。

2.构造方法

 //带参数的构造方法 参数时ArrayList的初始长度
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
//不带参数的构造方法(初始化的长度为0) 也是我们最常用的构造方法
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;
}
//带参数的构造方法 构造一个包含指定collection的元素的列表,这些元素按照该collection的迭代器返回的顺序排列的
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}

在这里需要注意的是,在不同版本的jdk中此处的实现机制略有不同。如下:

在jdk1.8.0_45中不带参数的构造方法:

 public ArrayList() {
  this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; //DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}
}

在JDK1.6中不带参数的构造方法:

 public ArrayList() {
this(10); //public ArrayList(int initialCapacity)中this.elementData = new Object[initialCapacity];
}

在JDK1.8.0_92和jdk1.8.0_45中代码虽然略微有些不同但是他们的实现机制是一样的,都是刚开始的声明一个空数组是在添加的时候才把数组的长度扩展为10,而在JDK1.6时在刚刚声明的时候就声明的长度为10的数组。这也是慢慢在做优化吧。

具体是在什么时候数组长度进行扩展的我们在下边看

3.添加元素

     //将指定的元素(E e)添加到此列表的尾部
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
} //将指定的元素(E e)插入到列表的指定位置(index)
public void add(int index, E element) {
rangeCheckForAdd(index); //判断参数index是否IndexOutOfBoundsException ensureCapacityInternal(size + 1); // Increments modCount!! 如果数组长度不足,将进行扩容
System.arraycopy(elementData, index, elementData, index + 1,
size - index); //将源数组中从index位置开始后的size-index个元素统一后移一位
elementData[index] = element;
size++;
} /**
* 按照指定collection的迭代器所返回的元素顺序,将该collection中的所有元素添加到此列表的尾部
* @throws NullPointerException if the specified collection is null
*/
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
//将数组a[0,...,numNew-1]复制到数组elementData[size,...,size+numNew-1]
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
} /**
* 从指定的位置开始,将指定collection中的所有元素插入到此列表中,新元素的顺序为指定collection的迭代器所返回的元素顺序
* @throws IndexOutOfBoundsException {@inheritDoc}
* @throws NullPointerException if the specified collection is null
*/
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index); //判断参数index是否IndexOutOfBoundsException Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount int numMoved = size - index;
if (numMoved > 0)
//先将数组elementData[index,...,index+numMoved-1]复制到elementData[index+numMoved,...,index+2*numMoved-1]
//即,将源数组中从index位置开始的后numMoved个元素统一后移numNew位
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
//再将数组a[0,...,numNew-1]复制到数组elementData[index,...,index+numNew-1]
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}

上面几个添加方法中具体的实现方法没有在上边列出来我放在下边

具体的实现方法:

      /**
* public方法,让用户能手动设置ArrayList的容量
* @param minCapacity 期望的最小容量
*/
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY; if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
} private void ensureCapacityInternal(int minCapacity) {
//当elementData为空时,ArrayList的初始容量最小为DEFAULT_CAPACITY(10)
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);
} //数组可被分配的最大容量;当需要的数组尺寸超过VM的限制时,可能导致OutOfMemoryError
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; /**
* 增加数组的容量,确保它至少能容纳指定的最小容量的元素量
* @param minCapacity 期望的最小容量
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//注意此处扩充capacity的方式是将其向右移一位再加上原来的数,实际上是扩充了1.5倍
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;
}

在这里值得注意的是grow()方法中的>> 1,其实该运算就是相当于除以2.例如:

 1010      十进制:10     原始数         number
10100 十进制:20 左移一位 number = number << 1;
1010 十进制:10 右移一位 number = number >> 1;

所以我们跟进 add(E e)方法就会知道当对ArrayList进行add操作时,最开始的时候进入add()方法,然后执行ensureCapacityInternal()方法,继续跟进在这里elementData == EMPTY_ELEMENTDATA为true所以minCapacity = 10。然后minCapacity - elementData.length > 0为true,执行grow()方法,每次执行grow()方法在一般情况下都会生成一个新数组且长度是原数组长度的1.5倍,但是有两种情况除外第一点就是当新生成的数组长度小于10时 那么将新生成的数组长度改为10,也就是grow()方法第一个if语句里的代码做的事情(就是保证了在添加元素小于8个的时候ArrayList的长度都是10),第二点就是当新生成的数组长度大于Integer.MAX_VALUE - 8时会对数组长度进行调整避免越界(这就是grow()方法第二个if语句里的代码做的事情)。这就是ArrayList容量自动扩充的基本原理。

我之前看过jdk1.6在此处的实现方式,略有不同但是也是生成新数组且长度是原数组的1.5倍。至于为什么是1.5倍我觉得可能是那些大牛以自己的经验或是经过科学的推导才得到这么一个最优解,我也不知道了 ,猜的 欢迎补充。

就介绍这么多吧,ArrayList里还有很多方法像删除元素,修改元素还有查找元素等等,就不在这里都写出来了。

我们大致了解了ArrayList的基本原理然后再去想ArrayList的特点就比较容易懂了。相对于LinkedList,ArrayList比较适用于搜索操作,LinkedList比较适用于插入或是删除操作。因为ArrayList适用数组实现的,在内存中所存储的数据是连续的所以很容易从一个元素定位到另一个元素,而LinkedList就不同了,LinkedList适用双链表实现的他就必须挨个一个一个的看是不是要找的元素,但是LinkedList在执行中间插入或是中间删除操作时效率是很高的。

还有在eclipse中查看源代码比较常用的快捷键是Alt + 左右方向键,可以实现在之前鼠标光标处跳转 非常实用。

欢迎大家查补缺漏!

javase基础回顾(一)ArrayList深入解析 解读ArrayList源代码(JDK1.8.0_92)的更多相关文章

  1. javase基础回顾(二)LinkedList需要注意的知识点 阅读源码收获

    我们在学习这一块内容时需要注意的一个问题是 集合中存放的依然是对象的引用而不是对象本身. List接口扩展了Collection并声明存储一系列元素的类集的特性.使用一个基于零的下标,元素可以通过它们 ...

  2. javase基础回顾(四) 自定义注解与反射

    本篇文章将从元注解.自定义注解的格式.自定义注解与反射结合的简单范例.以及自定义注解的应用来说一说java中的自定义注解. 一.元注解 元注解也就是注解其他注解(自定义注解)的java原生的注解,Ja ...

  3. javase基础回顾(三) 动态代理

    动态代理是大型框架中经常用到的经典的技术之一,博主在理解spring的控制反转(依赖注入)的思想时回头着重复习了一下java的动态代理. 在说动态代理之前我们先简单说一说代理是用来干什么的,用于什么样 ...

  4. javaSE基础之 ArrayList的底层简单实现

    最近就是想扒一扒存在硬盘里面的学习资料(突然想到什么),把以前写过的一些东西整理一下分享出来. 这边是ArrayList 的简单实现,当然只实现了部分方法 package com.yck.collec ...

  5. ArrayList、Vector和LinkedList等的差别与用法(基础回顾)

    ArrayList 和Vector是采取数组体式格式存储数据,此数组元素数大于实际存储的数据以便增长和插入元素,都容许直接序号索引元素,然则插入数据要设计到数组元素移动等内存操纵,所以索引数据快插入数 ...

  6. JAVASE笔记回顾

    第一部分,JAVA基础和面向对象 part01 入门与开发环境搭建 1: 计算机基础知识(了解)(1)计算机(2)计算机硬件(3)计算机软件系统软件:windows,linux,mac应用软件:QQ, ...

  7. 基础1 JavaSe基础

    JavaSe基础 1. 九种基本数据类型的大小,以及他们的封装类 boolean 无明确指定 Boolean char 16bits Character byte 8bits Byte short 1 ...

  8. javaSE基础04

    javaSE基础04 一.三木运算符 <表达式1> ? <表达式2> : <表达式3> "?"运算符的含义是: 先求表达式1的值, 如果为真, ...

  9. 1、java基础回顾与加强

    一.    基础回顾 1   集合 1.1  集合的类型与各自的特性 ---|Collection: 单列集合 ---|List: 有存储顺序, 可重复 ---|ArrayList:    数组实现, ...

随机推荐

  1. iOS开发——导入第三方库引起的unknown type name 'NSString'

    今天加入SVProgressHUD的第三方库的时候报了24个错误( too many errors emitted, stopping now),都是 expected identifier or ' ...

  2. HDU 3501 Calculation 2 ——Dirichlet积

    [题目分析] 卷积太有趣了. 最终得出结论,互质数和为n*phi(n)/2即可. 计算(n*(n+1)/2-n-n*phi(n)/2)%md,用反正法即可证明. [代码] #include <c ...

  3. Unknown/unsupported storage engine: InnoDB

    症状:无法启动mysql,在“mysql数据库目录/主机名.err”日志文件中报错 Unknown/unsupported storage engine: InnoDB原因:MySQL5.5.8 GA ...

  4. Thinking in scala (6)----高阶函数----返回一个函数

    在Thinking in scala (5)----高阶函数* 里面,我们演示了如何把一个函数作为参数传递给另外一个函数. 在本文里面,我们来演示函数式编程另外一个重要的特性:返回一个函数.首先来看这 ...

  5. web前端好学吗?

    最近这段时间许多学生讨论关于WEB前端工程师这个职位的问题.比如:关于前端难不难?好不好找工作?有没有用?好不好学?待遇好不好?好不好转其他的职位? 针对这个问题,课工场露露老师想跟大家谈谈自己对前端 ...

  6. 升级R版本后,更新Package

    升级R版本后,若重新安装所有的package将非常麻烦,可以尝试运行一下程序: 1)在旧版本中的R中运行 #--run in the old version of R setwd("C:/T ...

  7. freemarker配置,使用

    最近在项目中用到freemarker,总是报一些莫名其妙的错误. 调查得知是由于在配置文件中属性[tag_syntax]的设置问题,我们的环境下该属性(auto_detect)默认设置了自动检测,也就 ...

  8. HTML5定稿:手机App将三年内消失,互联网世界的第二次大战

    HTML5与app以对立竞争的产品形态展现在大众视野.从去年开始又有一大批技术派或者创业者盯向html5领域,移动游戏的爆发和微信朋友圈等众多平台为HTML5导流,能不能颠覆,或许只是时间上的问题. ...

  9. LNMP 快速安装

    网址是:http://lnmp.org/install.html 值得注意的是:它基本上把所有的扩展都装上了,有点冗余 安装完成后,根目录的位置是 /home/wwwroot/default LNMP ...

  10. Typescript中的Class

    let btn = document.createElement('button'); btn.textContent = 'Say Hello'; document.body.appendChild ...