JDK1.8 之 集合框架 ArrayList 源码解析

之前写了 ,集合框架的 Map 中的 HashMap 和LinkedHashMap,还有几个map没有涉及 不过工作中(个人而言) 用的比较少,用到了再去研究 今天就研究下用了好久的 ArrayList

ArrayList

  • 0 . 继承结构
    JDK1.8 之 集合框架 ArrayList 源码解析_第1张图片
    mark

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 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);

好了最后 结合图片看下


JDK1.8 之 集合框架 ArrayList 源码解析_第2张图片
mark

这就是整个扩容 嗯挺简单的

  • 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 还有一种特殊的 迭代器   
JDK1.8 之 集合框架 ArrayList 源码解析_第3张图片
mark

看见了么
ListIterator 是list 专属的 迭代器 看见方法了么
特别是 set 方法 一边迭代 一边 改变当前位置 特别方便 在有些场景下 就能用到
比如一个list很大 需要过滤改变一下值得时候 不要傻傻的放到一个新的list 里了

其实 实现 也简单 还是调用了set 方法

JDK1.8 之 集合框架 ArrayList 源码解析_第4张图片
mark

还有看了上面扩容的代码
我们应该知道如果要添加大容量数据我们应该提前初始化好容量


JDK1.8 之 集合框架 ArrayList 源码解析_第5张图片
mark

运行结果

JDK1.8 之 集合框架 ArrayList 源码解析_第6张图片
mark

最后扩展一个知识点 参考别人写过的一个例子



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);
    }

}

JDK1.8 之 集合框架 ArrayList 源码解析_第7张图片
mark

最开始我们就知道了ArrayList 实现了RandomAccess 接口 这是个 标记接口(空接口)

实现这个接口可以意思就是 可以达到 随机访问任意下标元素 速度比较快的特点

for循环 比迭代器快 当然 在ArrayList 中 直接去get 下标当然快

不过迭代器中间那么多代码 还有关于 LinkedList 为什么迭代那么慢 我下期 会说的

因为两者底层数据结构不同 其实ArrayList 的方法 还有些没讲

我在这边也只是列举了常用的 其他方法 踩坑 或者用到 我会继续更新

总结

- ArrayList 底层是 数组 
- 每次 容量满时 会采取JDK native 方法 扩容为原来的 1.5 倍 最大是Integer.MAX_VALUE  2^31-1  
- 实现了  RandomAccess  for循环 比 迭代器快
-  有一个专门的  ListIterator 扩展  Iterator 功能

我的公众号

JDK1.8 之 集合框架 ArrayList 源码解析_第8张图片
微信公众号

你可能感兴趣的:(JDK1.8 之 集合框架 ArrayList 源码解析)