java基础--------ArrayList源码分析

在java中我们或多或少都使用过ArrayList集合,但是它背后的工作原理到底是怎样的呢?今天我和大家一起进入ArrayList的家(源码)来学习。

在进入ArrayList源码之前,我们需要了解什么是顺序存储,因为ArrayList的进行存储时使用的就是顺序存储,大家可以去我的博客数据结构—顺序存储了解下。

使用ArrayList的步骤:
1、需要使用ArrayList<E> list = new ArrayList<>();初始化ArrayList,得到其对象
2、使用得到的该对象进行一波猛如虎的增删改查等操作

(一)获取ArrayList对象

1、整数构造
transient Object[] elementData;
private static final Object[] EMPTY_ELEMENTDATA = {};
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);
        }
    }

从源码中可以看到,使用整数构造获取对象时,首先是对参数判断,如果传入的整数参数大于0,那么创建一个新的Object数组,其中this.elementData是对应的源码是transient Object[] elementData;transient的作用是标注其变量不参与序列化过程。参数等于0时elementData等于空数组,其他情况的话抛出异常

2、无参构造
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
 public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
3、集合构造
public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

c.toArray()的作用是将集合转化成数组,elementData.getClass() != Object[].class的作用是判断c.toArray()转化的数组是否为Object类型(因为传入自定义的ArrayList可能c.toArray()返回的不是Object类型),如果不是则使用Arrays.copyOf(elementData, size, Object[].class);重新拷贝,不过它的最终目的都是为了返回Object类型的数组。其方法对应的源码是

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")

> /**三目运算,newType是否是Object类型,如果是copy =
new Object[newLength],否则copy=Array.newInstance(newType.getComponentType(), newLength)**/

        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;
    }
 public static Object newInstance(Class<?> componentType, int length)
        throws NegativeArraySizeException {
        return newArray(componentType, length);
    }
private static native Object newArray(Class<?> componentType, int length)
        throws NegativeArraySizeException;

其中native关键字修饰的方法说明该方法是原生态方法,它的实现不是在当前文件中,而是在用其它语言(如c或c++)实现的文件。这样做的原因是因为java语言本身不能对操作系统底层进行操作和访问的,它必须通过JNI接口调用其它语言来实现对操作系统底层的访问。

(二)添加数据

1、add(E e)方法
 private int size;
 private static final int DEFAULT_CAPACITY = 10;
 //将指定的元素追加到列表的末尾
 public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
 }
  
 private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
  }
//对elementData数组初始化
 private static int calculateCapacity(Object[] elementData, int minCapacity) {
	//如果使用的是无参构造,返回的初始值为10
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
    }
//进行下标判断
 private void ensureExplicitCapacity(int minCapacity) {
     modCount++;
     // 下标是否溢出,溢出则扩容
     if (minCapacity - elementData.length > 0)
         grow(minCapacity);
    }  
 //扩容
 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
 private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        // >>右移符号,>>1相当于除于2的1次方
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //如果添加的数组下标比它原来数组长度的1.5倍还要大
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
         //如果扩容后的大小大于MAX_ARRAY_SIZE的值
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 将elementData复制到扩容后的数组中,并elementData引用扩容后的数组
        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;
  }

add(E e)方法中使用calculateCapacity(Object[] elementData, int minCapacity)来初始化数组elementData容量,使用ensureExplicitCapacity(int minCapacity)对数组中的容量进行判断,判断是否需要扩容; grow(minCapacity)是扩容的具体实现方法,oldCapacity + (oldCapacity >> 1)表示将数组的长度增加为原来的1.5倍,如果添加的数组比它容量的1.5倍大但是比Integer.MAX_VALUE-8小,那么它的容量就是它本身

2、add(int index, E element)方法
//将指定的元素插入其中的指定位置
public void add(int index, E element) {
		//elementData数组下标的判断
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //留index位置出来
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
 //判断index是否符合条件
private void rangeCheckForAdd(int index) {
//如果插入的位置小于o或者大于数组elementData的长度则抛出异常
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

rangeCheckForAdd(index)方法判断插入的位置是否符合条件,ensureCapacityInternal(size + 1)方法进行数组的初始化和判断是否需要扩容,具体的分析请看上面add(E e)方法的的分析,
System.arraycopy(elementData, index, elementData, index + 1,size - index);的作用是将数组elementData中index位置后的数据后移一位(将index位置空出来)

3、addAll(Collection<? extends E> c)方法
public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();   //将集合c转化成数组并将其赋给数组a
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

(三)删除数组

1、remove(int index)方法
public E remove(int index) {
		//判断index是否符合条件
        rangeCheck(index);

        modCount++;
        //oldValue等于elementData数组中index的数据
        E oldValue = elementData(index);
		//得到index之后的个数
        int numMoved = size - index - 1;
        if (numMoved > 0)
        //将index后面的数组前移一位
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }
 //判断index是否符合条件
private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
 //返回elmentData中index的数据
 E elementData(int index) {
        return (E) elementData[index];
 }
2、remove(Object o)方法
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;
    }
 private void fastRemove(int index) {
        modCount++;
        //得到index数据之后的个数
        int numMoved = size - index - 1;
        if (numMoved > 0)
        //将index之后的数据前移一位
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }
3、clear() 方法
public void clear() {
        modCount++;
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

ArrayList删除数据有3个方法,它们分别是根据下标index删除、根据数据Object删除和全部删除,前两个方法删除数据的原理都是一样的,都是将从index后面的数据前移一位,只不过第二个方法(remove(Object o))比第一个方法(remove(int index))多了查询index的过程而已,第三个方法删除数据的原理很简单,将elementData中的全部数据修改为null,并且长度为0

(四)修改数据

public E set(int index, E element) {
        rangeCheck(index);
		//获取elementData数组中index位置的数据
        E oldValue = elementData(index);
        //将elementData数组中index位置的数据修改为element
        elementData[index] = element;
        return oldValue;
    }

(五)查询数据

public E get(int index) {
        rangeCheck(index);
        //返回下标为index的数据
        return elementData(index);
    }

以上是我对ArrayList中增、删、改、查的分析,如有不足请大家指出,谢谢(*^_^*)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值