如同C语言中字符数组向String过渡一样,作为面向对象语言,自然而然的出现了由Object[]数据形成的集合。本文从JDK源码出发简单探讨一下ArrayList的几个重要方法。

Fields

    //序列化Id保证了集合是可以进行RPC通信的
private static final long serialVersionUID = 8683452581122892189L;
//作为一个普通Object[]数组,集合的默认长度是10
private static final int DEFAULT_CAPACITY = 10;
//这里声明一个空Object数组,用来标示空集合
private static final Object[] EMPTY_ELEMENTDATA = {};
//默认长度的Object数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//真正存储数据的的Object数组,transient保证不会参与序列化
transient Object[] elementData;
//当前ArrayList的长度
private int size;

构造器

//当用户指定了容量的时候,elementData就是新new出来的Object数组,如果长度指定为0则等于声明的空Object数组。不支持负数长度。
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);
}
} //若用户没有指定长度,构造一个默认长度为10的空Object数组。
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//如果传入的是集合类,则拷贝出一个新的Object数组存放用户输入的数组内容
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
//根据c的声明方式不同,c.toArray()得到的结果类型是不同的。虽然elementData声明的是Object数组,但是如果c.toArray()不是Object数组类型,elementData无法存放Object类型。所以这里做了判断,如果是这种情况,重新拷贝一份新的Object数组。
//关于这点特性可以参考http://blog.csdn.net/aitangyong/article/details/30274749?utm_source=tuicool&utm_medium=referral
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 如果传入的长度为0,则elementData等于声明的空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}

内部方法

 //由于elementData会进行扩容,扩容机制见下文。所以会生成一些没有用的位置,通过trimToSize方法啊重新生成一段实际长度的Object数组。长度全为有效长度。
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
} //核心功能,提前扩容。当需要扩容的空间很大的时候,可以通过这个以前一次性扩容,否则会一次次慢慢扩容。这种方式效率很高。
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 如果用户希望的大小大于当前的容量,则进行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//扩容方法
private void grow(int minCapacity) {
// 新容量为老容量的1.5倍
int oldCapacity = elementData.length;
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) {
//如果用户期待的容量很大,比最大整数还大就是负数了,则OOM
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//否则就给最大的空间
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
} //新增元素
public void add(int index, E element) {
//检查索引,不合法就异常
rangeCheckForAdd(index);
//进行扩容,首先进行+1扩容
ensureCapacityInternal(size + 1);
//将index后面的统一往后移动,所以add效率很低
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
size++;
} //删除元素
public E remove(int index) {
//索引合法性检查
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
//统一往前移动
System.arraycopy(elementData, index+1, elementData, index,numMoved);
//把最后一个空出来的赋值null,方便gc回收
elementData[--size] = null; // clear to let GC do its work
return oldValue;
} //取子列表方法
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
} //检查参数
static void subListRangeCheck(int fromIndex, int toIndex, int size) {
if (fromIndex < 0)
throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
if (toIndex > size)
throw new IndexOutOfBoundsException("toIndex = " + toIndex);
if (fromIndex > toIndex)
throw new IllegalArgumentException("fromIndex(" + fromIndex +") > toIndex(" + toIndex + ")");
} private class SubList extends AbstractList<E> implements RandomAccess {
private final AbstractList<E> parent;
private final int parentOffset;
private final int offset;
int size; //依然是原始数组,不过是声明了一个不同的其实与结尾坐标,总体逻辑和外部类ArrayList基本相同
SubList(AbstractList<E> parent,
int offset, int fromIndex, int toIndex) {
this.parent = parent;
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount;
} //jdk1.7以后List本身有了sort功能,不用再通过Collections.sort来排序了
@Override
@SuppressWarnings("unchecked")
public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}

总结

ArrayList的源码重点在于:
1. 扩容的时候按照原容量的1.5倍扩容
2. 若需要的容量很大,可以通过ensureCapacity进行提前一步到位扩容,或者直接通过构造器声明一个大的ArrayList。
3. 对Object[]进行操作的时候都是通过System.arraycopy进行的,这是一个native方法,直接操作内存,等同于C语言中的底层方法。

JDK源码阅读——ArrayList的更多相关文章

  1. JDK源码阅读--ArrayList

    public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess ...

  2. JDK源码阅读—ArrayList的实现

    1 继承结构图 ArrayList继承AbstractList,实现了List接口 2 构造函数 transient Object[] elementData; // 数组保存元素 private i ...

  3. JDK源码阅读(三):ArraryList源码解析

    今天来看一下ArrayList的源码 目录 介绍 继承结构 属性 构造方法 add方法 remove方法 修改方法 获取元素 size()方法 isEmpty方法 clear方法 循环数组 1.介绍 ...

  4. JDK源码阅读-FileDescriptor

    本文转载自JDK源码阅读-FileDescriptor 导语 操作系统使用文件描述符来指代一个打开的文件,对文件的读写操作,都需要文件描述符作为参数.Java虽然在设计上使用了抽象程度更高的流来作为文 ...

  5. JDK源码阅读(一):Object源码分析

    最近经过某大佬的建议准备阅读一下JDK的源码来提升一下自己 所以开始写JDK源码分析的文章 阅读JDK版本为1.8 目录 Object结构图 构造器 equals 方法 getClass 方法 has ...

  6. 利用IDEA搭建JDK源码阅读环境

    利用IDEA搭建JDK源码阅读环境 首先新建一个java基础项目 基础目录 source 源码 test 测试源码和入口 准备JDK源码 下图框起来的路径就是jdk的储存位置 打开jdk目录,找到sr ...

  7. JDK源码阅读-FileOutputStream

    本文转载自JDK源码阅读-FileOutputStream 导语 FileOutputStream用户打开文件并获取输出流. 打开文件 public FileOutputStream(File fil ...

  8. JDK源码阅读-FileInputStream

    本文转载自JDK源码阅读-FileInputStream 导语 FileIntputStream用于打开一个文件并获取输入流. 打开文件 我们来看看FileIntputStream打开文件时,做了什么 ...

  9. JDK源码阅读-ByteBuffer

    本文转载自JDK源码阅读-ByteBuffer 导语 Buffer是Java NIO中对于缓冲区的封装.在Java BIO中,所有的读写API,都是直接使用byte数组作为缓冲区的,简单直接.但是在J ...

随机推荐

  1. taskctl实现自定义mysql存储过程作业类型调用

    TASKCTL支持任意作业类型的扩展,但目前TASKCTL 4.1.3版本中并没有内置mysql存储过程的作业插件.通过介绍使TASKCTL支持调度mysql存储过程作业类型的步骤,一方面解决一些朋友 ...

  2. 如何在github制作一个网页

    1.首先得先注册一个github账号,官网:https://github.com/ 2.注册完,登录账号进入首页,点右上角的 ‘+’ 创建新的仓库 3. 点击setting,选择一个主题, 4. 选完 ...

  3. file-API 实现添加图片 预览缩略图(自己学习)

    首先看看 "效果图" : 我们最终实现的就是点击右侧的"相机"按钮添加想要添加的图片然后可以根据需要删除(点叉删除本条)或再次添加图片,并显示缩略图....走起 ...

  4. Java 字符排序问题

    Java 字符排序问题 未专注于排序算法,而是写了一个MyString类,实现了comparable的接口,然后用Arrays的sort方法来实现排序.我觉得这道题的难度在于如果比较两个.因为大小写的 ...

  5. 数据处理之pandas简单介绍

    Offical Website :http://pandas.pydata.org/ 一:两种基本的数据类型结构 Series 和 DataFrame 先来看一下Series import panda ...

  6. NOIP 2001 提高组 题解

    NOIP 2001 提高组 题解 No 1. 一元三次方程求解 https://vijos.org/p/1116 看见有人认真推导了求解公式,然后猥琐暴力过的同学们在一边偷笑~~~ 数据小 暴力枚举即 ...

  7. centos7源码编译安装Ansible详细部署

    一.基础介绍==========================================================================================ansi ...

  8. Python爬虫从入门到放弃(二十二)之 爬虫与反爬虫大战

    爬虫与发爬虫的厮杀,一方为了拿到数据,一方为了防止爬虫拿到数据,谁是最后的赢家? 重新理解爬虫中的一些概念 爬虫:自动获取网站数据的程序反爬虫:使用技术手段防止爬虫程序爬取数据误伤:反爬虫技术将普通用 ...

  9. react - 解刨组件的多种写法

    一,原始的createClass写法 对于写react组件,很多人第一印象往往是createClass,这是因为createClass是react组件最原始的写法,基本每个学react的人都是接触这种 ...

  10. CgLib动态代理学习【Spring AOP基础之一】

    如果不了解JDK中proxy动态代理机制的可以先查看上篇文章的内容:Java动态代理学习[Spring AOP基础之一] 由于Java动态代理Proxy.newProxyInstance()的时候会发 ...