javase基础回顾(一)ArrayList深入解析 解读ArrayList源代码(JDK1.8.0_92)

我们在学习这一块内容时需要注意的一个问题是 集合中存放的依然是对象的引用而不是对象本身。
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)的更多相关文章
- javase基础回顾(二)LinkedList需要注意的知识点 阅读源码收获
我们在学习这一块内容时需要注意的一个问题是 集合中存放的依然是对象的引用而不是对象本身. List接口扩展了Collection并声明存储一系列元素的类集的特性.使用一个基于零的下标,元素可以通过它们 ...
- javase基础回顾(四) 自定义注解与反射
本篇文章将从元注解.自定义注解的格式.自定义注解与反射结合的简单范例.以及自定义注解的应用来说一说java中的自定义注解. 一.元注解 元注解也就是注解其他注解(自定义注解)的java原生的注解,Ja ...
- javase基础回顾(三) 动态代理
动态代理是大型框架中经常用到的经典的技术之一,博主在理解spring的控制反转(依赖注入)的思想时回头着重复习了一下java的动态代理. 在说动态代理之前我们先简单说一说代理是用来干什么的,用于什么样 ...
- javaSE基础之 ArrayList的底层简单实现
最近就是想扒一扒存在硬盘里面的学习资料(突然想到什么),把以前写过的一些东西整理一下分享出来. 这边是ArrayList 的简单实现,当然只实现了部分方法 package com.yck.collec ...
- ArrayList、Vector和LinkedList等的差别与用法(基础回顾)
ArrayList 和Vector是采取数组体式格式存储数据,此数组元素数大于实际存储的数据以便增长和插入元素,都容许直接序号索引元素,然则插入数据要设计到数组元素移动等内存操纵,所以索引数据快插入数 ...
- JAVASE笔记回顾
第一部分,JAVA基础和面向对象 part01 入门与开发环境搭建 1: 计算机基础知识(了解)(1)计算机(2)计算机硬件(3)计算机软件系统软件:windows,linux,mac应用软件:QQ, ...
- 基础1 JavaSe基础
JavaSe基础 1. 九种基本数据类型的大小,以及他们的封装类 boolean 无明确指定 Boolean char 16bits Character byte 8bits Byte short 1 ...
- javaSE基础04
javaSE基础04 一.三木运算符 <表达式1> ? <表达式2> : <表达式3> "?"运算符的含义是: 先求表达式1的值, 如果为真, ...
- 1、java基础回顾与加强
一. 基础回顾 1 集合 1.1 集合的类型与各自的特性 ---|Collection: 单列集合 ---|List: 有存储顺序, 可重复 ---|ArrayList: 数组实现, ...
随机推荐
- 字典NSDictionary的常见用法
// 动态获取字典的第一个典 NSString *firstKey = responseObject.keyEnumerator.nextObject;
- Ural Vol1(dif>=900)
目前已AC: 2 1040.Airline Company(构造) 题目要求与每个顶点相连的所有边编号最大公约数为1,其实只要其中的两条边编号互质,所有边编号的最大公约数一定为1.我们知道相邻的数字 ...
- 关于Java在Linux or Android平台调用.so库
Linux平台Java调用so库-JNI使用例子 android NDK开发及调用标准linux动态库.so文件 在Android项目中调用已有.so库 Android 调用.so文件 jni And ...
- 【转】C\C++代码优化的27个建议
1. 记住阿姆达尔定律: funccost是函数func运行时间百分比,funcspeedup是你优化函数的运行的系数. 所以,如果你优化了函数TriangleIntersect执行40%的运行时间, ...
- Github上的600多个iOS开源类库
Github上的600多个iOS开源类库,入下图所示,里面有很多资源,学习积累的好资源 地址:http://github.ibireme.com/github/list/ios/
- 用DMA直接驱动GPIO,实现GPIO最高输出速率(转)
源:用DMA直接驱动GPIO,实现GPIO最高输出速率 先上图:STM32F303芯片,72M的主频 可以看到GPIO的达到了14.4M的翻转速率, 再来上代码: RCC_AHBPeriph ...
- 为什么建立TCP连接需要三次握手,为什么断开TCP连接需要四次握手,TIME_WAIT状态的意义
为什么建立TCP连接需要三次握手? 原因:为了应对网络中存在的延迟的重复数组的问题 例子: 假设client发起连接的连接请求报文段在网络中没有丢失,而是在某个网络节点长时间滞留了,导致延迟到达ser ...
- select into from 和 insert into select 的用法
SELECT INTO 和 INSERT INTO SELECT 两种表复制语句 Insert是T-sql中常用语句,Insert INTO table(field1,field2,...) valu ...
- jdk8 之 java.time包AND DateUtils
package com.jansh.comm.util; import java.time.Clock; import java.time.LocalDate; import java.time.Lo ...
- bzoj3932
3932: [CQOI2015]任务查询系统 Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 1326 Solved: 480[Submit][Sta ...