之前写了 ,集合框架的 Map 中的 HashMap 和LinkedHashMap,还有几个map没有涉及 不过工作中(个人而言) 用的比较少,用到了再去研究 今天就研究下用了好久的 ArrayList
ArrayList
- 0 . 继承结构
ArrayList 是 实现List , Cloneable Serializable 接口
-
1 . 成员变量
private static final int DEFAULT_CAPACITY = 10;(默认初始容量)
private static final Object[] EMPTY_ELEMENTDATA = {};(如果创建的List是空的没有数据 则底层共享这个空数组)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};(和DEFAULT_CAPACITY 作用相同 共享空数组 只不过使用时候不对)
transient Object[] elementData; // 每个实力的数组
private int size;// 数组中的含有元素的个数
- 2 . 构造函数
// 这边 第一个构造很有意思哦 默认的数组指向了 数组的 static 的一个空数组变量
// 这边应该是是 为了防止我们 new 出许多新对象 new出多了无用对象节约内存
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 有参构造 初始化 数组容量 大小
// 同样的 这边 如果初始化话 0 那么指向的还是一个 共享 空的数组 给定多少 new 多大的数组
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);
}
}
-
3 . 增 add()
重点来了 看下 add 的时候到底发生了什么
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
这个 add 看起来很简单 就三行 第一行 计算这个list的 数组长度需不需要扩容 (顺便增加修改次数) 然后把add 进来的 放在数组的最后一位
好了我们重点看下 他的第一行扩容方法
// minCapacity 当前 list的 大小传下去
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
//增加修改次数 modCount++; 如果 大于当前数组大小就扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
// 扩容代码
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// newCapacity 新的 数组大小 是 old +old>>1() (>>1 就是 /2 的意思 因为位 运算符效率比较高 )
// 最后 最后新的 扩容大小 就是 原有的基础上的 1.5 倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果还是小于 插入的就是插入大小(一般不可能)
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 超过最大? 最大只能是 Integer.MAX_VALUE 2^31 -1
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
//执行扩容 调用 Arrays .copyof 工具类方法 旧的数组和 新的长度放进去
elementData = Arrays.copyOf(elementData, newCapacity);
}
public static T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
public static T[] copyOf(U[] original, int newLength, Class extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
//重点在这里
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
调用了一个 本地方法 native 执行扩容 (可能C 的代码 或者其他 )
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
好了最后 结合图片看下
这就是整个扩容 嗯挺简单的
- 4 . 增 get()
public E get(int index) {
//判断 index 是否小于0
rangeCheck(index);
return elementData(index);
}
// 直接根据下标从 数组里面取出来 O(1)
E elementData(int index) {
return (E) elementData[index];
}
- 4 . 增 remove()
public E remove(int index) {
//判断 index 是否正确
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
//只要不是 移除的是最后一位 都需要重新 生成数组 还是调用本地方法
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 最后一位置空 size 减少
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
//直接remove 对象就比较麻烦 for循环 remove O(n)
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;
}
-
4 . 改 set()
public E set(int index, E element) {
rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue;}
基本 只是讲完 讲一下扩展的
我们都知道 可以使用迭代器来 遍历list 值得注意的是 modCount之前代码提到人多 这个参数 迭代器在 每次迭代的时候 会检查这个参数 和他初始化的时候一样不一样,
俗称 fail-fast 很好理解 我们 不能 一遍迭代一遍add set remove(“remove”) 导致 放置多线程 操作
不能用增强for 因为 增强for 底层是 迭代器 然后 你用了 remove(“remove”) 导致
modCount 改变 当然会报错
第一种
// 遍历list删除
for(int i=0;i it = list.iterator();
while(it.hasNext()){
String x = it.next();
if(x.equalsIgnoreCase("remove")){
it.remove();
}
}
其实 list 还有一种特殊的 迭代器
看见了么
ListIterator 是list 专属的 迭代器 看见方法了么
特别是 set 方法 一边迭代 一边 改变当前位置 特别方便 在有些场景下 就能用到
比如一个list很大 需要过滤改变一下值得时候 不要傻傻的放到一个新的list 里了
其实 实现 也简单 还是调用了set 方法
还有看了上面扩容的代码
我们应该知道如果要添加大容量数据我们应该提前初始化好容量
运行结果
最后扩展一个知识点 参考别人写过的一个例子
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.RandomAccess;
/**
* Created by gx
* Date: 2017-08-30
* Time: 18:38
*/
public class TestRandomAccess {
public static void initList(List list, int n) {
for (int i = 0; i < n; i++) {
list.add(i);
}
}
//使用循环进行对列表的迭代
public static void traverseWithLoop(List list) {
long starttime = 0;
long endtime = 0;
starttime = System.currentTimeMillis();
for (int count = 0; count <= 1000; count++) {
for (int i = 0; i < list.size(); i++) {
list.get(i);
}
}
endtime = System.currentTimeMillis();
System.out.println(list.getClass().getName()+"使用for 循环 迭代一共花了" + (endtime - starttime) + "ms时间");
}
//使用迭代器对列表进行迭代
public static void traverseWithIterator(List list) {
long starttime = 0;
long endtime = 0;
starttime = System.currentTimeMillis();
for (int count = 0; count <= 1000; count++) {
for (Iterator itr = list.iterator(); itr.hasNext();) {
itr.next();
}
}
endtime = System.currentTimeMillis();
System.out.println(list.getClass().getName()+"使用Iterator迭代一共花了" + (endtime - starttime) + "ms时间");
}
public static void traverse(List list) {
long starttime = 0;
long endtime = 0;
if (list instanceof RandomAccess) {
System.out.println(list.getClass().getName()+"该list实现了RandomAccess接口");
starttime = System.currentTimeMillis();
for (int count = 0; count <= 1000; count++) {
for (int i = 0; i < list.size(); i++) {
list.get(i);
}
}
endtime = System.currentTimeMillis();
System.out.println(list.getClass().getName()+"迭代一共花了" + (endtime - starttime) + "ms时间");
} else {
System.out.println(list.getClass().getName()+"该list未实现RandomAccess接口");
starttime = System.currentTimeMillis();
for (int count = 0; count <= 1000; count++) {
for (Iterator itr = list.iterator(); itr.hasNext();) {
itr.next();
}
}
endtime = System.currentTimeMillis();
System.out.println(list.getClass().getName()+"迭代一共花了" + (endtime - starttime) + "ms时间");
}
}
public static void main(String[] args) {
ArrayList arraylist = new ArrayList();
LinkedList linkedlist = new LinkedList();
initList(arraylist, 1000);
initList(linkedlist, 1000);
traverse(arraylist);
traverse(linkedlist);
traverseWithIterator(arraylist);
traverseWithLoop(arraylist);
traverseWithIterator(linkedlist);
traverseWithLoop(linkedlist);
}
}
最开始我们就知道了ArrayList 实现了RandomAccess 接口 这是个 标记接口(空接口)
实现这个接口可以意思就是 可以达到 随机访问任意下标元素 速度比较快的特点
for循环 比迭代器快 当然 在ArrayList 中 直接去get 下标当然快
不过迭代器中间那么多代码 还有关于 LinkedList 为什么迭代那么慢 我下期 会说的
因为两者底层数据结构不同 其实ArrayList 的方法 还有些没讲
我在这边也只是列举了常用的 其他方法 踩坑 或者用到 我会继续更新
总结
- ArrayList 底层是 数组
- 每次 容量满时 会采取JDK native 方法 扩容为原来的 1.5 倍 最大是Integer.MAX_VALUE 2^31-1
- 实现了 RandomAccess for循环 比 迭代器快
- 有一个专门的 ListIterator 扩展 Iterator 功能







