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

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. 学习c++语言应该牢记的50条准则,同样学习其他语言也一样

    1.把C++当成一门新的语言学习(和C没啥关系!真的.): 2.看<Thinking In C++>,不要看<C++变成死相>: 3.看<The C++ Programm ...

  2. Masonry布局框架的使用

    Masonry是一个轻量级的布局框架 拥有自己的描述语法 采用更优雅的链式语法封装自动布局 简洁明了 并具有高可读性.比我们使用自动布局,繁琐的约束条件,好用多了.下面我们来学学masonry的使用方 ...

  3. 【java基础】内部类,局部内部类,匿名内部类、静态内部类、接口中的内部类

    内部类: 1.定义在一个类中的内部类,内部类的实例化伴随着外围类所定义的方法来构造,内部类对象有外围类的隐式引用,所以内部类可以直接访问外围类的外围类的域,包括私有的,这是内部类的访问特权,所以比常规 ...

  4. oracle系列--基础理论

    一.数据库系统架构: 外层(External Level)外层是提供给用户直接操作使用的 概念层(Conceptual Level)用来描述数据库中存放数据的类型.表之间的关系.高级的数据模型.用户的 ...

  5. IOS 消息

    发送消息: NSDictionary *dict=[[NSDictionary alloc]initWithObjectsAndKeys:@"num",[NSString stri ...

  6. osgEarth编译(转载)

    osgEarth编译 osgEarth的编译需要osg和一些第三方插件库,我主要参考了cnblogs上的一篇博文,但是也不够详细,并且我是在已经编译好osg的情况下去编译osgEarth,所以期间也遇 ...

  7. QStandardItemModel的简单应用

    The QStandardItemModel class provides a generic model for storing custom data. QStandardItemModel提供了 ...

  8. loading.io一个loading图标网站,跟大家分享

    loading.io是官方网址在首页选一款loading图标,看到左上角的 Try it now中有选中的图标后,可通过光标滑动选择图标大小,然后再点右边的get svg或get css等下载即可

  9. swift 启动图片的设置

    1 .找到Assets.xcassets 2. 在Assets.xcassets里创建 New LaunchImage 拖入相应的图片 3.选中你的项目,点击General 在App Icons an ...

  10. cookie、session、sessionid的区别

    我们都知道银行,银行的收柜台每天要接待客户存款/取款业务,可以有几种方案: 1.凭借柜台职员的记忆,由收柜台职员来为每位顾客办理存款/取款业务,单凭职员的记忆力,要记到每位顾客的相貌,并迅速这个顾客当 ...