• ArrayList源码简单解析


    学习集合入门就是增删改查,那么我也从增删改查去一 一分析源码解析。

    首先是构造函数,分为两种有参数和无参数。

     //有参构造
     public ArrayList(int initialCapacity) {
            super();
            if (initialCapacity < 0)
                throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
            this.elementData = new Object[initialCapacity];   //直接设置我们数组的长度
    //无参构造  
    public ArrayList() {
            super();
            this.elementData = EMPTY_ELEMENTDATA;   //把空数组赋值给我们的数组
        }

    简单阐述一下ArrayList中的属性代表的含义;

        private static final int DEFAULT_CAPACITY = 10;	//默认扩容大小10个单位数组
     
        private static final Object[] EMPTY_ELEMENTDATA = {};   //空数组
    
        private transient Object[] elementData;		//我们需要存入的数组
    
        private int size;					//也就是集合中的长度,数组中的length.用size属性进行计数元素个数

    首先学习集合那么入门就是创建集合并且为集合添加元素。

    	@Test
    	public void run1(){
    		ArrayList<Integer> list = new ArrayList<Integer>();
    		boolean boo = list.add(1);
    		System.out.println(boo); 		 //----true
    		System.out.println(list.size());         //----1
    	}

    然后进入源码查看相应的add方法进行下一步分析。

             public boolean add(E e) {
    			ensureCapacityInternal(size + 1);  // 初始化size为0 所以这里的参数就是0+1=1,先进入下方ensureCapacityInternal中
    			elementData[size++] = e;	//回到这里,因为进行了无参数构造方法,所需elementData已经初始化成功
    			return true;			//并且elementData[0++]=1    就是我们传入的1 下标为0,value为1
    		}
    	private void ensureCapacityInternal(int minCapacity) {	//minCapacity  是上面的size+1=1;
    	    if (elementData == EMPTY_ELEMENTDATA) {      //第一次插入空数组判定这里是正确的 然后下一步
    		minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);//Math.max方法把minCapacity赋值为10,默认的扩容大小
    	        }
    
    	    ensureExplicitCapacity(minCapacity);		//下一步
    	}
    	private void ensureExplicitCapacity(int minCapacity) {
            modCount++;					//计数器,用于线程检查 防止当前线程处理集合,其他线程破坏结构
    							//目前用不到
            if (minCapacity - elementData.length > 0)	//用于数组扩实现
                grow(minCapacity);
            }
     private void grow(int minCapacity) {				
            
            int oldCapacity = elementData.length;			       //第一次进入oldCapacity=0
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;					//赋值 为10
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
    
            elementData = Arrays.copyOf(elementData, newCapacity);		//扩容10个大小 Arrays.copyOf方法  调回第一步add
       }

    所以返回的是true,并且size也进行了++,所以在进行索取size方法时候  返回的就是 长度为1。

    get()方法就不用说了,想也能够想明白,就是在elementDate[index]对吧。然后只需要进行判断数组是否越界抛出异常。让我们来看看源代码。还真是这个样子的。

    public E get(int index) {
            rangeCheck(index);
    
            return elementData(index);
        }
     private void rangeCheck(int index) {
            if (index >= size)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }

    然后最纠结的就是删除方法了。remove()。因为遍历的不同可能会出现remove方法的异常。是因为上面的计数器在参与remove方的判断。在源代码中 ,增删改都需要进行 modCount++;所以在remove方法中会有一个等值来和他进行判断,首先进行简单的remove()方法源码解析。


    第一种简单的ArrayList对象remove方法也就是说,根据索引删除,或者根须传入的元素信息删除

    //根据索引删除 
    public E remove(int index) {
            rangeCheck(index);   //同样进行下标是否越界检查
    
            modCount++;            //增删改操作进行modCount++
            E oldValue = elementData(index);    //把数组value赋值给oldValue用与返回
    
            int numMoved = size - index - 1;    //这里是进行运算,arraycopy方法,对数组进行重新复制操作
            if (numMoved > 0)
                System.arraycopy(elementData, index+1, elementData, index,numMoved);  //删除的数组元素索引后一位所有的元素赋值给从index开始numMoved结束的新数组。            
            elementData[--size] = null; // clear to let GC do its work                //并且把最后一位元素复制成0,size进行了--size操作
    
            return oldValue;            //返回value值
        }

    第二种删除方式

    //根须元素删除
    public boolean remove(Object o) {
            if (o == null) {
                for (int index = 0; index < size; index++)
                    if (elementData[index] == null) {
                        fastRemove(index);
                        return true;
                    }
            } else {
                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
        }

    可以看出就是就是普通for循环找到对应的索引进行删除。而且也进行了mod++操作。

    那么我们再用迭代器增强for循环 进行一下删除操作试试看会出现什么情形。




    双双报错,至于为什么会出现这种情况,是因为没有同步上mod++所以跑出了异常,那么不是说增删改造作都会进行,mod++么,这是没错的,但是因为有一个变量需要和它进行等值操作而上面两种方法并没有进行等值操作,我们来看一下代码,就能看出来原因了。直接看迭代器的就能分析出来。在AbstractList中能找到这个方法因为ArrayList继承了AbstractList。

            //调用迭代器方法会创建Itr
            public Iterator<E> iterator() {
                return new Itr();
            }
             
       
    	private class Itr implements Iterator<E> {
            int cursor = 0;                        //用与记录元素位置
            int lastRet = -1;                       //上一次的计数
            int expectedModCount = modCount;       //假定咱们使用上面的集合 进行了5次  add操作那么这是modCount= 5 = expectedModCount;
            public boolean hasNext() {
                return cursor != size();	       //判定是否迭代条件
            }
            public E next() {
                checkForComodification();   //进入这个方法判断modCount和expectedModCount是否相等;  假定我们第一次进入next方法没有异常
                try {                       
                    int i = cursor;				
                    E next = get(i);			
                    lastRet = i;				
                    cursor = i + 1;				
                    return next;	        //返回得到的value   到目前第一次循环遍历没有问题    咱们直接跳出代码进行第二次分析。			
                } catch (IndexOutOfBoundsException e) {
                    checkForComodification();
                    throw new NoSuchElementException();
                }
            }
    	
            public void remove() {	            //迭代删除时   expectedModCount =   , modCount = 0
                if (lastRet < 0)
                    throw new IllegalStateException();
                checkForComodification();
    
                try {
                    AbstractList.this.remove(lastRet);
                    if (lastRet < cursor)
                        cursor--;
                    lastRet = -1;
                    expectedModCount = modCount;
                } catch (IndexOutOfBoundsException e) {
                    throw new ConcurrentModificationException();
                }
            }
    
            final void checkForComodification() {
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
            }
        }

    第二次分析++++

            第一次循环是没有问题的并且也能得到相应的iterator.next();也就是value值,但是向下执行时候进行了remove操作,这个时候modCount进行了++操作,再进行checkForComodification()方法判定时候直接跑出异常,因为之前操作modCount=5,而且 int expectedModCount = modCount; 所以等值相等,而第二次modCount更新时候并没有再次进行等值操作了。咱们进行了删除操作所以modCount这个时候是 6 ,而expectedModCount 是 5 , 所以执行抛出了异常,这就是为什么抛出异常的原因了,是因为并没有通知iterator这个元素已经进行了删除。

            就算能通过那么想一想获取出来的值就会是下一个元素,那么Null就是末尾,还能够获取出来,这都是假设,即使成立了也很矛盾,我们要遍历的是五个元素并且删除了一个剩下了四个,也就是出来的应该是四个并不是五个,所以因为iterator并不知道结构改变所以会事先进行check检查,并且抛出了异常。那么迭代其中提供了解决方法,就是使用迭代器的remove,实际上还是调用的list的remove方法,但是这里进行了计数的同步,请向上看迭代删除方法是否进行了modCount赋值操作。所以这里就通过不报错了。


    这里的难点也就在这里了,其他的方法自行查看源代码就能够看懂了。



  • 相关阅读:
    node.js + expres 的安装
    ubuntu文件夹建立软链接方法
    $.ajax()方法所有参数详解;$.get(),$.post(),$.getJSON(),$.ajax()详解
    与 的区别
    ubuntu卸载软件命令,apt-get remove
    php中session机制的详解
    mysql中查询语句中的一个知识点说明
    Git忽略规则及.gitignore规则不生效的解决办法
    Linux中find常见用法示例
    Waiting for table metadata lock
  • 原文地址:https://www.cnblogs.com/liclBlog/p/15349586.html
Copyright © 2020-2023  润新知