Java ArrayList源码分析(基于JDK1.8)--------------------破晓

Java ArrayList源码分析(基于JDK1.8)

文章目录

  • Java ArrayList源码分析(基于JDK1.8)
    • 1.成员及构造方法
    • 2.紧缩容器和扩容部分
      • 2.1 紧缩容器
      • 2.2 ArrayList的扩容*(重点)
    • 3. 剩余源码分析
    • 4 迭代器
    • 5 关于public void forEachRemaining(Consumer consumer)这个方法的例子

1.成员及构造方法

 private static final long serialVersionUID = 8683452581122892189L;

    /**
     * ArrayList底层实现本质是数组,所以有一个默认容量。
     */
    private static final int DEFAULT_CAPACITY = 10;
	
	//为什么有一个空容器,一个默认大小容器,目的是给真正的容器一个身份,在扩容时有一定的作用具体看扩容部分的分析
	
    /**
     * 空容器
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
  	默认大小空容器
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
    这个是ArrayList扩容时真正的容器
    transient这个关键字意思是在该对象序列化时,该成员不进行序列化。
     */
    transient Object[] elementData; 

    /**
     elementData数组中拥有的元素数目。(并不是数组的容量)
     */
    private int size;

    /**
     *参数是一个数量,这样可以构造一个指定数量的容器
     *如果参数是0,element容器被赋值为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);
        }
    }

    /**
     * 无参构造
     * 使容器的大小为默认的大小——10
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * 该构造方法是将任意一个最终父类为Collection接口,并且泛型是该ArrayList泛型的派生类的容器转换成ArrayList容器
     */
     
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();//将参数c转换为数组并赋值给容器elementData
        if ((size = elementData.length) != 0) {//如果elementData数据拥有元素
            // c.toArray是将容器中的数据以数组的形式返回。当然会存在数组的类型不是Object[]的情况
            if (elementData.getClass() != Object[].class)//如果参数容器的类型不是Object类型
              //将elementData数组中的元素拿出来放置到元素类型为Object的数组中,并返回。
                elementData = Arrays.copyOf(elementData, size, Object[].class);
              
        } else {
            //c容器没有一个元素,就是一个空容器,
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

从上面的源码我们可以得知

  1. elementData成员是一个Object[] 类型的数组,用来存储加入到ArrayList中的元素,它的初始化用的是懒加载的方式
  2. 在执行完ArrayList的构造方法后,elementData可能会有三种身份EMPTY_ELEMENTDATA ,DEFAULTCAPACITY_EMPTY_ELEMENTDATA ,两个都不是。

2.紧缩容器和扩容部分

2.1 紧缩容器

 /**
 功能:紧缩容器,使容器大小和元素数量相等,可以减少ArrayList所占用的空间
  */
    public void trimToSize() {
        modCount++;//计数-----数组容器每被操作一次,这个数字就会加1
        if (size < elementData.length) {//当前元素的数量小于容器的大小,需要紧缩
            elementData = (size == 0)//如果当前容器的元素数量为0,直接把数组容器赋值为一个空容器
              ? EMPTY_ELEMENTDATA    
              : Arrays.copyOf(elementData, size);//将elementData中的size个元素,拷贝到一个容量为size的容器中
        }
    }

2.2 ArrayList的扩容*(重点)

ArrayList的扩容应该是ArraytList比较复杂和有亮点的一部分

/*
		原著:如果,则增加此ArrayList实例的容量
		必要时,以确保它至少可以容纳元素的数量由最小容量参数指定。
		@param限制所需的最小容量												
     */
     /*
     功能:判断他有没有扩容的价值
     */
    public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            // any size if not default element table
            ? 0
            // larger than default for default empty table. It's already
            // supposed to be at default size.
            : DEFAULT_CAPACITY;
														
        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }
		//如果是默认容器,返回默认值和元素个数作比较返回最大的一个数作为扩容参考数
		//如果不是默认容器返回元素数
		//这个方法的目的是确定准确的扩容数量。
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);// 绝对是返回一个大于等于10的数
        }
        return minCapacity;
    }

    private void ensureCapacity (int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    /**
     * The maximum size of array to allocate.
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;


    /**
			容器扩容1.5倍,但是不能超出最大范围 Integer.MAX_VALUE - 8
			   
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;//得到老容器的长度 
        int newCapacity = oldCapacity + (oldCapacity >> 1);//进行1.5倍扩容,这块有种特殊情况,0和1的1.5倍都为它们本身;
        //newCapacity的初值为老容器的1.5倍,这以把这个1。5倍的值理解为最小扩容容量,如果参数minCapacity的值比newCapacity的值大,那么minCapacity将为扩容后的容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
            //但是扩容也不能任意的进行扩容,所以它规定了一个容量上限
            //当扩容量大于规定值时;返回一个有符号int的最大值2^31 -1,完成扩容
            也就是说ArrayList的最大容量是 Integer.MAX_VALUE
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // Arrays.copyOf(elementData, newCapacity);将elementData数组中的元素从第一个开始的	newCapacity个元素放置到一个容量为newCapacity的数组中,并返回这个数组。
        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;
    }

3. 剩余源码分析

本来想把所有的方法分成好几部分来说,我发现后面的不太好分,就不分了
ArrayList类实现了List接口,而List实现了Collection接口
Java ArrayList源码分析(基于JDK1.8)--------------------破晓_第1张图片总体来说Collection接口包含了Set和List这类容器共有的一些基本操作
上述内容作为了解性内容,我们接着看源码

 	/**
    返回该ArrayList中的元素个数  
     */
    public int size() {
        return size;
    }

    /**
    返回值为boolean类型,返回true代表该容器为空
     */
    public boolean isEmpty() {
        return size == 0;
    }

    
    /**
     原理:内部本质调用了indexOf()
     如果某个对象存在于ArrayList中,那indexOf的返回值一定是大于等于0的
     */
     public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

    /**
     功能:得到ArrayList中指定对象所在的下标
     当参数为null时,就在数组中找到值为null的下标并进行返回
     当参数为对象实例时,是用equals方法比的。,如果没有重载equals方法,比的是首地址
     如果没有找到返回的是-1;
     */
    public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

    /**
	功能:这个方法和indexOf方法相比一个是从前往后遍历,一个是从后往前遍历
		  lastIndexOf是从后往前遍历
     */
    public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

    /**
    功能:这个方法顾名思义就是克隆的意思
    原理:ArrayList类中有两个重要的成员, 一个是elementData,另一个是size;
    			克隆就是把源elementData数组容器的前size个元素赋值给新的elementData,把源size赋值给新size
    
     */
    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            //copyOf这个方法将elementData容器中从第一个开始的size个元素放到一个大小为size的数组里,并返回,注意返回值的数组地址和elementData数组的地址是不同的
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }

    /**
     将ArrayList中elementData数组中的元素放在一个数组里返回,因为使用了copyOf方法,所以对返回值数组的任何操作都不会影响到elementData数组
     */
    public Object[] toArray() {
    	//将elementData数组中从第一个元素开始的size个元素放在一个大小正好为size的新数组中返回
        return Arrays.copyOf(elementData, size);
    }

	/*
	这个方法是toArray()方法的重载,只不过将结果放到了a数组中。这个方法我本人建议不要使用
	因为如果你没有看过源码的话,你传递的参数是一个容量很小的数组,执行完的结果就会变成a数组里什么也没有
	*/
    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
    	//参数a相当于一个容器,用来存放将elementData数据中的元素的拷贝
    	//如果a数组的大小不够用直接返回一个新的数组
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyf(elementData, size, a.getClass());
        //将源数组elementData中以下标为0开始的size的元素,复制到a数组以0开始位置中
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

    //返回elementData序列下标为index的值
    @SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
    }

    /**
     返回elementData序列中下表所对应的值
     */
    public E get(int index) {
        rangeCheck(index);//判断是否下标越界

        return elementData(index);
    }
 /**
   	功能:将指定下标中的元素替换成新的元素,并返回被替换的元素
   	原理:参数为,你要替换的下标index,和要替换成的新元素。
   		先判断下标越界,然后读出下标所对应的元素
   		把新元素放入其中
   		返回被替换的元素
     */
    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

    /**
     *add方法目的在于增加一个元素,将原有元素数量加1变为新的参数minCapCity,会有以下两种情况:1.第一次add并且是无参构造,2.除了第一种情况的所有情况
     			第一种情况:我们的容器elementData就会被懒加载成DEFAULTCAPACITY_EMPTY_ELEMENTDATA。这是就要判断minCapcity和10哪个大并返回大的值
     			第二种情况:我们的容器elementData不为DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     */
     
     /*
     功能给elementData序列增加一个新元素
     原理:因为elementData序列的容量可能不够,所以要先进行扩容,扩容后,将新元素放进去
					 添加成功后返回为true     
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    /**
     *给下标为index的位置增加一个元素,这个下标位置的元素和之后的元素统一向后移动一个元素
     和set()方法的区别是:set()替换对应下标位置的元素,而add有点类似于插入
     	如过一个数组,用它的总数量减去一个下标,就表示包含该下表在内的元素数数量
     	比如一个数组的总元素数量为5(size = 5),假设index= 1    5 - 1 = 4 包含该下标在内后面还有4个元素
     	
     	原理:先判断下标有没有越界,然后进行扩容
     	将包括index和以后的所有元素移到宜index + 1为开始
     	最后把新元素放入到elementData序列中以index为下标的空间。
     */
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //将elementData序列中的index为起始下标的size - index个元素赋值给elementData序列以index+ 1起始的位置,这里的 size - index意为数组元素的个数减去下标数,即代表从该下标起有多少个元素(包括该下标) 
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

    /**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).
     *
     * @param index the index of the element to be removed
     * @return the element that was removed from the list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
     
    /*
    删除下标所对应的节点,到时写博客时画个图
    */
    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;
    }

    /**
     功能:删除elementData序列中的一个对象;
     原理:如果要删除的对象为null,那就找到序列中的元素等于null,找到并删除
     				如果传进来的是一个真正的对象;
     	用的是equals方法来比较
     	成功删除返回的是true,否则为false			 
     */
    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;
    }

    /*
     对elementData数组中的元素进行移动,将下标index + 1开始的所有元素往前移动一个元素位置
     移动完,将最后一个元素赋值为null
     */
    private void fastRemove(int index) {
        modCount++;
        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
    }

    /**
     * 遍历一遍把数组里面的值全改成null
      并把size清零;
     */
    public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

    /**
    参数是基类为Collection的任何容器,但是该容器的泛型必须是ArrayList容器的泛型的派生类
    把参数化为数组,得到该数组的数量num,并对elementData进行扩容
    把数组以0为起始下标,数量为num个元素复制到elementData数组中以size为起始下标的位置中
    如果传入一个空容器,则返回false,否则true
     */
    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

    /**
    功能:这个方法就有点追加的的感觉将一个外来容器的全部元素追加到起始下标为index的空间,如果传进来的容器为空返回false,加进去后改变size的值
     */
    public boolean addAll(int index, Collection<? extends E> c) {
    	//进行下表判断
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount

        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

    /**
     删除一个范围,如果fromIndex = 1,toIndex = 3则删除下标为1和下标为2的数
     原理:把从toIndex开始往后的所有元素向前平移(toIndex - fromIndex) 为,得到删除后的元素数
     从这个数往后到size全部赋值为0
     最后更改size的值
     */															
    protected void removeRange(int fromIndex, int toIndex) {
        modCount++;   
        int numMoved = size - toIndex;
        System.arraycopy(elementData, toIndex, elementData, fromIndex,
                         numMoved);

        // clear to let GC do its work
        //newSize为删除后剩余的元素个数
        int newSize = size - (toIndex-fromIndex);
        for (int i = newSize; i < size; i++) {
            elementData[i] = null;
        }
        size = newSize;
    }

    /**
     下标越界判断:如果下标大于等于元素个数,就会发生下标越界
     */
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    /**
     * 增加所用的下标检查,如果下标大于size或者小于零报错
     */
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    /**
     * 返回下边越界的字符串
     */
    private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+size;
    }

    /**
   	删除elementData数组中与Collection c容器中相同的元素删掉
     */
    public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);//这个方法用于判定参数是否为null,为null报异常
        return batchRemove(c, false);//把elementData序列中与c序列相同的元素全部删除
    }

    /**
 	retain有包含的意思,这个和removeAll的区别是:是把elementData序列中与c序列不同的元素全部不删掉。  
     */
    public boolean retainAll(Collection<?> c) {
        Objects.requireNonNull(c);//这方法用来判断参数是不是null,为null报异常
        return batchRemove(c, true);
    }
    /*
				功能:这个方法的第二个参数比较讲究,可以为true也可以为false,

    当参数为false时,就把参数容器与elementData中一样的数据删掉
    当参数为true时,把两个容器不一样的数据删掉
    原理:当参数为false时:遍历elementData序列,找c序列中不包含的元素,把这些元素依次覆盖到elementData序列,并把之后的清除
    			当参数为true时, 遍历elementData,找到从容器中相同的元素,把这些元素依次覆盖到elementData序列,并把之后的清除    
   */
    private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        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.
            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;
    }

4 迭代器

//同过ArrayList对象调用得到迭代对象
 public Iterator<E> iterator() {
        return new Itr();
    }

    /**
     * An optimized version of AbstractList.Itr
     */
    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        Itr() {}
		
		//是否存在下一个元素
        public boolean hasNext() {
            return cursor != size;
        }
		
		//返回下标为cursor的元素
        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }
		//迭代器提供的删除方法,用于删除当前迭代对象
        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
		//传入一个Consumer接口的实现类,实现accept方法,该方法的内容是提供了对遍历出元素的一个处理内容,其实并没有大家想的那么神秘,仅仅是提供了一个接口,你把具体的操作实现进去。
         @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            //完成一遍遍历
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }
		/*
		我们知道ArrayList是一个非线程安全的容器,在遍历非线程安全的额容器时尽量使用迭代器,并不是因为它是线程安全的,而是因为当你便利时,存在其他线程更改这个ArrayList时,迭代器会报错
		*/
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

5 关于public void forEachRemaining(Consumer consumer)这个方法的例子

 package com.mec.studySource;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.function.Consumer;

public class Test {

	public static void main(String[] args) {
		ArrayList list = new ArrayList<Integer>();
		list.add(1);
		list.add(2);
		list.add(3);
		list.add(4);
		Iterator<Integer> iterator = list.iterator();
		Consumer<Integer> consumer = new Consumer<Integer>() {
			
			@Override
			public void accept(Integer t) {
				System.out.println("num: " + t);
				
			}
		};
		iterator.forEachRemaining(consumer);
	}
		
	

}

或者大家嫌弃这样书写麻烦。可以采用匿名函数的写法(x)-> {}; "->"左边为参数,右边为函数体

package com.mec.studySource;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.function.Consumer;

public class Test {

	public static void main(String[] args) {
		ArrayList list = new ArrayList<Integer>();
		list.add(1);
		list.add(2);
		list.add(3);
		list.add(4);
		Iterator<Integer> iterator = list.iterator();
		Consumer<Integer> consumer = (x)->{System.out.println("num: " + x);};
		
		iterator.forEachRemaining(consumer);
	}
}

运行结果:
Java ArrayList源码分析(基于JDK1.8)--------------------破晓_第2张图片

你可能感兴趣的:(Java ArrayList源码分析(基于JDK1.8)--------------------破晓)