2

ArrayList源码浅析

概述

ArrayList是底层结构为数组,长度可变的有序集合

位置

java.util.ArrayList

类定义

ClassDefine.png
继承AbstractList
实现List、RandomAccess、Cloneable、Serializable

  • AbstractList
  • List
  • RandomAccess
  • Cloneable
  • Serializable

1.AbstractList
    _作用:简单实现List中的方法_
2.List
     为什么实现两边List?

static interface Subject{
        void sayHi();
        void sayHello();
    }
    static class AbstractSubject implements Subject{
        @Override
        public void sayHi() {
            System.out.println("hi");
        }

        @Override
        public void sayHello() {
            System.out.println("hello");
        }
    }
    static class SubjectImpl extends AbstractSubject /*implements Subject*/{

        @Override
        public void sayHi() {
            System.out.println("hi");
        }

        @Override
        public void sayHello() {
            System.out.println("hello");
        }
    }
    static class ProxyInvocationHandler implements InvocationHandler {
        private Subject target;
        public ProxyInvocationHandler(Subject target) {
            this.target=target;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.print("say:");
            return method.invoke(target, args);
        }

    }
    public static void main(String[] args) {
        Subject subject=new SubjectImpl();
        Subject subjectProxy=(Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(), subject.getClass().getInterfaces(), new ProxyInvocationHandler(subject));
        subjectProxy.sayHi();
        subjectProxy.sayHello();
    }
    
    结果:
    Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to ProxyTest$Subject
        at ProxyTest.main(ProxyTest.java:51)

3.RandomAccess
    _作用:使其支持快速随机访问_

/**
 * 支持ArrayList支持快速随机查询,使用普通for时效率更高
 */
public static void test13(){
    ArrayList<String> arrayList = new ArrayList<>();
    for (Integer i = 0; i < 1000000; i++) {
        arrayList.add("1");
    }
    long arrayStart = System.currentTimeMillis();
    for (String s : arrayList) {
    }
    System.out.println("===forEach:"+(System.currentTimeMillis()-arrayStart));
    long arrayStart2 = System.currentTimeMillis();
    for (int i = 0; i < arrayList.size(); i++) {
    }
    System.out.println("===for:"+(System.currentTimeMillis()-arrayStart2));
 }
        
运行结果:
===forEach:16
===for:5    

    java.util.Collections中的应用
collections.png
为什么LinkedList不实现呢?
个人浅析:不是因为加了这个标记接口使for循环效率变高,而是加了这个接口可以让其他工具类用效率最高的方式处理被标记的类

4.Cloneable
5.Serializable

成员属性

//序列化ID
private static final long serialVersionUID = 8683452581122892189L;
//默认容量
private static final int DEFAULT_CAPACITY = 10;
//共享空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//默认空数组,只有在调用默认构造函数时会使用
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存放数据数据
transient Object[] elementData;
//数组内实际元素个数
private int size;
//数组最大元素数
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

transient
短暂的; 转瞬即逝的; 临时的

被其修饰的字段在序列化过程中将被忽略,如果需要对其做特殊操作,可以定义writeObject和readObject来进行实现特殊的序列化和反序列化
readObj.png

writeObj.png
既不是重写父类方法,也不是实现接口方法,如何发挥作用?
java.io.ObjectInputStream.readSerialData
java.io.ObjectOutputStream.writeSerialData
序列化和反序列化时会调用这两个方法,方法中判断被操作对象中是否存在这两个方法

构造方法

//自定义初始容量
public ArrayList(int initialCapacity)
//默认构造方法
public ArrayList()
//将传入集合转为ArrayList
public ArrayList(Collection<? extends E> c)
construction.png

Test Method:

    static void test09(){
        try {
            List<Integer> integers = Arrays.asList(1, 2, 3);
            Object[] asObjs = integers.toArray();
            asObjs[2] = "123";
        }catch (Exception e) { //java.lang.ArrayStoreException: java.lang.String
            e.printStackTrace();
        }

        try {
            List<String> arrayList = new ArrayList<>();
            arrayList.add("123");
            arrayList.add("123");
            arrayList.add("123");
            Object[] arrayObjs = arrayList.toArray();
            arrayObjs[2] = 123;
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

核心方法

//确认容量
//如果必要,增加此ArrayList实例的容量,以确保它至少可以容纳由minimum capacity参数指定的元素数。
public void ensureCapacity(int minCapacity)
//确认容量内部
private void ensureCapacityInternal(int minCapacity)
//确认清楚的容量
private void ensureExplicitCapacity(int minCapacity
//扩容
//增加容量以确保它至少可以保存最小容量参数指定的数量的元素
private void grow(int minCapacity)

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //将容量扩容至原容量的1.5倍
        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;
    }

Test Code:

    static void test10(){
             int MAX_ARRAY_SIZE = Integer.MAX_VALUE-8;
             int minCapacity = Integer.MAX_VALUE+1;
             // overflow-conscious code
             int oldCapacity = Integer.MAX_VALUE;//换个较小的数11
             
             int newCapacity = oldCapacity + (oldCapacity >> 1);
             if (newCapacity - minCapacity < 0)
                 newCapacity = minCapacity;
             if (newCapacity - MAX_ARRAY_SIZE > 0)
                 newCapacity = hugeCapacity(minCapacity);
                 
             System.out.println(newCapacity);
         }
         private static int hugeCapacity(int minCapacity) {
             int MAX_ARRAY_SIZE = Integer.MAX_VALUE-8;
             if (minCapacity < 0) // overflow
                 throw new OutOfMemoryError();
             return (minCapacity > MAX_ARRAY_SIZE) ?
                     Integer.MAX_VALUE :
                     MAX_ARRAY_SIZE;
         }
         
    结果:抛出异常     

常用方法

//是否包含指定元素
public boolean contains(Object o) 
//返回指定元素下标
public int indexOf(Object o)
//获取指定位置元素
public E get(int index)
//替换指定下标元素
public E set(int index, E element) 
//在集合末尾添加元素
public boolean add(E e)
//在集合指定位置添加元素
public void add(int index, E  element)
//将另一个集合追加至指定集合末尾
public boolean addAll(Collection<? extends E> c)
//将另一个集合插入指定集合位置
public boolean addAll(int index, Collection<? extends E> c)
//移除指定下标元素
public E remove(int index)
//移除指定元素
public boolean remove(Object o)
//移除入参数组中在原集合相同的元素
public boolean removeAll(Collection<?> c)
//保留入参数组中在原集合相同的元素
public boolean retainAll(Collection<?> c)

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

这个方法可以看出逻辑还是比较简单,大概就是将要剔除元素后面的元素整体赋值到要剔除元素以后,然后在降最后一位置空

batchRemove

private boolean batchRemove(Collection<?> c, boolean complement) {
    final Object[] elementData = this.elementData;
    int r = 0, w = 0;//r表示正常循环原始数组的下标位置;w表示符合要求的元素将要存放的位置(即记录要覆盖的元素位置)
    boolean modified = false;
    try {
        for (; r < size; r++)
            //判断当前遍历到的原数组元素是否满足入参数组中元素(存在或不存在)
            //其实就是找到想要的元素
            if (c.contains(elementData[r]) == complement)
                //满足则将当前元素复制到“结果列表”的最后一位
                elementData[w++] = elementData[r];
    } finally {
        // Preserve behavioral compatibility with AbstractCollection,
        // even if c.contains() throws.
        //如果发生异常,将已经检查位置之后的(r下标以后的)元素,放到“结果列表”w的位置上
        if (r != size) {
            System.arraycopy(elementData, r,
                             elementData, w,
                             size - r);
            w += size - r;
        }
        //原集合是否发生变动,发生则删除无用元素
        if (w != size) {
            // clear to let GC do its work
            for (int i = w; i < size; i++)
                elementData[i] = null;
            modCount += size - w;
            size = w;
            modified = true;
        }
    }
    return modified;
}

如果使用正向逻辑
    for (; r < size; r++)
        if (c.contains(elementData[r]) == complement) {
        } else {
            elementData[w++] = elementData[r];
        }

批量修改代码如上,可以看出来并不是简单的循环调用remove,而是采用了新的方法,代码看起来可能比较枯燥,下面是我发挥我灵魂画手的能力,画了一个步骤图。
batchRemove.png
他的整体思路也是将有用元素提前,并在结束以后将无用元素置空

迭代器

private class Itr implements Iterator<E>

并发修改异常

当使用迭代器遍历集合,在此过程中对集合中元素进行修改则会抛出
对于删除元素,可以使用ArrayList迭代器中实现的remove方法进行删除

增强for中删除元素一定会抛并发修改异常吗?
不一定,当删除的元素位于集合的倒数第二位时,不会抛异常

public static void test14(){
    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    list.add(4);
    for (Integer integer : list) {
        if (3 == integer) {
            list.remove(integer);
        }
    }
    System.out.println(list);//[1, 2, 4]
}

属性

  • int cursor;//要返回的下一个元素的索引(游标)
  • int lastRet = -1; //返回最后一个元素的索引; -1如果没有这样的话
  • int expectedModCount = modCount;

方法

  • public boolean hasNext()
  • public E next()
  • public void remove()

fail-fast

在系统设计中,快速失效系统一种可以立即报告任何可能表明故障的情况的系统。快速失效系统通常设计用于停止正常操作,而不是试图继续可能存在缺陷的过程。这种设计通常会在操作中的多个点检查系统的状态,因此可以及早检测到任何故障。快速失败模块的职责是检测错误,然后让系统的下一个最高级别处理错误。

最后

还是一如既往感谢大家的阅读,如果觉得写得还凑活的话,不妨点个赞哈~


怕翻船的忒修斯
74 声望36 粉丝

talk is cheap,show me the bug