手撕ArrayList源码
文章目录
一、背景(罗素悖论)
一天,村里的理发师挂出了一个牌子
“村里所有不给自己理发的人,都由我来理发,我也只给这些人理发”
有人问理发师:你自己的头发谁来理?
罗素:英国哲学家、数学家,1902年提出,撼动了数学界的基石。
集合论:现代数学的基石,数学家康托尔,德国人,有着犹太的血统。
开始是研究三角函数,逐渐摸索并提出了集合论,引来巨大的争议,
包括他的老师,逐渐抑郁,以致精神失常,落寞地病逝于精神病院。
当前最聪明的三大人种:犹太人、日耳曼人、亚裔人(中国人)
电影《美丽心灵》 博弈论的提出者 纳什 的个人传记,一生都于精神分裂
抗争,最终获得诺贝尔经济学奖。
二、集合这些事儿(三大块)
List 有顺序可重复的 ArrayList
Set 无顺序不可重复的 HashSet
Map 键值对 key-value HashMap
三、ArrayList
如果存储渡一所有的成员,用什么?
100 - 1000 - 10000 (容器:动态的,快速查询)
1)概述 (60分)
array 数组(本质) list列表
数组:int[] arr = new int[3] 初始化需要定义长度,且长度不能更改
动态数组:arraylist解决数组长度固化的问题,同时带来新的问题。
判断容量不足时,进行扩容,扩容算法?
数组的特性:内存空间是连续存储的,随机访问的效率非常之高。
封装为arraylist后,增加和删除的效率就降低了。
增加时,可能需要扩容;删除时,可能需要移动
面试题:
a arraylist是如何扩容的?
b 如果要加入1w条数据,如何提高效率? (80分)
c 它是线程安全的吗?如何做到线程安全?
d fail_fast是什么? (100分
2)源码分析(1.8版本)
a 类声明
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
i 继承了AbstractList并实现List , 需要提供最基本的增删改查功能
ii 实现了RandomAccess,支持随机访问/快速访问,和数组的访问方式一致
iii 实现了Cloneable,能够被克隆
iv 实现了Serializable,可以被序列化,如果需要自定义序列化,需要实现writeObject和readObject方法
b 构造器 (alt+7 查看方法列表)
i 无参
//存储所有数据的对象数组
Object[] elementData
public ArrayList() {
//初始化时,赋值一个空的数组 {}
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
ii 有参int类型
//传入初始化容量
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);
}
}
iii 有参集合类型
//记录数组实际包含的元素数量
int size;
// 支持将其他集合转为arraylist
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
// 判断获取的数组是否是object[]类型
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
c 增加元素
//赋值并且将size+1
public boolean add(E e) {
//确认容量是否足够
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//先计算容量 然后确认精确的容量
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
// 默认的初始化容量
int DEFAULT_CAPACITY = 10;
//对于空数组至少需要有1个容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 返回默认容量(10)和最小容量(1)的最大值
//第一次扩容的时候,至少扩容到10
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
//记录更改的次数
int modCount = 0;
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
//扩容的核心操作
grow(minCapacity);
}
// elementData是{} minCapacity是10
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// " >> 1 " 右移==/2
// 新容量是旧容量的1.5倍 why?
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
//最小值校验 当前赋值10
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);
}
扩容算法:
i 最小扩容范围是10
ii 默认扩容范围是旧容量的1.5倍
验证性能:
如果数据量很大,并且逐个add的时候,需要扩容多少次?
1k 1w 10w 100w 分别是多少?
12 18 23 29
优秀的算法 能够减少扩容次数
3)、删除元素
支持两种方式:索引、对象本身
//以索引删除为例
public E remove(int index) {
//范围检查
rangeCheck(index);
//更改次数+1
modCount++;
E oldValue = elementData(index);
// 计算要移动的元素个数
int numMoved = size - index - 1;
if (numMoved > 0)
//通过拷贝的方式 移动元素
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//把最后一位元素置为空 size-1
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
4)、遍历元素
在foreach循环中不能移除元素,会报同步修改异常
ConcurrentModificationException
fast-fail机制 (快速失败) 线程不安全
本质上,是在可能发生故障的时候,快速提醒,快速处理。
5)、线程安全(5分 知识广度)
Vector类是线程安全,过时了,加了一堆锁,效率低极了
CopyOnWriteArrayList 在concurrent包下
原理:把增删的操作通过新的复制来实现,然后改变array的引用。
(降低效率)