zoukankan      html  css  js  c++  java
  • ArrayList源码浅析

    也快要秋招了,博客也没有任何的代码,只有几个遇到的问题记录,所以就写些吧,顺便复习下,如果有哪块写的有问题,欢迎大家批评指正。

    public class ArrayList<E> extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    {
        private static final long serialVersionUID = 8683452581122892189L;
        //默认的一个初始化数组大小
        private static final int DEFAULT_CAPACITY = 10;
        //这两个都是空数组,主要是用来初始化容器或者判断某些给的形参数组是否为空
        private static final Object[] EMPTY_ELEMENTDATA = {};
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
        //实际存放对象的数组
        transient Object[] elementData; 
        //当前的数组中存放的对象的个数
        private int size;
        
        
        
        
        //我们平时最常用的无参构造方法
        public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
        
        //比较少用的有参构造方法,直接将给的形参容器复制到我们的arraylist中
        public ArrayList(Collection<? extends E> c) {
            //将容器转化为数组
            elementData = c.toArray();
            //容器是否不是空的,给size(也就是当前的arraylist中对象的个数)中赋值。
            if ((size = elementData.length) != 0) {
                //判断字节码对象是否相等,也就是类型是否一致,字节码对象只会在第一次加载这种类时加载字节码,且只加载一次
                if (elementData.getClass() != Object[].class)
                    //将内置的数组赋值为该容器转化的数组
                    elementData = Arrays.copyOf(elementData, size, Object[].class);
            } else {
                //将内置的数组赋值为空数组
                this.elementData = EMPTY_ELEMENTDATA;
            }
        }
        
        //我们平时常用的add方法
        public boolean add(E e) {
            //确保容量足够,因为每次添加一个对象,所以给的参数是size+1
            //这个方法调用的是最关键的地方,在这之后会调用一个方法将容器的容量扩容
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            //上面已经确保了容量足够,且没有异常
            //所以直接给当前数组的不是null的那个最末尾的下表的下一个赋值
            //注意:size是真正的当前存储的对象的个数
            //而elementData这个数组是很有可能有一部分位置是空的,也就是null
            elementData[size++] = e;
            return true;
        }
        
        //确定容量方法,其内调用了一个确定最小容量和一个确定详细容量两个方法
        private void ensureCapacityInternal(int minCapacity) {
            ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
        }
        
        //这个方法内就是判断下elementData是否是空的,是空的就直接判断这个size+1是否比默认的初始容量10大
        private static int calculateCapacity(Object[] elementData, int minCapacity) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                //比较minCapacity是否比10大
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            }
            return minCapacity;
            //总之就是返回一个最小的容量数值
        }
        
        //该方法确定当前的容量是否足够,并决定是否增加容量
        private void ensureExplicitCapacity(int minCapacity) {
            //modCount用来记录修改次数,在迭代器内判断是否出现并发修改异常,这个先不管
            modCount++;
    
            //上面也说过了,elementData很可能是有一部分是null
            //所以还需要判断一下,elementData是否足够大
            //一般添加一个元素,elementData.length和size是一样大的
            //但是不排除我们会使用addAll(Collection<?> c)方法
            if (minCapacity - elementData.length > 0)
                //容量不够,增加容量,将calculateCapacity方法确定的最小容量传入
                grow(minCapacity);
        }
        //增长容量方法
        private void grow(int minCapacity) {
            int oldCapacity = elementData.length;
            //(oldCapacity >> 1)是右移操作,相当于除2
            //但是这个是最快的,直接将命令给cpu的,我们平时除2也可以这样,能稍微快点,上过组成原理的同学应该知道
            //这里就是得到一个elementData.length的1.5倍的值,准备和minCapacity比较出一个合理的值
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            //如果这个1.5倍的值大于最小容量,那么我们直接取值最小容量就可以
            //没必要用1.5倍的值,数组是固定大小,会浪费内存
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            //MAX_ARRAY_SIZE的定义为private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
            //MAX_ARRAY_SIZE已经很大,接近int的极限,如果比这个值还要大
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                //得出一个容量值,这个值会非常大
                newCapacity = hugeCapacity(minCapacity);
            // minCapacity is usually close to size, so this is a win:
            //这句英文说这个这最小容量通常接近于我们elementData的实际存储对象个数
            //大概他觉得我们最长使用的是add(E e)这样的添加很少的元素对象,不会一次添加大量元素对象
            //System.arraycopy(original, 0, copy, 0,Math.min(original.length, newLength));
            //这个Arrays.copyOf之后会调用上面这一句代码
            //original是elementData,newLength是newCapacity,copy是完成扩容后的数组
            //最后会return copy,也就是完成了这一次扩容
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
        
        //比较出一个合适的容量值的方法
        private static int hugeCapacity(int minCapacity) {
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError
                    ("Required array size too large");
            //这里Integer.MAX_VALUE这个值可能会报错
            //因为jvm会有一个默认的内存大小,这个值为21亿的MAX_VALUE可能会超过这个内存
            //和数组下标越界异常类似,但是这回是内存不够,可以调整jdk默认内存解决
            return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
        }
        
        //知道了add(E e)方法后,其他方法也差不多了
        
        //
        public E remove(int index) {
            //检查是否超出边界范围,只有一句判断是否判处数组越界异常
            //private void rangeCheck(int index) {
            //    if (index >= size)
            //        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
            //}
            rangeCheck(index);
            //同add方法一样,modCount用来记录修改次数
            modCount++;
            E oldValue = elementData(index);
    
            int numMoved = size - index - 1;
            if (numMoved > 0)
                //其实ArrayList的改变大小就是靠System.arraycopy这个本地方法完成的
                System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved);
            //清除数组对那个删除对象的引用,垃圾回收器才会回收那个没有引用标记的对象
            elementData[--size] = null; // clear to let GC do its work
    
            return oldValue;
        }
    
    }

     上面提到了两个可能会有问题的地方,一个是Integer.MAX_VALUE这个值会使得jvm内存不够分配报异常,一个是modCount记录修改次数,也可能报异常

    先说第一个地方

         try {
                int[] a=new int[Integer.MAX_VALUE-2];
                System.out.println(a.length);
            } catch (java.lang.OutOfMemoryError e) {
                e.printStackTrace();
            }
            try {
                int[] b=new int[Integer.MAX_VALUE];
                System.out.println(b.length);
            } catch (java.lang.OutOfMemoryError e) {
                e.printStackTrace();
            }

    这个代码执行后会报错误,Java heap space是内存快到极限时抛出,Requested array size exceeds VM limit是已经在内存中找不到该内存位置了,解决方法是调整jvm默认分配内存大小

    第二个地方modCount,先看这么一段代码

         ArrayList<Integer> list=new ArrayList<>();
            list.add(1);
            list.add(2);
            list.add(3);
            Iterator<Integer> iterator = list.iterator();
            while(iterator.hasNext()){
                System.out.println(iterator.next());
                list.remove(0);
            }

    运行后,会 抛出java.util.ConcurrentModificationException,这样的一个异常,为什么会抛出这样的一个异常呢?并没有多个线程同时修改集合啊

     先看下Iterator<Integer> iterator = list.iterator()中的iterator是什么

     继续往下,看到一个内部类

     这个类中的next方法,可以看到调用了checkForComodification方法,下面的if (i >= elementData.length) throw new ConcurrentModificationException();是为了防止多线程修改

     调用的checkForComodification方法

     到这里知道了,每次ArrayList去增删改元素对象的时候,改变了modCount,而其内部类对象的expectedModCount只是修改前的一个modCount的值,

    所以调用next方法时,如果使用ArrayList的增删改,那么就会抛出这么一个异常,解决方法是使用迭代器的remove方法

     可以看到,这个remove方法中,expectedModCount和modCount每次都会同步下,所以不会报异常了,但是它只有remove方法,如果想要使用其它方法可以使用list.listIterator()获取一个更加强大的迭代器

    其余的get set lastIndexOf等方法的代码行数大都比较少,有兴趣的同学可以自己看下,我也是java新手,写博客更多是为了做个记录,难免有些地方有错误,如果有哪块写的不好,或者有问题,欢迎大家批评指正。

  • 相关阅读:
    框架:Java-Spring Cloud
    DB-数据库:百科
    公司-风险投资公司-软银银行集团:百科
    百科-Java-API:POI
    组织:基金会
    un-解决方案-MSP(管理服务提供商):百科
    汉语-词语:金融
    DBS-MySQL:MYSQL获取自增ID的四种方法
    linux框架之ibus
    jQuery+CSS 简单代码实现遮罩层( 兼容主流浏览器 )
  • 原文地址:https://www.cnblogs.com/darkclouds/p/11601654.html
Copyright © 2011-2022 走看看