探索 ArrayList 原理 - 第三节ArrayList 常用方法源码分析

探索 ArrayList 原理

jdk1.8 API
黑马教学视频: java进阶教程丨全面深入解析ArrayList原理(源码分析+面试讲解)

4. ArrayList 常用方法源码分析

4.1 构造方法

在这里插入图片描述

  • 执行这段代码 ArrayList<Object> objects1 = new ArrayList<>(); 看一下无参构造干了些什么事

    • 无参构造
    	//真正存储数据的容器
        transient Object[] elementData; 
    	//默认空容量的数组,长度为 0
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    	// 空参构造
        public ArrayList() {
            //初始化赋值一个空数组
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
    
  • 执行这段代码 ArrayList<Object> objects2 = new ArrayList<>(5); 看一下有一个整型参构造干了些什么事

    • 有参构造: [指定容量参数]
    	//真正存储数据的容器
        transient Object[] elementData; 
    	//空容量的数组,长度为 0
        private static final Object[] EMPTY_ELEMENTDATA = {};
    	// 有参构造 
       public ArrayList(int initialCapacity) {
       		//接入参值 ,初始化数据
            if (initialCapacity > 0) {
                this.elementData = new Object[initialCapacity];
            } else if (initialCapacity == 0) { //接入参值为0 ,初始化数据
                this.elementData = EMPTY_ELEMENTDATA;
            } else { //其它就抛出异常
                throw new IllegalArgumentException("Illegal Capacity: "+
                                                   initialCapacity);
            }
        }
    
  • 执行下面这段代码

    • 看一下有一个集合作为参数的构造干了些什么事
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("aaa");
        arrayList.add("bbb");
        arrayList.add("ccc");
    
        ArrayList<String> list = new ArrayList<>(arrayList);
        for (String s:list ) {
            System.out.println(s);
        }
    
  • 有参构造: [指定合集参数]

    	//真正存储数据的容器
        transient Object[] elementData; 
    	//长度为 0的空数组
        private static final Object[] EMPTY_ELEMENTDATA = {};
    	// 集合长度
        private int size;
    	// 有参构造 
        public ArrayList(Collection<? extends E> c) {
        	// 将构造方法中的参数转为数组
            elementData = c.toArray();
            	//将新数组长度赋值给size
            if ((size = elementData.length) != 0) {
                // 再次进行判断
                if (elementData.getClass() != Object[].class)
                	//数组的创建和拷贝
                    elementData = Arrays.copyOf(elementData, size, Object[].class);
            } else {
                // 就把空数组的地址赋值给集合存元素的数组
                this.elementData = EMPTY_ELEMENTDATA;
            }
        }
    	//将集合转为数组的请求
        public Object[] toArray() {
        	//调用工具类的方法 elementData 源数据, size 是源数组长度
            return Arrays.copyOf(elementData, size);
        }
    
    	//工具类
    	class Arrays {
    	  //
    	   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")
    	        //不管三元运算符结果如何,都会创建一个新的数组;
    	        //新的数组的长度一定是和集合的size 一样
    	        T[] copy = ((Object)newType == (Object)Object[].class)  //此处比较地址-->详解看第7节Arrays.copyOf()
    	            ? (T[]) new Object[newLength]
    	            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    	        //新数组的拷贝 :把源数组拷贝到新创建的数组中   
    	        System.arraycopy(original, 0, copy, 0,
    	                         Math.min(original.length, newLength));
             //返回新数组
            return copy;
          }
    	}
    
4.2 案例演示
4.3 添加方法
  • Api 文档如下
    在这里插入图片描述

  • 1、指定的元素追加到此列表末尾 源码分析

    • 模拟添加数据,分析添加方法,如下
        public static void main(String[] args) {
            ArrayList<String> strList = new ArrayList<>();
            strList.add("1");
    	}
    
    • 根据如上代码进行分析添加一个元素
    //真正存储数据的容器
    transient Object[] elementData; 
    //默认空容量的数组,长度为 0
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
    //长度为 0的空数组
    private static final Object[] EMPTY_ELEMENTDATA = {};
    //默认 初始化数组容量为 10 
    private static final int DEFAULT_CAPACITY = 10;
    
    //2 The size of the ArrayList (the number of elements it contains). [ArrayList的大小(它包含的元素数)]
    private int size;
    
    //初始化数组的修改次数
    protected transient int modCount = 0;
    
    //1 Appends the specified element to the end of this list. [在list 末尾追加一个指定元素]
    public boolean add(E e) {
        // 确保内部容量,size是添加前数组内元素的数量
        ensureCapacityInternal(size + 1);  // size 默认0
        //添加数据
        elementData[size++] = e;
        return true;
    }
    // 3 确保内部容量可用,size是添加前数组内元素的数量
    private void ensureCapacityInternal(int minCapacity) {
    	//elementData 初始化的存储容器
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    // 4 计算数组长度  
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
    	//判断 集合存元素的地址是否为默认空容量数组元素
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //当执行new ArrayList() 时会初始化引用
        	//返回最大的值
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    
    //明确的数组容量
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // minCapacity 当前真实数组容量大小  - 初始化数组元素容量大小({}.length = 10) > 0
        if (minCapacity - elementData.length > 0) 
        	//扩容
            grow(minCapacity);
    }
    /**
     * 
     * @DESC 核心扩容方法
     * @param minCapacity 当前真实数组容量大小
     *  扰动
     **/
    private void grow(int minCapacity) {
       // 初始的时候elementData= {} 所以旧的容量 oldCapacity= {}.length = 0
       int oldCapacity = elementData.length;
       // 右移:>>  右移几位就相当于除以2的n 次幂
       // 左移:<<  左移几位就相当于乘以2的n 次幂
       // 新容量 = 旧容量+右移过后的值 
       int newCapacity = oldCapacity + (oldCapacity >> 1); //源容量的1.5倍 
       // 赋值新容量大小
       if (newCapacity - minCapacity < 0)
           newCapacity = minCapacity;
       // 新容量大小 跟 integer 最大容量比较    
       if (newCapacity - MAX_ARRAY_SIZE > 0)
           newCapacity = hugeCapacity(minCapacity);
       // minCapacity is usually close to size, so this is a win:
       //
       elementData = Arrays.copyOf(elementData, newCapacity);
    }
    //超大容量方法
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
    
  • 2、在此列表中的指定位置插入指定的元素 源码分析

    • 模拟添加数据,分析添加方法,如下
        public static void main(String[] args) {
            ArrayList<String> strList = new ArrayList<>();
            strList.add("2");
            strList.add("3");
      	    strList.add(1,"1");
    	}
    
    • 根据如上代码进行分析指定位置插入指定的元素
    	//真正存储数据的容器
        transient Object[] elementData; 
    	//默认空容量的数组,长度为 0
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
       
    	//长度为 0的空数组
        private static final Object[] EMPTY_ELEMENTDATA = {};
       	//默认 初始化数组容量为 10 
        private static final int DEFAULT_CAPACITY = 10;
        
    	//2 The size of the ArrayList (the number of elements it contains). [ArrayList的大小(它包含的元素数)]
        private int size;
    	
    	//初始化数组的修改次数
        protected transient int modCount = 0;
    
    	//
        public void add(int index, E element) {
            // 
            rangeCheckForAdd(index);
    
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            // 将数组元素elementData 从索引位置1 开始 
            //    还是拷贝到elementData 索引位置为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));
        }
       	// 3 确保内部容量可用,size是添加前数组内元素的数量 
        private void ensureCapacityInternal(int minCapacity) {
            ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
        }
        // 4 计算数组长度  
        private static int calculateCapacity(Object[] elementData, int minCapacity) {
           if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
             return Math.max(DEFAULT_CAPACITY, minCapacity);
           }
           return minCapacity;
        }
    	//5 确保数组容量,然后进行扩容
        private void ensureExplicitCapacity(int minCapacity) {
            modCount++;
    
       // minCapacity 当前真实数组容量大小  - 数组元素容量大小(elementData.length = 10) < 0
        if (minCapacity - elementData.length > 0) // 因为已经添加过元素,最后一次是指定索引添加元素,初始值已经有值 
    			// 只有容量不够才去扩容
                grow(minCapacity); 
        }
    
  • 指定位置赋值内存地址分析,如下图解
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 3、按指定集合的Iterator 返回顺序将指定集合中的所有元素追加到此列表末尾 源码分析

    • 模拟添加数据,分析添加方法,如下
        public static void main(String[] args) {
            ArrayList<String> strList = new ArrayList<>();
            strList.add("2");
            strList.add("3");
      	    strList.add(1,"1");
    	}
    
    • 根据如上代码进行分析将指定集合中的所有元素追加到此列表末尾
    		//真正存储数据的容器
    	    transient Object[] elementData; 
    		//默认空容量的数组,长度为 0
    	    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    	   
    		//长度为 0的空数组
    	    private static final Object[] EMPTY_ELEMENTDATA = {};
    	   	//默认 初始化数组容量为 10 
    	    private static final int DEFAULT_CAPACITY = 10;
    	    
    		//2 The size of the ArrayList (the number of elements it contains). [ArrayList的大小(它包含的元素数)]
    	    private int size;
    		
    		//初始化数组的修改次数
    	    protected transient int modCount = 0;
    	    
            public boolean addAll(Collection<? extends E> c) {
    	        //将有数据的集合转换为数组
    	        Object[] a = c.toArray();
    	        //将有数据的集合的长度赋值给numNew 
    	        int numNew = a.length;
    	        //校验 计算数组容量,首次扩容
    	        ensureCapacityInternal(size + numNew);  // Increments modCount
    	        //真正的拷贝:把a元素整体拷呗到elementData中
    	        System.arraycopy(a, 0, elementData, size, numNew);
    	        //集合长度进行更改:size 的大小为数据元素a的长度
    	        size += numNew;
           		 return numNew != 0;
        	}
        	// 3 确保内部容量可用
    	    private void ensureCapacityInternal(int minCapacity) {
    	    	//elementData 初始化的存储容器
    	        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    	    }
    	    // 4 计算数组长度  
    	    private static int calculateCapacity(Object[] elementData, int minCapacity) {
    	    	//判断 集合存元素的数据是否等于默认空容量数组元素
    	        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    	        	//返回最大的值
    	            return Math.max(DEFAULT_CAPACITY, minCapacity);
    	        }
    	        return minCapacity;
    	    }
    	
    		//明确的数组容量
    	    private void ensureExplicitCapacity(int minCapacity) {
    	        modCount++;
    	        // minCapacity 当前真实数组容量大小  - 初始化数组元素容量大小({}.length = 10) > 0
    	        if (minCapacity - elementData.length > 0) 
    	        	//扩容
    	            grow(minCapacity);
    	    }
    	    /**
    	     * 
    	     * @DESC 核心扩容方法
    	     * @param minCapacity 当前真实数组容量大小
    	     *  扰动
    	     **/
    	    private void grow(int minCapacity) {
    	       // 初始的时候elementData= {} 所以旧的容量 oldCapacity= {}.length = 0
    	       int oldCapacity = elementData.length;
    	       // 右移:>>  右移几位就相当于除以2的n 次幂
    	       // 左移:<<  左移几位就相当于乘以2的n 次幂
    	       // 新容量 = 旧容量+右移过后的值 
    	       int newCapacity = oldCapacity + (oldCapacity >> 1); //源容量的1.5倍 
    	       // 赋值新容量大小
    	       if (newCapacity - minCapacity < 0)
    	           newCapacity = minCapacity;
    	       // 新容量大小 跟 integer 最大容量比较    
    	       if (newCapacity - MAX_ARRAY_SIZE > 0)
    	           newCapacity = hugeCapacity(minCapacity);
    	       // minCapacity is usually close to size, so this is a win:
    	       //
    	       elementData = Arrays.copyOf(elementData, newCapacity);
    	   }
    	    //超大容量方法
    	    private static int hugeCapacity(int minCapacity) {
    	        if (minCapacity < 0) // overflow
    	            throw new OutOfMemoryError();
    	        return (minCapacity > MAX_ARRAY_SIZE) ?
    	            Integer.MAX_VALUE :
    	            MAX_ARRAY_SIZE;
    	    }
    
  • 结论:底层使用了System.arraycopy 的方法进行拷贝;

  • 4、 将指定集合的所元素插入到此列表中,从指定的位置开始 源码分析

    • 模拟添加数据,分析添加方法,如下
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<>();
            list.add("黑马程序员");
            list.add("传智播客");
            list.add("传智大学");
    
            ArrayList<String> listSource = new ArrayList<String>();
            listSource.add("醋丁鱼");
            listSource.add("博学谷");
            listSource.addAll(1,list);
    	}
    
    • 根据如上代码进行分析指定位置插入指定集合的所元素
    		//真正存储数据的容器
        transient Object[] elementData; 
    	//默认空容量的数组,长度为 0
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
       
    	//长度为 0的空数组
        private static final Object[] EMPTY_ELEMENTDATA = {};
       	//默认 初始化数组容量为 10 
        private static final int DEFAULT_CAPACITY = 10;
        
    	//2 The size of the ArrayList (the number of elements it contains). [ArrayList的大小(它包含的元素数)]
        private int size;
    	
    	//初始化数组的修改次数
        protected transient int modCount = 0;
    		    
         public boolean addAll(int index, Collection<? extends E> c) {
         	//检验索引  (index > size || index < 0)
            rangeCheckForAdd(index);
            //将有数据的集合转换为数组
            Object[] a = c.toArray();
            //记录数据源长度
            int numNew = a.length;
            // 目的就是为了给集合存储数据的数组进行扩容
            ensureCapacityInternal(size + numNew);  // Increments modCount
    		//numMoved  要移动的元素的个数 =  数据目的长度-addAll的第一个参数(索引)
    		//size: 数据目的长度(也就是最终添加到的那个元素的长度)
    		//现在需要添加元素,需要后面的元素向后移动留出位置,先计算移动的元素的个数,使用公式size - index即可
            int numMoved = size - index;
            if (numMoved > 0) //如果移动个数大于0
                // 数据目的集合首先自己先做对应拷贝
                System.arraycopy(elementData, index, elementData, index + numNew,
                                 numMoved);
            // 真正的将数据源中的所有数据添加到数据目的
            System.arraycopy(a, 0, elementData, index, numNew);
            size += numNew;
            return numNew != 0;
        }
       	//检查 索引范围
        private void rangeCheckForAdd(int index) {
            if (index > size || index < 0)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
       	// 3 确保内部容量可用
        private void ensureCapacityInternal(int minCapacity) {
        	//elementData 初始化的存储容器
            ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
        }
        // 4 计算数组长度  
        private static int calculateCapacity(Object[] elementData, int minCapacity) {
        	//判断 集合存元素的数据是否等于默认空容量数组元素
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            	//返回最大的值
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            }
            return minCapacity;
        }
    
    	//明确的数组容量 然后扩容
        private void ensureExplicitCapacity(int minCapacity) {
            modCount++;
            // minCapacity 当前真实数组容量大小  - 初始化数组元素容量大小({}.length = 10) > 0
            if (minCapacity - elementData.length > 0) 
            	//扩容
                grow(minCapacity);
        }
        /**
         * 
         * @DESC 核心扩容方法
         * @param minCapacity 当前真实数组容量大小
         *  扰动
         **/
        private void grow(int minCapacity) {
           // 初始的时候elementData= {} 所以旧的容量 oldCapacity= {}.length = 0
           int oldCapacity = elementData.length;
           // 右移:>>  右移几位就相当于除以2的n 次幂
           // 左移:<<  左移几位就相当于乘以2的n 次幂
           // 新容量 = 旧容量+右移过后的值 
           int newCapacity = oldCapacity + (oldCapacity >> 1); //源容量的1.5倍 
           // 赋值新容量大小
           if (newCapacity - minCapacity < 0)
               newCapacity = minCapacity;
           // 新容量大小 跟 integer 最大容量比较    
           if (newCapacity - MAX_ARRAY_SIZE > 0)
               newCapacity = hugeCapacity(minCapacity);
           // minCapacity is usually close to size, so this is a win:
           //
           elementData = Arrays.copyOf(elementData, newCapacity);
       }
        //超大容量方法
        private static int hugeCapacity(int minCapacity) {
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
        }
    
  • 指定位置插入集合内存地址分析,如下图解

    • 1、 初始化添加后的内存地址分析
    • 在这里插入图片描述
    • 2、 计算了numMoved 要移动的个数:首次移动之后的内存地址分析
      在这里插入图片描述
  • 3、移动自身元素之后,拷贝源数组元素到目的数组元素中
    在这里插入图片描述

  • 指定位置插入集合移动位置分析,代码模拟添加位置,代码如下

     public static void main(String[] args) {
            String[] array1 = {"黑马程序员","传智播客","传智大学"};
    
            //添加空值模拟初始化集合后的10个空间长度
            String[] array2 = {"醋丁鱼","博学谷",null,null,null,null,null,null,null,null};
    
            int numNew = array1.length;
            //size - index  size:目标数组长度   - 索引
            int numMoved = 2 - 1;
    
            if (numMoved > 0)
                System.arraycopy(array2, 1, array2, 1 + numNew,
                        numMoved);
        	    // 复制 array2 元素 从索引位置1开始  目标位置也是array2 开始存储数据的索引位置1+numNew  源数组的数量:要移动的个数numMoved
    			System.out.println("第一次自身拷贝:"+Arrays.toString(array2)); // 1+ numNew 就是要留出array1元素的位置
    	        // {"醋丁鱼","博学谷",null,null,博学谷,null,null,null,null,null};
    	        System.arraycopy(array1, 0, array2, 1, numNew);
    
        	System.out.println("第二次源数组给目标数据拷贝:"+Arrays.toString(array2));
        }
    
    • 输出结果如下
    	第一次自身拷贝:[醋丁鱼, 博学谷, null, null, 博学谷, null, null, null, null, null]
    	第二次源数组给目标数据拷贝:[醋丁鱼, 黑马程序员, 传智播客, 传智大学, 博学谷, null, null, null, null, null]
    
4.4 删除方法
  • Api jdk1.8在这里插入图片描述

  • 1、remove(int index) 删除该列表中指定位置的元素。

    • 删除该列表中指定位置的元素。 将任何后续元素移动到左侧(从其索引中减去一个元素)。
    • 模拟删除数据,分析删除方法,如下
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<>();
            list.add("黑马程序员");
            list.add("传智播客");
            list.add("传智大学");	
       		list.remove(1);
    	}
    
    • 根据如上代码进行分析指定位置删除集合中的元素
    • 源码
        public E remove(int index) {
        	// 检查索引是否超出范围,如果超出抛出异常:IndexOutOfBoundsException
            rangeCheck(index);
            // 标记修改数量统计
            modCount++;
            //取出索引位置处元素,赋值给oldValue 
            E oldValue = elementData(index);
    		//numMoved  要移动的元素的个数 =  数据目的长度-addAll的第一个参数(索引)
    		//size: 数据目的长度(也就是最终添加到的那个元素的长度)
            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
    
            return oldValue;
        }
        
        E elementData(int index) {
            return (E) elementData[index];
        }
    
        private void rangeCheck(int index) {
            if (index >= size)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
    
    
    • 模拟删除数据索引位置 代码如下
        public static void main(String[] args) {
            String[] array1 = {"黑马程序员","传智播客","传智大学"};
    
            String[] removeIndexElementMoc = removeIndexElementMoc(array1, 1);
            System.out.println(Arrays.toString(removeIndexElementMoc));
    
        }
        /**
         *
         * @param index 要删除数据的索引位置
         */
        private static String[] removeIndexElementMoc(String[] array ,int index) {
            int size = array.length;
    
            String oldValue = array[index];
            //要移动的个数numMoved
            int numMoved = size - index - 1;
            if (numMoved > 0)
                // 复制 array 元素 从索引位置index+1开始  array 开始存储数据的索引位置 index  源数组的数量
                System.arraycopy(array, index+1, array, index, numMoved);
                //分析 index + 1 : 数组位置元素为: 传智大学
                //分析 numMoved : 要复制的源数组数量	            
            array[--size] = null; // clear to let GC do its work
            return array;
        }
    
    • 输出结果如下
    	[黑马程序员, 传智大学, null]
    
  • 2、remove(Object o) 从列表中删除指定元素的第一个出现(如果存在)。

    • 从列表中删除指定元素的第一个出现(如果存在)。 如果列表不包含该元素,则它不会更改。 更正式地,删除具有最低索引i的元素,使得(o==null ? get(i)==null : o.equals(get(i))) (如果存在这样的元素)。 如果此列表包含指定的元素(或等效地,如果此列表作为调用的结果而更改),则返回true 。
    • 模拟删除数据,分析删除方法,如下
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<>();
            list.add("黑马程序员");
            list.add("传智播客");
            list.add("传智大学");	
       		list.remove("传智大学");
    	}
    
    • 根据如上代码进行分析指定位置删除集合中的元素
    • 源码
        public boolean remove(Object o) {
            // 如果为null ,则删除空元素,否则删除指定元素
            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++;
             //要移动的个数numMoved
            int numMoved = size - index - 1;
            if (numMoved > 0)
            	//拷贝元素elementData  从索引位置 index+1  到目标元素elementData 从第index 位置开始覆盖到 numMoved 结束 
                System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved);
            elementData[--size] = null; // clear to let GC do its work
        }
    
    • 模拟删除集合数据 代码 参考 remove(int index) 模拟即可;
  • 3、removeAll(Collection<?> c) 从此列表中删除指定集合中包含的所有元素。

    • 从此列表中删除指定集合中包含的所有元素。
    • 模拟删除数据,分析删除方法,如下
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<>();
            list.add("黑马程序员");
            list.add("传智播客");
            list.add("传智大学");
            ArrayList<String> list2 = new ArrayList<>();
            list.add("传智大学");
            list.removeAll(list2);
    	}
    
    • 根据如上代码进行分析 从此列表中删除指定集合中包含的所有元素。
    • 源码
    • 模拟删除数据索引位置 代码如下
    • 输出结果如下
        public boolean removeAll(Collection<?> c) {
            // 校验集合是否为空
            Objects.requireNonNull(c);
            return batchRemove(c, false);
        }
        public static <T> T requireNonNull(T obj) {
    	   if (obj == null)
               throw new NullPointerException();
           return obj;
        }
        /**
         * @Param c : 被删除的集合列表
         * @Param complement: 
         * 批量删除
         **/
    	private boolean batchRemove(Collection<?> c, boolean complement) {
    		// 把源数组赋值给 局部elementData 数组
            final Object[] elementData = this.elementData;
            //r用来控制循环,w是记录有多少个差集  removeAll 调用记录差集  
            int r = 0, w = 0;
            boolean modified = false;
            try {
                for (; r < size; r++)
                    // 校验c集合元素是否在elementData中:是否为false/true
                    if (c.contains(elementData[r]) == complement)
                     // 若是 complement = true 则将 c中包含elementData中的元素 替换 elementData[w++]位置值 达到元素前移效果
                    // 若是 complement = false则将 c中不包含elementData中的元素 存入 elementData[w++]位置值 达到元素前移效果
                        elementData[w++] = elementData[r];
            } finally {
                // Preserve behavioral compatibility with AbstractCollection,
                // even if c.contains() throws.
                if (r != size) {
                    System.arraycopy(elementData, r,
                                     elementData, w,
                                     size - r);
                    w += size - r;
                }
                if (w != size) { //将 自 elementData[w] 到 elementData[size-1]的值全部置为null
                    // clear to let GC do its work
                    for (int i = w; i < size; i++)
                        elementData[i] = null;
                    modCount += size - w;  // 标记elementData数组修改次数
                    size = w;
                    modified = true;
                }
            }
            return modified;
     }
     // 集合是否包含元素O
     public boolean contains(Object o) {
        return indexOf(o) >= 0; //没有数据则为-1
    }
     public int indexOf(Object o) {
         if (o == null) {
             for (int i = 0; i < size; i++)
                 if (elementData[i]==null)
                     return i;
         } else {
             for (int i = 0; i < size; i++)
                 if (o.equals(elementData[i]))
                     return i;
         }
         return -1;
    }
    
  • 4、removeIf(Predicate<? super E> filter) removeIf()的入参是一个过滤条件,用来判断需要移除的元素是否满足条件。

    • 方法用于删除所有满足特定条件的数组元素。
    • 模拟根据条件删除数据,伊森的根据删除方法
            ArrayList arr  = new ArrayList();
            arr.add("a");
            arr.add("b");
            arr.add("c");
            arr.add("d");
            arr.add("e");
            arr.add("d");
            System.out.println(arr);
            arr.removeIf(item->"d".equals(item));
            System.out.println(arr);
    
    • 源码
      @Override
        public boolean removeIf(Predicate<? super E> filter) {
            Objects.requireNonNull(filter);
            // figure out which elements are to be removed
            // any exception thrown from the filter predicate at this stage
            // will leave the collection unmodified
            // 用于记录删除数量的局部变量
            int removeCount = 0;
            //removeSet 用来记录被 删除的元素的下标,节省空间且有去重性
            final BitSet removeSet = new BitSet(size);
            //expectedModCount  线程安全的统计量 
            final int expectedModCount = modCount;
            final int size = this.size;
            //把符合条件的待删除元素的下标设置到BitSet中,即BitSet中对应的下标设为true
            for (int i=0; modCount == expectedModCount && i < size; i++) {
                @SuppressWarnings("unchecked")
                final E element = (E) elementData[i];
                if (filter.test(element)) {
                    removeSet.set(i);
                    removeCount++;
                }
            }
            // 检测是否有其它线程并发修改这个ArrayList 
            // 这个就是并发是fail-fast机制,可以让当前线程快速失败,而不会产生资源竞争,导致锁之类的现象
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
    
            // shift surviving elements left over the spaces left by removed elements
            //
            final boolean anyToRemove = removeCount > 0;
            if (anyToRemove) {
                final int newSize = size - removeCount;
                for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
                    i = removeSet.nextClearBit(i);
                    elementData[j] = elementData[i];
                }
                for (int k=newSize; k < size; k++) {
                    elementData[k] = null;  // Let gc do its work
                }
                this.size = newSize;
                if (modCount != expectedModCount) {
                    throw new ConcurrentModificationException();
                }
                modCount++;
            }
    
            return anyToRemove;
        }
    
    
4.5 修改方法
  • 1、set(int index, E element) 用指定的元素替换此列表中指定位置的元素。

    • index: 要替换的元素的索引 ;element: 要存储在指定位置的元素
    • 模拟修改数据,分析修改方法,如下
        ArrayList<String> list = new ArrayList<>();
        list.add("黑马程序员");
        list.add("传智播客");
        list.add("传智大学");
        System.out.println(list);
        Object set = list.set(0, "安锐捷大学");
        System.out.println(set);
        System.out.println(list);
    
    • 根据如上代码进行分析用指定的元素替换此列表中指定位置的元素 返回旧的元素
    • 源码
       public E set(int index, E element) {
            rangeCheck(index);
            E oldValue = elementData(index);
            elementData[index] = element;
            return oldValue;
      }
    
  • 输出结果如下

    	[黑马程序员, 传智播客, 传智大学]
    	黑马程序员
    	[安锐捷大学, 传智播客, 传智大学]
    
4.6 获取方法
  • 1、get(int index) 返回此列表中指定位置的元素。
    • index: 要返回的元素的索引
    • 源码,比较简单,直接上源码
     public E get(int index) {
          rangeCheck(index);
    
         return elementData(index);
     }
    
4.7 转换方法
  • 1、toString() 返回此集合的字符串表示形式。

    • 首先需要明确的是,toString() 方法不是ArrayList 的方法,而是继承的AbstractCollection 的方法
    • 模拟修改数据,分析修改方法,如下
        ArrayList<String> list = new ArrayList<>();
        list.add("黑马程序员");
        list.add("传智播客");
        list.add("传智大学");
        String s = list.toString();
        System.out.println(list);
    
    • 根据如上代码进行分析 返回此集合的字符串表示形式
    • 源码
    public abstract class AbstractCollection<E> implements Collection<E> {
           public String toString() {
    	        Iterator<E> it = iterator();
    	        if (! it.hasNext())
    	            return "[]";		
    	        StringBuilder sb = new StringBuilder();
    	        sb.append('[');
    	        //无限循环
    	        for (;;) {
    	            E e = it.next();
    	            sb.append(e == this ? "(this Collection)" : e);
    	            if (! it.hasNext())
    	                return sb.append(']').toString();
    	            sb.append(',').append(' ');
    	        }
    	    }
    }
    
  • 输出结果如下

    	[黑马程序员, 传智播客, 传智大学]
    
4.8 迭代器
  • 1、public Iterator iterator() 普通 迭代器。

  • 案例1 已知集合: List list = new ArrayList();里面有三个元素 hello,java,php 使用迭代器遍历获取集合的每一个元素

    • 首先需要明确的是,toString() 方法不是ArrayList 的方法,而是继承的AbstractCollection 的方法

      • 模拟示例,分析如下
          public static void main(String[] args) {
              List<String> arrayList = new ArrayList<String>();
              arrayList.add("hello");
              arrayList.add("java");
              arrayList.add("php");
              Iterator<String> it = arrayList.iterator();
              while (it.hasNext()){
                  String next = it.next();
                  System.out.println(next);
              }
          }
      
      • 根据如上代码进行分析 返回此集合的字符串表示形式
      • 源码
           // 获取迭代器的方法
      	 public Iterator<E> iterator() {
              return new Itr(); // 创建了一个对象
          }
      
      /**
       *  AbstractList.Itr 内部类  迭代器源码
       */
      private class Itr implements Iterator<E> {
          int cursor;       //光标索引 下一个光标位置索引  index of next element to return
          int lastRet = -1; //记录-1 index of last element returned; -1 if no such
          int expectedModCount = modCount; // 将集合实际修改的次数赋值给预期修改的次数
      
          Itr() {}
      	//判断集合是否有元素
          public boolean hasNext() {
              // 光标是否不等于集合的size
              return cursor != size;
          }
      
          //返回下一个元素
          public E next() {
          	//校验:
              checkForComodification();
              // 初始化时 i = 0
              int i = cursor;
              // 判断,如果大于集合的size 就说明没有元素了
              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];
          }      
      	// 校验预期修改集合次数是否和实际修改集合次数一样
          final void checkForComodification() {
              if (modCount != expectedModCount)
                  // 如果不一样就会产生并发修改异常
                  throw new ConcurrentModificationException();
          }
      }
      
    • 输出结果如下

      	hello
      	java
      	php
      
  • 案例2 已知集合: List list = new ArrayList();里面有三个元素 hello,java,php 使用迭代器遍历获取集合看是否有"php" 这个元素,如果有,就使用集合对象删除该元素

    • 首先需要明确的是,toString() 方法不是ArrayList 的方法,而是继承的AbstractCollection 的方法
      • 模拟示例,分析如下
           public static void main(String[] args) {
              List<String> arrayList = new ArrayList<String>();
              arrayList.add("hello");
              arrayList.add("java");
              arrayList.add("php");
              Iterator<String> it = arrayList.iterator();
              while (it.hasNext()){
                  String next = it.next();
                  if(next.equals("php")){
                      arrayList.remove("php");
                  }
              }
          }
      
      • 根据如上代码进行分析
      • 源码
        //----------------------------添加数据源码 操作----------------------------
      	// 添加数据源码 
          public boolean add(E e) {
              ensureCapacityInternal(size + 1);  // Increments modCount!!
              elementData[size++] = e;
              return true;
          }
          //  确保内部容量可用
          private void ensureCapacityInternal(int minCapacity) {
          	//elementData 初始化的存储容器
              ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
          }
          //  计算数组长度  
          private static int calculateCapacity(Object[] elementData, int minCapacity) {
          	//判断 集合存元素的数据是否等于默认空容量数组元素
              if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
              	//返回最大的值
                  return Math.max(DEFAULT_CAPACITY, minCapacity);
              }
              return minCapacity;
          }
      
      	//明确的数组容量 然后扩容
          private void ensureExplicitCapacity(int minCapacity) {
             // 记录添加数据的个数(也就是集合修改的次数)
              modCount++;
              // minCapacity 当前真实数组容量大小  - 初始化数组元素容量大小({}.length = 10) > 0
              if (minCapacity - elementData.length > 0) 
              	//扩容
                  grow(minCapacity);
          }
      
      
      //----------------------------迭代器源码 操作----------------------------
       // 获取迭代器的方法
      public Iterator<E> iterator() {
          return new Itr(); // 创建了一个对象,初始化成员变量
      }
      /**
       *  AbstractList.Itr 内部类  迭代器源码
       */
      private class Itr implements Iterator<E> {
          int cursor;       //光标索引 下一个光标位置索引  index of next element to return
          int lastRet = -1; //记录-1 index of last element returned; -1 if no such
          // 获取迭代器的时候,expectedModCount  的值(也就是集合修改的次数)
          int expectedModCount = modCount; // 将集合实际修改的次数赋值给预期修改的次数
      
          Itr() {}
      	//判断集合是否有元素
          public boolean hasNext() {
              // 光标是否不等于集合的size
              return cursor != size;
          }
      
          //返回下一个元素
          public E next() {
          	//校验:
              checkForComodification();
              // 初始化时 i = 0
              int i = cursor;
              // 判断,如果大于集合的size 就说明没有元素了
              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];
          }      
      	// 校验预期修改集合次数是否和实际修改集合次数一样
          final void checkForComodification() {
              if (modCount != expectedModCount)
                  //如果不一样,就会产生并发修改异常
                  throw new ConcurrentModificationException();
          }
      }
         //集合删除元素的方法
          public boolean remove(Object o) {
              // 判断要删除的元素是否为null ,
              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) {
               // 在删除的方法中集合实际修改次数会自增  
               // 集合实际修改次数为:4  但是预期修改次数为 3
              modCount++;
              //计算集合要移动元素的个数
              int numMoved = size - index - 1;
              if (numMoved > 0)
                 // 移动的核心代码
                  System.arraycopy(elementData, index+1, elementData, index,numMoved);
                //就是让删除的元素置为null ,目的就是为了尽快被 垃圾回收掉
              elementData[--size] = null; // clear to let GC do its work
          }
      
    • 结论如下
      1、集合每次调用add方法的时候,实际 修改次数变量的值都会自增一次 (modCount++;)
      2、在获取迭代器的时候,集合只会执行一次,将实际 修改集合赋值给预期修改集合的次数,就是在初始化迭代器成员变量的时候 (int expectedModCount = modCount)
      3、集合在删除元素的时候也会针对 实际修改次数的变量进行自增的操作
      fastRemove()方法的modCount++;
    • 报错如下:
    Exception in thread "main" java.util.ConcurrentModificationException
    	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
    	at java.util.ArrayList$Itr.next(ArrayList.java:859)
    	at source.Iterator_Analyse.main(Iterator_Analyse.java:22)
    
  • 案例3 已知集合: List list = new ArrayList();里面有三个元素 hello,php,java 使用迭代器遍历获取集合看是否有"php" 这个元素,如果有,就使用集合对象删除该元素

    • 注意:此次 php 元素位置第1索引位置
      • 模拟示例,分析如下
           public static void main(String[] args) {
                List<String> arrayList = new ArrayList<String>();
      	        arrayList.add("hello");
      	        arrayList.add("php");
      	        arrayList.add("java");
      	        Iterator<String> it = arrayList.iterator();
      	        while (it.hasNext()){
      	            String next = it.next();
      	            if(next.equals("php")){
      	                arrayList.remove("php");
      	            }
      	        }
          }
      
  • 结论:

    • 当要删除的元素在集合的倒数第二个位置的时候,不会产生并发修改异常;
  • 原因:

    • 是因为在调用hasNext方法的时候,删除数据之后,光标的值 和集合的长度一样,那么就会返回false;因此就不侍调用next() 方法获取集合的元素,那么底层就不会产生并发修改的异常了; 注意,这只是一个巧合,巧合;
  • 迭代器默认方法源码分析

    • default void remove() 从底层集合中删除此迭代器返回的最后一个元素(可选操作)。
    • 模拟分析示例
         List<String> arrayList = new ArrayList<String>();
            arrayList.add("hello");
            arrayList.add("php");
            arrayList.add("java");
            Iterator<String> it = arrayList.iterator();
            while (it.hasNext()){
                String next = it.next();
                if(next.equals("php")){
                    // 迭代器删除
                    it.remove();
                }
            } 
    
    • 源码分析
       //------------------迭代器自带删除方法--------------------------
       public void remove() {
           if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();
    
            try {
                //内部类调用外部类删除方法
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                //把实际修改集合次数赋值给预期修改次数
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
       final void checkForComodification() {
          if (modCount != expectedModCount)
              throw new ConcurrentModificationException();
       }    
       //-----------------集合删除的方法 ----------------------------
       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);
            elementData[--size] = null; // clear to let GC do its work
    
            return oldValue;
        }
    
  • 结论:

    • 迭代器调用 remove 方法删除元素,其实底层真正还是调用集合自己的删除方法来删除元素;
    • 在调用remove 方法中每次都会给预期修改次数的变量赋值;
4.9 清空方法
  • void clear() 从列表中删除所有元素。
  • 源码 clear() 比较简单,直接上源码
        public void clear() {
            modCount++;
    
            // clear to let GC do its work
            for (int i = 0; i < size; i++)
                elementData[i] = null;
    
            size = 0;
        }
    
4.10 包含方法
  • boolean contains(Object o) 如果此列表包含指定的元素,则返回 true
  • 源码比较简单,直接上源码
     // 集合是否包含元素O
     public boolean contains(Object o) {
        return indexOf(o) >= 0; //没有数据则为-1
    }
     public int indexOf(Object o) {
	     if (o == null) { //判断 元素是否为null
	         for (int i = 0; i < size; i++)
	             if (elementData[i]==null)
	                 return i;
	     } else {// 取集合中的每一个元素和要找的元素进行比较内容
	         for (int i = 0; i < size; i++)
	             if (o.equals(elementData[i]))
	                 return i;
	     }
	     return -1;
    }
4.11 判断集合是否为空方法
  • boolean isEmpty() 如果此列表不包含元素,则返回 true 。

  • 源码

      public boolean isEmpty() {
            return size == 0;
        }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值