ArrayList源码分析(Java8)

做~自己de王妃 提交于 2020-01-24 02:52:03

ArrayList集合

1、特点:

- 底层是Object[] 的数组
- 在内存中是连续的内存空间、并且按照顺序进行存储的
- 允许有空值存在
- 初始化大小为10

*注意:
1、因为是连续的存储空间,所以在查找某个位置数据是很快的,往中间插入数据是很慢的。
2、只有同一种数据类型的数据才可以放在同一个数组当中。
3、在往集合添加数据的时候需要检查集合是否需要扩容,扩容操作对于性能而言消耗是很大的,如果已知数据量多大的情况下,最好指定集合的大小。*

2、构造方法:

(1)无参构造方法:

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

(2)指定初始化大小的构造方法:

    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) { // 当初始化大小大于0时:
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {//当初始化大小等于0时:
            this.elementData = EMPTY_ELEMENTDATA;
        } else { //否则参数错误
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

(3)引入另一个集合的构造方法:

    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

3、执行添加操作:

(1)普通添加数据:

    public boolean add(E e) {
        //判断是否需要扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //然后执行在当前位置进行赋值
        elementData[size++] = e;
        return true;
    }

(2)向指定位置添加数据:

    public void add(int index, E element) {
        rangeCheckForAdd(index); //这个方法用来判断添加的位置是否越界(大于数组总长度、小于0)
        ensureCapacityInternal(size + 1);  //检查是否需要扩容
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index); //我猜测是将数组之后的值往后移
        elementData[index] = element; //移动完之后当前位置还是老的值,然后新值直接覆盖
        size++;
    }

(3)添加中的扩容操作:

    //扩容入口
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
    //查看当前的数组大小,如果第一次存,则直接返回初始值10
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
    
    //判断当前是否是一个空的集合,如果是,就将默认的10和当前传过来的数(当前数据长度 + 1)
        
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //第一次都返回10
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //如果当前数组不是空的,直接返回当前数组长度 + 1
        return minCapacity;
    }
    
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++; //将修改次数 + 1

        // overflow-conscious code
        //将当前数组内的实际长度与数组的限定长度进行对比,如果实际长度大于了限定长度就进行扩容。
        所以:在第一次存数据的时候就实现了一步扩容,并且扩成初始值10!
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    //真正的扩容方法
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1); 新长度 = 旧长度 + 旧长度/2 (即:新的 = 旧的 * 1.5)
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity; //如果新长度小于旧长度(也就是说这是第一次添加数据,那新长度就等于 1)
        if (newCapacity - MAX_ARRAY_SIZE > 0) 
            newCapacity = hugeCapacity(minCapacity); //扩容到最大值时(int.Max - 8)时返回int的最大值,表示再也不能扩容了
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity); //创建完新的数组,然后实现数据复制。
    }
    

4、执行删除操作:

(1)根据下标删除

    public E remove(int index) {
   
        rangeCheck(index); //判断下标是否符合标准

        modCount++; //操作次数 + 1
        E oldValue = elementData(index); // 获取到旧值,删除成功后返回

        int numMoved = size - index - 1; //获取当前需要移动的元素的个数
        if (numMoved > 0)
        //将当前位置往后的一位数到数组的最后一位数统一往前移动一位
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // 将数组最后一位置空

        return oldValue;
    }

(2)根据内容删除

    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;
                }
        }
        //如果值不存在直接返回false
        return false;
    }

(3)fastRemove(index)

    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
    }

(4)在循环遍历删除:

        List<Integer> list = new ArrayList<>();
        
        list.add(3);
        list.add(5);
        list.add(9);

        list.add(0,999);

        System.out.println(list);
        
        for (Integer num : list){
            if(num == 9){
                list.remove(num); //会报错:java.util.ConcurrentModificationException
            }
        }
        

Result:上面这个代码报错的原因是因为在遍历之前就已经把当前集合的操作数赋值给一个预期操作数的变量,执行remove之前会把当前的操作指令数 + 1,然后当前操作数会跟预期操作数进行比较,如果不一致就会报上述错误,总而言之就是在改变集合之前先对当前操作数进行修改了。

So:怎么正确删除集合的元素呢?

        List<Integer> list = new ArrayList<>();

        list.add(3);
        list.add(5);
        list.add(9);

        list.add(0,999);

        System.out.println(list);
        
        // 先将集合变成iterator
        Iterator<Integer> iterator = list.iterator();
        
        while(iterator.hasNext()){
           Integer num = iterator.next();
           if(num == 5){
               iterator.remove();
           }
       }
        System.out.println(list);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!