zoukankan      html  css  js  c++  java
  • ArrayList源码解读

    一、前言

       一直都只是会用ArrayList,对于ArrayList的理解都比较简单。正好借此机会也把自己观看源码的一些理解写出来。方便自己以后回顾。使用的版本是JDK1.8。

    二、ArrayList特点

    随机访问速度快,插入和移除性能较差(数组的特点),支持null元素,有顺序,元素可以重复,线程不安全。

    三、数组扩容所使用到的API

     1、直接将数组容量扩容至一个固定值。生成一个新的数组

       Object[] objs = {1, 2};
          System.out.println(objs.length);
          Object[] objects = Arrays.copyOf(objs, 10);
         System.out.println(objects.length);
    

    2、将原数组从起始位置开始到复制的长度,替换目标数组从目标起始位置到替换的长度。

    //原始数组
    int [] arr={1,2,3,4,5,6,7,8};
    for (int i : arr) {
        System.out.print(i+",");
    }
    System.out.println();
    //目标数组
    int[] arr1=new int[11];
    arr1[0]=1;
    arr1[1]=2;
    arr1[2]=3;
    //参数  原数组 ,起始位置 ,目标数组 ,目标的起始位置 , 复制的长度   这是一个本地方法
    System.arraycopy(arr,0,arr1,3,8);
    //从起始位置复制,替换目标起始位置,需要复制的长度
    for (int i : arr1) {
        System.out.print(i+",");
    }
    

    结论:从arr数组0索引开始,copy到arr1数组从索引为3的开始。长度为8。

    四、ArrayList的继承实现关系

    public class ArrayList<E> extends AbstractList<E>  implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    
    • ArrayList实现了List接口是一个数组队列拥有了List基本的增删改查功能
    • ArrayList实现了RandomAccess接口拥有随机读写的功能
    • ArrayList实现了Cloneable接口可以被克隆
    • ArrayList实现了Serializable接口并重写了序列化和反序列化方法,使得ArrayList可以拥有更好的序列化的性能

    五、ArrayList类属性

    //序列版本号
    private static final long serialVersionUID = 8683452581122892189L;
    //默认数组容量
    private static final int DEFAULT_CAPACITY = 10;
    //当指定数组的容量为0时使用这个常量赋值
    private static final Object[] EMPTY_ELEMENTDATA = {};
    //默认空参构造函数时使用这个常量赋值
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    //真正存放数据的对象数组,transient标识不被序列化
    transient Object[] elementData;
    //数组中的真实元素个数,该值小于或等于elementData.length
    private int size;
    // 最大数组容量
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    //记录修改次数    继承于AbstractList
    protected transient int modCount = 0;
    

    六、ArrayList构造方法

    //初始化设置数组集合长度   
     public ArrayList(int initialCapacity) {
            if (initialCapacity > 0) {
                this.elementData = new Object[initialCapacity];//初始长度大于0,就直接初始化数组容量
            } else if (initialCapacity == 0) {
                this.elementData = EMPTY_ELEMENTDATA;//默认复制一个空数组
            } else {
                throw new IllegalArgumentException("Illegal Capacity: "+
                                                   initialCapacity);//报错
            }
        }
    
      public ArrayList() {
    //默认空数组   JDK1.7之后初始化的时候不指定大小,那么集合初始大小为0
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
    
      public ArrayList(Collection<? extends E> c) {
            elementData = c.toArray();//转化为数组
            if ((size = elementData.length) != 0) {//非空集合
                if (elementData.getClass() != Object[].class)//是否转化为Object类型数组
                    elementData = Arrays.copyOf(elementData, size, Object[].class);//不为Object类型数组就进行复制
            } else {
                this.elementData = EMPTY_ELEMENTDATA;//空集合,初始为空数组
            }
        }
    

    七、add方法

     public boolean add(E e) {
            ensureCapacityInternal(size + 1); //真实数组元素加1  调用ensureCapacityInternal方法
            elementData[size++] = e;   //进行处理之后,将值复制到索引为size的位置上     
            return true;
        }
    
        private void ensureCapacityInternal(int minCapacity) {
            ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
        }
    
      //判断
        private static int calculateCapacity(Object[] elementData, int minCapacity) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                //空数组,返回两个值最大的一个
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            }
            //不是空数组直接返回minCapacity
            return minCapacity;
            }
    
        private void ensureExplicitCapacity(int minCapacity) {
            modCount++; //修改统计数+1
             // 判断数组真实元素个数加1后的长度与当前数组长度大小关系,如果小于0,返回,如果大于0就调用grow()方法
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
    
    
        private void grow(int minCapacity) {
           //原始数组的长度
            int oldCapacity = elementData.length;
             // 新增数组容量大小    (oldCapacity >> 1)=oldCapacity/2
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            //这个地方有一个面试题,如果初始数组的长度1,增加的元素超过1个,那么上面计算的新数组大小也是为1的。这个判断就是如果新数组小于minCapacity,最少保证newCapacity=minCapacity
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                //最多不能超过最大容量
                newCapacity = hugeCapacity(minCapacity);
           //复制数组从新赋值
            elementData = Arrays.copyOf(elementData, newCapacity);
        }            
    
     public void add(int index, E element) {
            rangeCheckForAdd(index);//索引校验
          //这里和上面add()处理的差不多   扩容数组
            ensureCapacityInternal(size + 1);  
          //这个地方和上面最开始说的那种copy例子差不多,从elementData数组的index索引位置开始,copy到elementData数组索引位置为index+1。长度为size-index
           //说白了就是将存在index索引位置的值一直到size-index中间所有的值。替换index-1索引位置一直到size-index的值
            System.arraycopy(elementData, index, elementData, index + 1,
                             size - index);
            elementData[index] = element;//根据索引复制
            size++;
        }
    
        private void rangeCheckForAdd(int index) {
        //超过数组实际长度报错
            if (index > size || index < 0)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
    

    结论:从源码上看,两个方法大致相同,最主要的差别是System.arraycopy(elementData, index, elementData, index + 1, size - index),从index位置开始以及之后的数据,整体拷贝到index+1开始的位置,然后再把新加入的数据放在index这个位置,而之前的数据不需要移动。(这些动作比较消耗性能)

    八、get方法

     public E get(int index) {
            rangeCheck(index);//校验索引是否越界
    //根据索引取值
            return elementData(index);
        }
    

    九、remove方法

     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);
            //因为数组已经替换了,把size-1的索引位置的元素赋值为null,方便GC回收
            elementData[--size] = null;
    
            return oldValue;
        }    
    
    public boolean remove(Object o) {
            if (o == null) {
          // 为null的时候遍历删除第一个位null的值
                for (int index = 0; index < size; index++)
                    if (elementData[index] == null) {
                        fastRemove(index);
                        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; 
    }
    

    结论:删除其实还是使用的是java.lang.System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length),也是比较消耗性能的

    十、set方法

      public E set(int index, E element) {
            rangeCheck(index);
    
            E oldValue = elementData(index);
            elementData[index] = element;
            return oldValue;
        }
    

    结论:修改没什么好说的,索引校验,然后获取之前索引的值,替换、返回。

    总结:基于数组实现的List在随机访问和遍历的效率比较高,但是往指定位置加入元素或者删除指定位置的元素效率比较低。jdk1.7之前默认初始长度为10,jdk1.8之后初始长度为0,第一次add的时候如果没有指定长度默认为10。如果数组的实际容量 + 1 大于 数组的存储容量的时候,就开始扩容,每次扩1.5 倍。

  • 相关阅读:
    233
    膝为筋之府
    AES 可逆性加密算法
    DES 可逆性加密算法
    Java Swing如何让窗体居中显示
    eclipse 安装插件
    eclipse 搭建Swt 环境
    Java Set元素比较
    Greenplum数据库中系统表gp_ distribution_ policy详解
    Greenplum数据库中系统表pg_class详解
  • 原文地址:https://www.cnblogs.com/yangk1996/p/12653070.html
Copyright © 2011-2022 走看看