zoukankan      html  css  js  c++  java
  • JDK1.8源码(一)——java.util.ArrayList

    ArrayList 定义

    ArrayList 是一个用数组实现的集合,支持随机访问,元素有序且可以重复。

    1 public class ArrayList<E> extends AbstractList<E>
    2         implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    View Code

     

     蓝色实线箭头是指Class继承关系

     绿色实线箭头是指interface继承关系

     绿色虚线箭头是指接口实现关系

     由上可知ArrayList继承AbstractList 并且实现了List和RandomAccess,Cloneable, Serializable接口。

    、实现 List 接口

      List 接口定义了实现该接口的类都必须要实现的一组方法,如下所示,下面我们会对这一系列方法的实现做详细介绍。

    字段属性

    //集合默认大小
    private static final int DEFAULT_CAPACITY = 10;
    //空的数组实例
    private static final Object[] EMPTY_ELEMENTDATA = {};
    //这也是一个空的数组实例,和EMPTY_ELEMENTDATA空数组相比是用于了解添加元素时数组膨胀多少
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    //存储 ArrayList集合的元素,集合的长度即这个数组的长度
    //1、当 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 时将会清空 ArrayList
    //2、当添加第一个元素时,elementData 长度会扩展为 DEFAULT_CAPACITY=10
    //3、transient修饰表示序列化对象的时候,这个属性就不会序列化到指定的目的地中;通过方法来手动序列化,下面讲解
    transient Object[] elementData; 
    //表示集合的长度
    private int size;
    View Code

    构造函数

    ①、无参构造函数

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    View Code

      此无参构造函数将创建一个 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 声明的数组,注意此时初始容量是0,而不是大家以为的 10。

      注意:根据默认构造函数创建的集合,ArrayList list = new ArrayList();此时集合长度是0.

    ②、给定大小有参构造函数

     1     public ArrayList(int initialCapacity) {
     2         if (initialCapacity > 0) {
     3             this.elementData = new Object[initialCapacity];
     4         } else if (initialCapacity == 0) {
     5             this.elementData = EMPTY_ELEMENTDATA;
     6         } else {
     7             throw new IllegalArgumentException("Illegal Capacity: "+
     8                                                initialCapacity);
     9         }
    10     }
    View Code

      初始化集合大小创建 ArrayList 集合。当大于0时,给定多少那就创建多大的数组并赋给赋给elementData;当等于0时,将空的数组实例EMPTY_ELEMENTDATA 赋给elementData;当小于0时,抛出异常。

    ③、泛型参数有参构造函数

    public ArrayList(Collection<? extends E> c) {
        //将collection对象转换成数组,然后将数组的地址的赋给elementData。
        elementData = c.toArray();
        //更新size的值,并且如果size大于0
        if ((size = elementData.length) != 0) {
            if (elementData.getClass() != Object[].class)
                //把collection对象的内容copy到elementData中
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            //size等于0直接将空对象EMPTY_ELEMENTDATA的地址赋给elementData
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
    View Code

    Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素。?是“任意类”的意思,extends继承不多说,E是指定类型。

      用法举例:

    List stu=new ArrayList<Student>();
    stu.add(new Student());
    List students=new ArrayList<Student>(stu);
    View Code

    添加元素

    public boolean add(E e) {
        //添加元素之前,首先要确定集合的大小,如果集合不够装则需要扩容
        //第一次size=0
        ensureCapacityInternal(size + 1);
        //集合size位置赋值,先引用size的值,再对size自增
        elementData[size++] = e;
        return true;
    }
    View Code
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //第一次添加,空集合,10和1取最大值,所以第一次添加元素初始集合length为10
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //扩容判断
        ensureExplicitCapacity(minCapacity);
    }
    View Code
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        //如果size+1大于集合的大小,说明刚好集合已经满了,则需要扩容
        //size 是集合实际元素个数,elementData.length是集合长度,元素没满,用null填满
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    View Code
    private void grow(int minCapacity) {
        //集合的容量
        int oldCapacity = elementData.length;
        //>>1表示右移位,相当于除以2的n次方,这里除以2的1次方,相当于容量增加原来容量的一半
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            //扩容一半后还比minCapacity小,那就只有oldCapacity=0的情况了,也就是第一次添加元素,集合大小为10
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            //扩容容量大于最大集合大小
            newCapacity = hugeCapacity(minCapacity);
            //数组的复制
            elementData = Arrays.copyOf(elementData, newCapacity);
    }
    View Code
    private static int hugeCapacity(int minCapacity) {
        //Integet.MAX_VALUE+1时就会溢出,这时会变成负数,Integer.MAX_VALUE+1=Integer.MIN_VALUE
        if (minCapacity < 0)
            throw new OutOfMemoryError();
        //size+1比最大集合大小还大就取最大整数,否则取最大集合大小
        //所以先扩容到MAX_ARRAY_SIZE,下一次扩容就扩容到Integer.MAX_VALUE 
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
    View Code
    public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        // 判断类型是否为Object,是Object,生成一个大小为newLength类型为Object的数组实例
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);// 类型不为Object,通过反射重新生成一个大小为newLength的新类型数组实例
        ///将原数组内容拷贝到新数组中,新数组取最小的数组长度,这里指copy的数组的长度,是原数组的长度
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }
    //native关键字表示系统方法,Java可以通过JNI来调用其他语言(主要还是C/C++语言)编写的方法
    /*Object src : 原数组
    int srcPos : 从元数据的起始位置开始
    Object dest: 目标数组
    int destPos: 目标数组的开始起始位置
    int length : 要copy的数组的长度
    */
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);
    View Code

      ①、当通过 ArrayList() 构造一个空集合,初始长度是为0的,size也为0,第 1 次添加元素,会创建一个长度为10的数组,并将该元素赋值到数组的第一个位置,下标为0的位置。

      ②、第 2 次添加元素,集合不为空,而且由于集合的长度size+1是小于数组的长度10,所以直接添加元素到数组的第二个位置,不用扩容。

      ③、第 11 次添加元素,此时 size+1 = 11,而数组长度是10,这时候创建一个长度为10+10*0.5 = 15 的数组(扩容1.5倍),然后将原数组元素引用拷贝到新数组。并将第 11 次添加的元素赋值到新数组下标为10的位置。

      ④、当第一次扩容容量 oldCapacity + (oldCapacity >> 1)大于最大集合大小Integer.MAX_VALUE - 8时,判断size+1和Integer.MAX_VALUE - 8的大小,第一次size+1肯定小,这时扩容到Integer.MAX_VALUE - 8

      ⑤、当集合size=Integer.MAX_VALUE - 8时,然后添加元素时,创建一个大小为 Integer.MAX_VALUE 的数组,在进行元素添加。

      ⑥、第 Integer.MAX_VALUE + 1 次添加元素时,抛出 OutOfMemoryError 异常。

      注意:能向集合中添加 null 的,因为数组可以有 null 值存在。

    查找元素

    public E get(int index) {
        //检查索引是否越界,越界则抛出异常
        rangeCheck(index);
        //直接返回处于该下标位置的数组元素
        return elementData(index);
    }
    View Code

    修改元素

    public E set(int index, E element) {
        //判断索引是否越界
        rangeCheck(index);
        //获取原数组指定索引的元素
        E oldValue = elementData(index);
        //将指定索引处的元素替换为 element
        elementData[index] = element;
        return oldValue;//返回原数组索引元素
    }
    View Code

    删除元素

    ①、通过索引位置删除

    public E remove(int index) {
        rangeCheck(index);//判断给定索引的范围,超过集合大小则抛出异常
        modCount++;
        E oldValue = elementData(index);//得到索引处的删除元素
        int numMoved = size - index - 1;
        if (numMoved > 0)//size-index-1 > 0 索引处不是最后一个元素
            //将数组elementData 的下标index+1之后长度为 numMoved的元素拷贝到从index开始的位置
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; //将数组最后一个元素置为 null
        //返回删除的元素
        return oldValue;
    }
    View Code

    、直接删除指定元素

    public boolean remove(Object o) {
        if (o == null) {//如果删除的元素为null
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    //return第一表示返回值,第二表示中止函数往下执行
                    return true;
                }
        } else {//不为null,通过equals方法判断对象是否相等
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
    //和通过索引删除元素方法相同
    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
    }
    View Code

    remove(Object o)方法是删除第一次出现的该元素。然后通过System.arraycopy进行数组自身拷贝。

    注意并不是删除所有该元素

    遍历集合

      ①、迭代器 iterator

      用法:

    ArrayList<String> list = new ArrayList<>();
    list.add("1");
    list.add("2");
    list.add("3");
    Iterator<String> it = list.iterator();
    while(it.hasNext()){
        String str = it.next();
        System.out.print(str+" ");
    }
    View Code

    interator方法返回一个 Itr 对象,这个类是 ArrayList 的内部类。

    public Iterator<E> iterator() {
        return new Itr();
    }
    View Code
    private class Itr implements Iterator<E> {
        int cursor; //游标,下一个要返回的元素的索引
        int lastRet = -1; //上一次返回元素的索引; 如果没有这样的话返回-1.
        int expectedModCount = modCount;//这个值是在用户调用ArrayList的iterator方法时候确定的
    
        public boolean hasNext() {
            //通过 cursor != size 判断是否还有下一个元素
            //下面每次获取一个元素cursor都要+1
            return cursor != size;
        }
    
        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];//返回索引为i处的元素,并将 lastRet赋值为i
        }
    
        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();
    
            try {
                ArrayList.this.remove(lastRet);//调用ArrayList的remove方法删除元素
                cursor = lastRet;//游标指向删除元素的位置,本来是lastRet+1的,这里删除一个元素,然后游标就不变了
                lastRet = -1;//lastRet恢复默认值-1,因为上一个返回的元素已经删除了
                expectedModCount = modCount;//expectedModCount值和modCount同步,因为进行add和remove操作,modCount会加1
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    
        @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();
        }
        //expectedModCount这个值是在用户调用ArrayList的iterator方法时候确定的
        //前面在新增元素add() 和 删除元素 remove() 时,我们可以看到 modCount++。修改set() 是没有的
        //也就是说不能在迭代器进行元素迭代时进行增加和删除操作,否则抛出异常
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
    View Code

    逻辑上讲,迭代时可以添加元素,但是一旦开放这个功能,很有可能造成很多意想不到的情况。
    比如你在迭代一个ArrayList,迭代器的工作方式是依次返回给你第0个元素,第1个元素,等等,假设当你迭代到第5个元素的时候,你突然在ArrayList的头部插入了一个元素,使得你所有的元素都往后移动,于是你当前访问的第5个元素就会被重复访问。
    java认为在迭代过程中,容器应当保持不变。因此,java容器中通常保留了一个域称为modCount,每次你对容器修改,这个值就会加1。当你调用iterator方法时,返回的迭代器会记住当前的modCount,随后迭代过程中会检查这个值,一旦发现这个值发生变化,就说明你对容器做了修改,就会抛异常。  

     迭代时不能调用 ArrayList.remove() 方法,可以调用 迭代器的 remove() 方法删除元素:

    ArrayList<String> list = new ArrayList<>();
    list.add("a");
    list.add("b");
    list.add("c");
    Iterator<String> it = list.iterator();
    while(it.hasNext()){
        String str = it.next();
        System.out.print(str+" ");
        list.remove(str);//集合遍历时进行删除或者新增操作,都会抛出 ConcurrentModificationException 异常
        //list.add(str);
        list.set(0, str);//修改操作不会造成异常
        it.remove();//调用迭代器内部删除方法
    }
    View Code

    序列化

    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        int expectedModCount = modCount;
        s.defaultWriteObject();
        s.writeInt(size);
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }
    View Code
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        elementData = EMPTY_ELEMENTDATA;
        s.defaultReadObject();
        s.readInt(); 
        if (size > 0) {
            ensureCapacityInternal(size);
            Object[] a = elementData;
            for (int i=0; i<size; i++) {
                a[i] = s.readObject();
            }
        }
    }
    View Code

     ArrayList在序列化的时候会调用writeObject,直接将size和element写入ObjectOutputStream;反序列化时调用readObject,从ObjectInputStream获取size和element,再恢复到elementData。假如elementData的长度为10,而其中只有5个元素,那么在序列化的时候只需要存储5个元素,而数组中后面5个元素是不需要存储的。于是将elementData定义为transient,避免了Java自带的序列化机制,就可以保证只序列化实际存储的那些元素,而不是整个数组,从而节省空间和时间。

  • 相关阅读:
    洛谷P3620 [APIO/CTSC 2007] 数据备份
    洛谷P2744 量取牛奶
    洛谷P1560 蜗牛的旅行
    luogu P1776 宝物筛选_NOI导刊2010提高(02)
    luogu P1020 导弹拦截
    luogu P2015 二叉苹果树
    luogu P1137 旅行计划
    树形dp瞎讲+树形dp基础题题解
    luogu P1252 马拉松接力赛 P1803 凌乱的yyy / 线段覆盖
    luogu P1196 [NOI2002]银河英雄传说
  • 原文地址:https://www.cnblogs.com/java-chen-hao/p/9566499.html
Copyright © 2011-2022 走看看