一点点蚕食JDK源码(一)ArrayList源码

本文深入剖析JDK中ArrayList的源码实现,包括add、remove等核心方法的工作原理及其实现细节,揭示了ArrayList如何通过动态扩容和modCount变量确保数据结构的一致性和安全性。
    一点点蚕食JDK源码(一)ArrayList源码
在对数据结构和算法有一定了解之后,我开始学习JDK源码,下面是自己的记录。
采用的方式是将JDK源码中主要的类切离出来,含有集合Collection,并发JUC两个主要的包。
本篇主要是学习ArrayList源码。
切分出的代码,我放到git上,有兴趣继续研读的看官,可以fork。 ArrayList源码
先来看add方法:

	/**
	 * Appends the specified element to the end of this list.
	 *
	 * @param e
	 *            element to be appended to this list
	 * @return <tt>true</tt> (as specified by {@link Collection#add})
	 */
	public boolean add(E e) {
		/**
		 * 1、对数组容量的控制。若数组实际容量小于10 则不扩容,否则扩容为原来的1.5倍
		 */
		ensureCapacityInternal(size + 1); // Increments modCount!!
		elementData[size++] = e;
		return true;
	}
ensureCapacityInternal()方法:

	private void ensureCapacityInternal(int minCapacity) {
		// 当声明为空实例时,取数组的初始长度为10
		if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
			// 选用Math.max()方法,而没有再次实现一个方法
			minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
		}
		// 是否进行扩容
		ensureExplicitCapacity(minCapacity);
	}
ensureExplicitCapacity(int minCapacity)方法

private void ensureExplicitCapacity(int minCapacity) {
		// 这个在HashMap中也有应用,主要作用是数组内部结构是否有变化(删除/新增等操作,具体参见关于该变量的注释)
		modCount++;

		// overflow-conscious code
		// 是否大于当前的elementData数组的长度,若为true则增长为原来的1.5倍,扩容
		if (minCapacity - elementData.length > 0)
			grow(minCapacity);
	}
grow()方法是实现的动态扩容的关键代码,在HashMap中也有应用,可以类比。

	/**
	 * Increases the capacity to ensure that it can hold at least the number of
	 * elements specified by the minimum capacity argument.
	 *
	 * @param minCapacity
	 *            the desired minimum capacity
	 */
	private void grow(int minCapacity) {
		// overflow-conscious code
		// 获取当前的elementData[] 数组的长度
		int oldCapacity = elementData.length;
		// 此处用位运算,来完成数组的增加到原来的1.5倍
		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);
	}
remove()方法:

	/**
	 * Removes the element at the specified position in this list. Shifts any
	 * subsequent elements to the left (subtracts one from their indices).
	 *
	 * @param index
	 *            the index of the element to be removed
	 * @return the element that was removed from the list
	 * @throws IndexOutOfBoundsException
	 *             {@inheritDoc}
	 */
	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);
		// 主动设置为null,强制发生GC
		elementData[--size] = null; // clear to let GC do its work

		return oldValue;
	}
至此,阅读了add,remove方法,在实现栈-stack类,模拟stack的pop,push方法很有指导意义,我也是因为在实现栈的过程中,开始读了JDK中ArrayList源码,较为简单。

关于modcount:

"还有一个比较有意思的设计是AbstractList中的modCount变量

每次对ArrayList进行修改操作(add/remove这样,get不算),它都会将父类AbstractList中的modCount这个变量自加一次

所以modCount这个变量的意思就是modification count了,表示这个list从new出来到现在,一共经历了多少次modification

如果我们需要遍历某个List,最简单的想法就是去拿这个list的迭代器,然后一路next就行了

但是在调用ArrayList的iterator方法创建迭代器时,迭代器初始化时,会用一个expectedModCount变量会记录下这个ArrayList当前的modCount

然后每次调用迭代器的方法时,都会去比对一下迭代器里的expectedModCount是否等于关联的ArrayList的modCount,如果不等,就抛出一个ConcurrentModificationException

设计目的是,ArrayList与它的迭代器都不是线程安全的,ArrayList的迭代器只能在ArrayList不被修改的情况下才能使用,如果获取迭代器后,ArrayList又被修改(无需并发修改,在同一个线程内修改也行),那么迭代器可能会指向一个异常的位置(位置串了一两位都算是好的,如果ArrayList压缩了,可能会引起数组越界的问题)。从简便的角度来看,直接禁止使用这个迭代器是最容易的。

 

与之相对的就是concurrent包里的CopyOnWriteArrayList,它的迭代器就不会报ConcurrentModificationException"








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值