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自带的序列化机制,就可以保证只序列化实际存储的那些元素,而不是整个数组,从而节省空间和时间。

  • 相关阅读:
    对象池使用时要注意几点
    Flash3D学习计划(一)——3D渲染的一般管线流程
    714. Best Time to Buy and Sell Stock with Transaction Fee
    712. Minimum ASCII Delete Sum for Two Strings
    647. Palindromic Substrings(马拉车算法)
    413. Arithmetic Slices
    877. Stone Game
    338. Counting Bits
    303. Range Sum Query
    198. House Robber
  • 原文地址:https://www.cnblogs.com/java-chen-hao/p/9566499.html
Copyright © 2011-2022 走看看