• ArrayList源码解析


    1. 引言

    上个月去一家公司面试 java 实习生,面试官说的一句话我记得很清楚 作为一个java 工程师,你不去看源码是很难提高的。通过看源码,不仅可以更快的解决问题,而且可以直接接触到大牛写的代码,了解他们的设计思想,是有很大的好处的。 虽然后面由于种种原因把实习给辞了,不过面试官给的建议我认为是有用的,从今天开始了解一下 JDK 中集合框架的源码,以此提升自己。

    查看 JDK 版本为 1.8。另外,由于本人只是水平有限,因此博客中有问题欢迎大家指出。

    2. 概要

    ArrayList 底层的数据结构很简单,就是利用数组来完成的。在源码中是这样定义的:transient Object[] elementData; ,根据构造函数的不同,其初始化方式也不同。其默认大小为 10。

    这里为什么要使用 transient 关键字修饰呢?通过网上查找资料了解到,elementData 数组中的元素并不总是满的,例如数组长度为 10 而存储的元素只有五个。ArrayList 自定义了自己的 wirteObject 方法,保证只有这五个元素被序列化。

    还需要注意的一点是,ArrayList 不是线程安全的,在多线程条件下,可以考虑使用 Collections.synchronizedList(List list) 来返回一个线程安全的 ArrayList。也可以使用 concurrent 包下的 CopyOnWriteArrayList 类。

    3. 常用 API

    3.1 扩容

    ArrayList 底层虽然是使用数组存储数据,但我们并不需要担心会发生数组越界的情况。每次添加元素时,内部都会执行一个方法 ensureCapacityInternal(int minCapacity) ,传入的参数为当前数组长度+1,即现在 ArrayList 需要的最小容量,判断添加一个元素之后是否会存在数组越界的情况。

    private void ensureCapacityInternal(int minCapacity) {
       if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }
    

    ensureExplicitCapacity 函数中,比较 ArrayList 需要的最小容量和当前数组的长度大小,判断是否进行扩容,如果需要的话,则调用 grow 方法。通过查看源代码,发现,它扩容后的大小一般为原数组的 1.5 倍。当然数组的长度也是有限的,它的最大长度为 Integer.MAX_VALUE - 8

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

    3.2 添加元素

    了解了 ArrayList 的扩容过程,那么添加元素这个函数就变得简单了。其实现如下,其中 size 表示当前 ArrayList 元素的个数,添加元素直接对下标为 index 的位置赋值。

    public boolean add(E e) {
         ensureCapacityInternal(size + 1);  
         elementData[size++] = e;
         return true;
     }
    

    3.3 删除元素

    在 ArrayList 中,有两种删除元素的方法

    一个是通过角标:public E remove(int index)。其思路是找到指定位置,通过 Systemarraycopy 方法将下标 index 后的元素复制到原数组中直接覆盖下标为 index 的元素,以此达到删除的效果。

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

    另一个是直接删除指定的元素:public boolean remove(Object o)。删除指定元素,第一步先判断 o 对象是否为 null,如果是的话,删除数组中值为 null 的元素,如果不是,则删除指定的元素。其最终删除的方法也是通过赋值数组来完成,这里就不再重复。

    3.4 修改元素、查找元素

    修改元素与查找元素实现的原理差不多,其中应该注意的是在修改或者查找元素之前,应该先判断下需要查找的下标是否超出数组的长度,如果是的话,就应该抛出一个异常。ArrayList 是通过 rangeCheck 来判断的。

    public E set(int index, E element) {
        rangeCheck(index);
    
        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }
    
  • 相关阅读:
    java foreach遍历的前提条件
    Java中的null值总结
    mybatis不可忽略的细节
    设计模式:创建型模式
    设计模式(四):原型模式
    设计模式(三):建造者模式
    设计模式(二):单例模式(DCL及解决办法)
    设计模式(一):简单工厂、工厂模式、抽象工厂
    定时任务 ScheduledExecutorService
    快速访问GitHub
  • 原文地址:https://www.cnblogs.com/firepation/p/9448065.html
Copyright © 2020-2023  润新知