ArrayList源码

扩容机制

简述一下扩容机制:
1、 先确认最少需要多少容量minCapacity可以将元素装下 (size代表当前容量,elementData.length 代表总容量)
add()方法最少需要 size + 1, addAll()方法最少需要 size + x
2、 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
即ArrayList还未分配空间,则比较 DEFAULT_CAPACITY = 10 和 minCapacity 的大小,选择最大的为最少需要容量
3、 若当前总容量elementData.length 小于 minCapactiy,则需要调用grow()扩容
4、 增长为原来的1.5倍,如果增长后还是不够,那么就增长到minCapacity。那么这样的话,此时elementData数组是满的,没有剩余空间。
如果数据太多,比MAX_ARRAY_SIZE还大了,就把空间容量扩大为Integer.MAX_VALUE

扩容机制主要由那么几个函数实现

private void ensureCapacityInternal(int minCapacity)
private void ensureExplicitCapacity(int minCapacity)
private void grow(int minCapacity)

依次讲一下

ensureCapacityInternal

区别于ensureExplicitCapacity:
这个函数是口头上说,最少需要多少容量,但是还没去查它自己到底有多少。
相当这个函数相当于一个人评估自己最少需要多少钱能满足生活,但仅仅是评估而已,他自己还不知道自己家里有多少钱。

 private void ensureCapacityInternal(int minCapacity) {
 		//判断一下当前的ArrayList是不是由无参构造函数构造的
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

※ 这个方法出现在 add() 和addAll()方法中,在 add()方法中调用 ensureCapacityInternal(size + 1), 因为add只是添加一个元素。
而在addAll()方法中则是 ensureCapacityInternal(size + numNew)。

※ 可以把这个方法看作是 “确保Arraylist的内部存储空间至少为 minCapacity”

※ 为什么要针对无参构造函数函数来做这样一个判断呢?因为无参构造函数的minCapacity比较特殊。通过其他构造函数生成的ArrayList,它们没有所谓的默认初始容量,所以要求的最小容量就是在add()或addAll()调用ensureCapacityInternal()方法时传入的minCapacity。

而无参构造函数不同,传入的minCapactiy可能会比DEFAULT_CAPACITY更小,那么此时就多给ArrayList分配点容量,所以 minCapacity = DEFAULT_CAPACITY ;而如果传入的minCapactiy比DEFAULT_CAPACITY更大,那么就和别的ArrayList一样了,分配minCapacity的容量就行了

ensureExplicitCapacity

这个函数就是在上一个函数评估的基础上,去查自己家里有多少钱,查出来如果家里的钱不够,那么就用 grow函数去增长,增长到 minCapacity

private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
grow
private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        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);
    }

add方法

ArrayList有两个add()方法。

 public boolean add(E e) 
 就是把元素插到尾部
 public void add(int index, E element)
 把元素插到指定的地方,原来的那个元素后退一格

关键是 System.arraycopy 来实现数组的移位操作,这个函数可以在做算法题的时候用,很方便。

addAll方法

 public boolean addAll(Collection<? extends E> c)
 public boolean addAll(int index, Collection<? extends E> c)

remove方法

ArrayList有两个remove()方法。

public E remove(int index)
把指定下标的元素删除
 public boolean remove(Object o)
 从下标0开始循环,找到o后删掉。也就是说,只删除遇到的第一个

也是利用了System.arraycopy来对数组做整体移动。
之后再将最后一个元素作 elementData[size–] =null 的操作,让垃圾回收器去清理即可。

注意remove(Object o)中,对删除元素进行判断时,如果
o= =null ,则 利用 if(o==elementData[index]) 进行判断。
o!=null , 则利用 if(o.equals(elementData[index])进行判断。

迭代器相关

ConcurrentModificationException

当执行如下代码时,会抛出ConcurrentModificationException异常
原因是主线程在遍历元素时,其他线程往集合中添加了元素。

	public static void main(String[] args) throws Exception {
		ArrayList<Integer> list = new ArrayList<>(8);
		list.addAll(Arrays.asList(1, 2, 3, 5, 6));
		Thread thread = new Thread(() -> {
			list.add(5);
			list.add(4);
		});
		thread.start();
		for (Integer integer : list) {
			System.out.println(integer);
		}

	}

当然,这样也是不允许的

		for (Integer integer : list) {
			list.add(1);
			System.out.println(integer);
		}

剖析其源码,是因为ArrayList使用了一个modCount,当向ArrayList中添加或删除元素时,都会使modCount++,其实就类似于cas的版本号。

查看迭代器源码

/**
     * 这里可以看出 迭代器是 ArrayList 的一个内部实现类 典型的迭代器模式
     */
    public Iterator<E> iterator() {
        return new Itr();
    }

    /**
     * An optimized version of AbstractList.Itr
     */
    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
         // 这里将 modCount 赋值给了 expectedModCount
        int expectedModCount = modCount;

        Itr() {}

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        final void checkForComodification() {
            // 若修改后 modCount会变化 会与 expectedModCount 数值不相等
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

看源码可以得出获取迭代器时会将 expectedModCount 赋值为 modCount, 若在使用迭代器迭代期间修改列表则会导致两者不相等,调用next()时会进行checkForComodification检查抛异常。说明迭代时不可以进行修改操作。

modCount主要目的就是用来限制用户在迭代时修改列表,造成数据错乱

那么有没有办法在遍历一个list的时候,还向list中添加元素呢?办法是有的。就是java concurrent包中的CopyOnWriteArrayList。
具体的请看 我写的 CopyOnWriteArrayList 文章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值