加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > Java > 正文

ArrayList源码浅析

发布时间:2020-12-15 07:32:45 所属栏目:Java 来源:网络整理
导读:也快要秋招了,博客也没有任何的代码,只有几个遇到的问题记录,所以就写些吧,顺便复习下,如果有哪块写的有问题,欢迎大家批评指正。 public class ArrayListE extends AbstractListE implements ListE ,RandomAccess,Cloneable,java.io.Serializable { pr

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

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,copy,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()获取一个更加强大的迭代器

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读