Java ArrayList 源代码分析
Java ArrayList
之前曾经参考 数据结构与算法这本书写过ArrayList
的demo,本来以为实现起来都差不多,今天抽空看了下jdk
中的ArrayList
的实现,差距还是很大啊
首先看一下ArrayList
的类图
ArrayList
实现了Serializable Cloneable RandomAccess List
这几个接口,可序列化,可克隆,可以随机访问
构造方法:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
之前手写ArrayList
的时候,都会用一个默认容量来 new 一个数组,在jdk
中实现是默认一个空数组,因为有的时候ArrayList
创建后并不会添加元素
当然,这两个都是静态私有域
值得注意的是 this.elementData
是一个Object的数组 transient
表示这个属性不用被序列化,通过注释可以得知,element在第一次添加的时候会被扩容到默认容量(默认为10)
add 方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
add 方法中调用了 ensureCapacityInternal
相当于确保容量最少是size+1,size
就是当前ArrayList
元素个数,然后在elementData
末尾加入元素
接下来看一下是如何确保容量的
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
ensureCapacityInternal
首先会调用calculateCapacity
,这里主要是为了计算第一次初始化的时候,因为我们在默认初始化的时候,默认容量是10,但是为什么确保阔容是Math.max(DEFAULT_CAPACITY, minCapacity);
,这里主要是因为如果我们添加一个集合的话,要确保至少大小是集合中元素的大小,否则可能会多一次扩容
然后调用ensureExplicitCapacity
ensureExplicitCapacity
:先设置一下当前容器已经被更改,然后判断当前最少需要容量是不是大于数组长度,如果大于,那就扩容
private void grow(int minCapacity) {
// overflow-conscious code
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) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
首先获取旧数组的长度然后用旧数组长度进行扩容为1.5倍,然后判断和最小需求容量对比,如果小于最小容量,那么就扩容到最小容量那么长,然后判断是不是大于一个阈值,如果大于这个最大阈值,那么就扩容到Integer.MAX_VALUE(正整数最大值,2^31-1)
至于为什么要判断minCapacity<0
,那是因为假设当前已经扩容到最大值,要是还不够,那么再扩容就是int溢出
最后把源数组copy到新的容量大小赋值给elementData
,Array.copyOf
底层是native方法(System.arraycopy)
之前自己写的
ArrayList
都是通过oldcaptain = oldcaptain<<1+1;
来进行扩容的(+1是避免旧数组长度为0的情况),jdk
对于不同的情况有不同的扩容标准,而且以前自己的Copy都是用数组遍历Copy的很笨重,这里学到了
再来看一下 add(int index,T ele)
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
这个也很好理解,就是先检查index是否在范围内(0~size)如果不在就抛出一个越界异常
然后准备扩容,接下来就是数组拷贝
System.arraycopy
也是一个native方法
看一下注释就是把src
从srcPos
开始拷贝到dest
从destPos
开始的位置一共copy
length
这么长
如果src==dst
那么这个函数表现就像先拷贝到一个临时数组,再覆盖dst
对应位置
不会像
*dst++=*src++
把后面的元素覆盖然后后面元素都是一个值
这样就是把elementData
从index开始到最后一个元素,拷贝到src
+1的位置
最后执行elementData[index] = element;
把元素覆盖
然后我们看remove :
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);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
remove方法跟add基本同理,但是不需要扩容而且最后覆盖元素的时候是使用null填充最后一个元素
之前实现的时候没考虑到用null覆盖,这样会导致在
GC
的时候,本来需要删除的元素还可以通过ArrayList
找到,然后就无法GC
,这里学到了
remove一个对象
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
找对应元素的话基本都是大同小异,主要是fastRemove
跟自己实现的不太一样
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
}
fastRemove
里面跟remove基本相同,少了一个index判断也没有返回值
clear:
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
clear方法之前一直以为是直接把size设为0,但是jdk
里面实现是遍历一下设null,但是这里我总觉得应该再多提供一个fastclear
什么的比较好吧
设为null会让对象索引不到,可以被垃圾回收,但是如果频繁add clear的话总觉得不值得啊
再看一下一些跟集合的操作
通过一个集合初始化:
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
这里首先调用集合的toArray()
方法,不过要确保elementData
真的是一个Object[]数组
Java 中对象数组子类数组引用也可以转换为超类的引用
比方说 Manager 继承了 Employee
Manager[]managers = new Manager[10];
那么我们可以
Employee[]employees = managers;//完全没问题
但是如果我们在使用employees的时候在里面存放了一个new Employees,那么就会发生一个异常
这个
jdk
的bug可以查一下
Java集合中toArray
一般情况下都是Object[]
数组,不过手动实现一个集合,有可能出问题,所以jdk
采用这种方式避免了不必要的麻烦
就是避免这种情况:
ArrayList<Integer> integers = new ArrayList<>(0);
integers.add(1);
System.out.println(integers.toArray().getClass());
Integer[]integers_array = new Integer[2];
integers_array[0]=1;
integers_array[1]=2;
Class c = Arrays.asList(integers_array).toArray().getClass();
System.out.println(c);
Array.asList
就是包装一个视图,里面使用add remove什么的都会抛一个异常
Java ArrayList 源代码分析的更多相关文章
- Android系统进程间通信Binder机制在应用程序框架层的Java接口源代码分析
文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6642463 在前面几篇文章中,我们详细介绍了A ...
- java TreeMap 源代码分析 平衡二叉树
TreeMap 的实现就是红黑树数据结构,也就说是一棵自平衡的排序二叉树,这样就可以保证当需要快速检索指定节点. TreeSet 和 TreeMap 的关系 为了让大家了解 TreeMap 和 Tre ...
- Java ThreadLocal 源代码分析
Java ThreadLocal 之前在写SSM项目的时候使用过一个叫PageHelper的插件 可以自动完成分页而不用手动写SQL limit 用起来大概是这样的 最开始的时候觉得很困惑,因为直接使 ...
- Java ConcurrentHashMap 源代码分析
Java ConcurrentHashMap jdk1.8 之前用到过这个,但是一直不清楚原理,今天抽空看了一下代码 但是由于我一直在使用java8,试了半天,暂时还没复现过put死循环的bug 查了 ...
- Java HashMap 源代码分析
Java HashMap jdk 1.8 Java8相对于java7来说HashMap变化比较大,在hash冲突严重的时候java7会退化为链表,Java8会退化为TreeMap 我们先来看一下类图: ...
- Java中arraylist和linkedlist源代码分析与性能比較
Java中arraylist和linkedlist源代码分析与性能比較 1,简单介绍 在java开发中比較经常使用的数据结构是arraylist和linkedlist,本文主要从源代码角度分析arra ...
- Android应用程序进程启动过程的源代码分析
文章转载至CSDN社区罗升阳的安卓之旅,原文地址: http://blog.csdn.net/luoshengyang/article/details/6747696 Android 应用程序框架层创 ...
- java源代码分析----jvm.dll装载过程
简述众所周知java.exe是java class文件的执行程序,但实际上java.exe程序只是一个执行的外壳,它会装载jvm.dll(windows下,以下皆以windows平台为例,linux下 ...
- Java源代码分析与生成
源代码分析:可使用ANTLRANTLR是开源的语法分析器,可以用来构造自己的语言,或者对现有的语言进行语法分析. JavaParser 对Java代码进行分析 CodeModel 用于生成Java代码 ...
随机推荐
- 【2017-11-08】Linux与openCV:opencv版本查看及库文件位置等
1. 查看当前系统中opencv的版本: pkg-config --modversion opencv 可以看到系统中目前存在opencv2.4.9.1及opencv3.2.0两个版本. 不太清楚op ...
- July 28th 2017 Week 30th Friday
If equal affection cannot be, let the more loving be me. 如果没有相等的爱,那就让我爱多一点吧. There is seldom equal a ...
- HTTP协议图--HTTP 报文首部之首部字段(重点分析)
1.首部字段概述 先来回顾一下首部字段在报文的位置,HTTP 报文包含报文首部和报文主体,报文首部包含请求行(或状态行)和首部字段. 在报文众多的字段当中,HTTP 首部字段包含的信息最为丰富.首部字 ...
- vs中添加工具cmder并自动定位到当前目录
有时在vs中为了使用git命令行,需要打开cmder工具,并让cmder自切换到当前目录: 方法1: 看下效果: 方法2:在文件夹中右键(添加到右键自行百度)
- angularjs中factory, service和provider
在Angular里面,services作为单例对象在需要到的时候被创建,只有在应用生命周期结束的时候(关闭浏览器)才会被清除.而controllers在不需要的时候就会被销毁了(因为service的底 ...
- python中的BaseManager通信(一)文件三分
可以在windows下单机运行 主部分(提供服务器) #mainfirst.py from multiprocessing.managers import BaseManager import Que ...
- SOJ 4309 Sum of xor 异或/思维
Source ftiasch 解题思路: 本题的题解有参考这里,但是那篇年代太久远,讲的也不甚清晰,所以可能会对很多新手造成困扰,所以又写了这一篇. 亦或有很多规律,本题使用到的是n^(n+1)=1, ...
- Java工具类(util) 之01- 数学运算工具(精确运算)
数学运算工具(精确运算) /** * * @author maple * */ public abstract class AmountUtil { private AmountUtil() { } ...
- P3177 [HAOI2015]树上染色
题目描述 有一棵点数为 N 的树,树边有边权.给你一个在 0~ N 之内的正整数 K ,你要在这棵树中选择 K个点,将其染成黑色,并将其他 的N-K个点染成白色 . 将所有点染色后,你会获得黑点两两之 ...
- oracle11g之管理oracle数据库笔记(理论基础知识)
第三章 管理oracle数据库 1.启动数据库步骤;(创建启动实例--> ...