AbstractCollection源码解析
这里主要就是以下几个方法的理解可能需要费点时间
public Object[] toArray() {}
public <T> T[] toArray(T[] a) {}
private static <T> T[] finishToArray(T[] r, Iterator<?> it) {}
private static int hugeCapacity(int minCapacity) {}
public abstract class AbstractCollection<E> implements Collection<E> {
//构造方法
protected AbstractCollection() {
}
//迭代,看来迭代器是要一层层的下放到子类
public abstract Iterator<E> iterator();
//尺寸
public abstract int size();
//判断容器尺寸是否为空
public boolean isEmpty() {
return size() == 0;
}
//判断容器是否包含当前指定元素
public boolean contains(Object o) {
//迭代
Iterator<E> it = iterator();
//比较判断,为null的时候,判断一下
if (o==null) {
while (it.hasNext())
if (it.next()==null)
return true;
} else {
//判断取出下一个,进行equals比较
while (it.hasNext())
if (o.equals(it.next()))
return true;
}
return false;
}
//容器转化为数组
/**
* 这一块代码可以用示例来说明:
* 假设List:a,b,c,d,e五个元素,数组r的大小就是5
* 然后进行迭代,iterator()的实现在子类中,大同小异,就是根据索引进行取值
* 然后for循环跑起来,同时迭代器判断是否有下一个,如果不出意外,应该5个都跑完然后返回一个对应的5个元素的复制数组
* 如果此时有另外一个线程对这个容器做了操作,可能删除(remove)了一个元素,这时候迭代器可能就跑到第四个索引就表示没有下一个了,直接return了;
*/
public Object[] toArray() {
// 初始化一个指定尺寸的数组
Object[] r = new Object[size()];
//获取迭代器
Iterator<E> it = iterator();
//迭代
//这里为什么不单单使用迭代器而直接使用for循环,是因为在迭代器遍历过程中添加元素,本来是五个,数组的大小也定义的是5个,此时迭代器是6个,但数组大小不变,直接给数组赋值,就会越界了
for (int i = 0; i < r.length; i++) {
if (! it.hasNext()) // fewer elements than expected
//Arrays.copyOf(r, i);方法的作用就是拷贝一个数组,第一个参数是要被拷贝的数组,第二个是要被拷贝数组的长度,如果超长,则补0
//https://blog.youkuaiyun.com/qq_35329382/article/details/81252264
//在这里的作用就是:如果没有下一位,也就是当前就是最后一位了,则拷贝整个数组返回,i是索引,是从0开始的,而我们要传入的是length,所以i++后刚好是length
//因为有可能某个线程对该集合进行了删除操作,导致r.length>size(),为了节省空间,因此返回一个较小的数组
return Arrays.copyOf(r, i);
//进行赋值
r[i] = it.next();
}
//如何执行到这一步呢?https://blog.youkuaiyun.com/qsk12345qsx/article/details/84656581
//判断是否有下一个,有则执行 finishToArray(r, it) ,没有则返回空数组r
//如何能执行到这一步呢?还是接着上面的解释,多线程操作中容器中添加了元素,此时数组已经for循环完成,但是迭代器中仍然有值的话就需要继续往数组里面添加,没有增删操作就直接返回数组r
//什么时候才会返回数组r呢?有这么一种操作就是多线程,A线程在遍历赋值时,B线程添加了一个元素,所以for循环结束后,迭代器里还有值,就往下走,此时B又删除了一个元素,这时A紧接着判断是否还有下一个,没有了,就直接返回了r;这样又衍生了一个问题,就是假设B不止删除了一个,他删除了两个,甚至更多,此时我们仍然返回的是r,就有点不对头了
//先往下走,看看finishToArray
return it.hasNext() ? finishToArray(r, it) : r;
}
//泛型 转化为数组
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
// Estimate size of array; be prepared to see more or fewer elements
//获取容器的大小size
int size = size();
//定义一个泛型数组
//如果传入的数组a的长度小于容器中元素的个数,则反射一个数组实例,大小为容器大小;如果大于或等于容器大小则创建等同于传进来的数组大小,直接赋值
T[] r = a.length >= size ? a :
(T[])java.lang.reflect.Array
.newInstance(a.getClass().getComponentType(), size);
Iterator<E> it = iterator();
//对容器元素迭代
for (int i = 0; i < r.length; i++) {
//迭代器中没有下一个元素了
//1、第一种情况,就是传进来的数组容量比容器容量大,容器都迭代完了,数组还在for循环;
//2、第二种情况,就是多线程被删除了元素,迭代器中的元素 减少,迭代次数就少了;
if (! it.hasNext()) { // fewer elements than expected
//判断a和r是否是同一个,这就符合第一种情况,将传进来的a赋值给了r,如果是一样的,那就将r对应的索引值制空
if (a == r) {
r[i] = null; // null-terminate
} else if (a.length < i) {
//如果a的长度小于i:这就是传进来的a的容量要小于容器本身,这里就是反射获取到的数组r,则直接将原数组对应拷贝返回
return Arrays.copyOf(r, i);
} else {
//这里就是剩下的情况:如果在容器内元素迭代的时候,多线程删除n个,此时a的length是要大于或等于i的,此时将r中的值拷贝到a中
System.arraycopy(r, 0, a, 0, i);
//如果a的length要大于i,则将当前i所对应的a的索引值制空
if (a.length > i) {
a[i] = null;
}
}
return a;
}
//做赋值操作,将容器中的值赋值给数组
r[i] = (T)it.next();
}
// more elements than expected
//剩下的和上面的toArray一样
return it.hasNext() ? finishToArray(r, it) : r;
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
@SuppressWarnings("unchecked")
private static <T> T[] finishToArray(T[] r, Iterator<?> it) {
//将数组和迭代器传进来
//获取当前数组的长度
int i = r.length;
//判断是否有下一个元素
//判断迭代器中是否有下一个元素
while (it.hasNext()) {
//获取数组的长度
int cap = r.length;
//第一次应该是长度相等的,肯定需要扩容,不然就越界了
if (i == cap) {
//需要扩容数组,扩容原始长度/2+1,也就是扩容1.5倍+1
int newCap = cap + (cap >> 1) + 1;
// overflow-conscious code
//对扩容之后的大小进行校验
//如果新的容量比数组最大值要大
if (newCap - MAX_ARRAY_SIZE > 0)
//重新计算大小,怎么计算?
//返回Integer.MAX_VALUE或者MAX_ARRAY_SIZE,就是他的新的大小,当到达Integer.MAX_VALUE的时候,再往下走就越界了,抛异常
newCap = hugeCapacity(cap + 1);
//扩容
r = Arrays.copyOf(r, newCap);
}
//赋值
r[i++] = (T)it.next();
}
// trim if overallocated
//如果没有执行上面那个while,则直接返回数组r;执行了则截取扩容后的数组有元素的部分进行拷贝返回。
//至于为什么是i,而不是i-1,因为上面的i是做索引用的,是从0出发的;就比如说之前有5个元素,i=r.length,之后赋值是r[i++],此时r[5]=元素值,i当前的值是6,也就是容器r的长度
return (i == r.length) ? r : Arrays.copyOf(r, i);
}
//容器扩容,
//看下面的返回值,第一步先是MAX_ARRAY_SIZE,第二步是Integer.MAX_VALUE,如果超过了Integer.MAX_VALUE(2^31-1),就开始变成负数了
private static int hugeCapacity(int minCapacity) {
//走完最大值,就会走负数,此时越界
if (minCapacity < 0) // overflow
throw new OutOfMemoryError
("Required array size too large");
//
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
//添加操作,留给子类实现
public boolean add(E e) {
throw new UnsupportedOperationException();
}
//移除操作,里面的操作内容比较简单,就是通过迭代器判断并移除
public boolean remove(Object o) {
Iterator<E> it = iterator();
if (o==null) {
while (it.hasNext()) {
if (it.next()==null) {
it.remove();
return true;
}
}
} else {
while (it.hasNext()) {
if (o.equals(it.next())) {
it.remove();
return true;
}
}
}
return false;
}
//判断当前容器是否包含指定容器中的所有元素
public boolean containsAll(Collection<?> c) {
for (Object e : c)
if (!contains(e))
//有一个不包含就返回false
return false;
return true;
}
//添加一个容器中的所有元素到当前容器中
public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}
//从当前容器中移除指定容器中的所有元素
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
boolean modified = false;
Iterator<?> it = iterator();
while (it.hasNext()) {
if (c.contains(it.next())) {
it.remove();
modified = true;
}
}
return modified;
}
//取交集
//判断指定容器中如果不包含当前容器中的元素,则移除该元素,最终结果就是取交集
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
boolean modified = false;
Iterator<E> it = iterator();
while (it.hasNext()) {
if (!c.contains(it.next())) {
it.remove();
modified = true;
}
}
return modified;
}
//迭代移除
public void clear() {
Iterator<E> it = iterator();
while (it.hasNext()) {
it.next();
it.remove();
}
}
//打印
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(' ');
}
}
}
再把上面那四个方法单独拎出来看一下
public Object[] toArray() {}
public <T> T[] toArray(T[] a) {}
private static <T> T[] finishToArray(T[] r, Iterator<?> it) {}
private static int hugeCapacity(int minCapacity) {}
作用:将容器转化为数组
public Object[] toArray() {
// Estimate size of array; be prepared to see more or fewer elements
Object[] r = new Object[size()];
Iterator<E> it = iterator();
for (int i = 0; i < r.length; i++) {
if (! it.hasNext()) // fewer elements than expected
return Arrays.copyOf(r, i);
r[i] = it.next();
}
return it.hasNext() ? finishToArray(r, it) : r;
}
初始化一个容器同等大小的数组,进行迭代;
该方法中有两个return方法:
-
第一个return方法的发生条件:
- 单线程正产执行,数组遍历完,容器迭代完,正常结束return;
- 多线程,有线程对对容器进行了元素删除,则迭代次数减少,提前return;
-
第二个return方法发生的条件:
- 多线程,有线程对容器做了元素添加,for循环遍历结束而容器中仍然还有元素,则走finishToArray(r, it)方法;
- 多线程,有线程对容器做了元素添加,for循环遍历结束而容器中仍然还有元素,但是此时又有线程对容器做了删除操作,则容器又没有了下一个元素,直接返回数组r;
里面有一个Arrays.copyOf(T[] original, int newLength)方法:
作用:指定一个源数组,指定长度,从源数组中拷贝指定长度的元素到一个新数组中返回;
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")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
指定类型创建一个数组,然后使用
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
创建一个新数组返回,最后的参数Math.min(original.length, newLength)是取数组长度和指定长度的最小值,即可以取到指定长度的值,还可以防止越界。
private static <T> T[] finishToArray(T[] r, Iterator<?> it) {
int i = r.length;
while (it.hasNext()) {
int cap = r.length;
if (i == cap) {
int newCap = cap + (cap >> 1) + 1;
// overflow-conscious code
if (newCap - MAX_ARRAY_SIZE > 0)
newCap = hugeCapacity(cap + 1);
r = Arrays.copyOf(r, newCap);
}
r[i++] = (T)it.next();
}
// trim if overallocated
return (i == r.length) ? r : Arrays.copyOf(r, i);
}
获取之前处理了的数组的长度i,做之后赋值数组的索引用;
对容器进行迭代;
在迭代过程中,先获取之前数组的长度,然后第一次扩容(因为此时数组已经满了,没有剩余空间),一次扩容1.5倍+1;
对数组容量做边界值检查,如果新扩容的数组大于MAX_ARRAY_SIZE ,则做边界值判断,确保数组容量在MAX_ARRAY_SIZE 之内,或者就是MAX_ARRAY_SIZE ,或者Integer.MAX_VALUE,就这几种可能
- 小于或等于MAX_ARRAY_SIZE (2^31-1-8)
- 等于Integer.MAX_VALUE (2^31-1)
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError
("Required array size too large");
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
这段代码就是对上面容量的解释,首先传进来的值不能大于Integer.MAX_VALUE,然后就是Integer.MAX_VALUE 和MAX_ARRAY_SIZE判断取值了,超过了MAX_ARRAY_SIZE就是Integer.MAX_VALUE;
再说到上面的扩容之后赋值;最后进行return:
- 如果索引i+1后和数组长度相等,则直接返回数组r
- 否则就是扩容扩多了没用完,这时候就需要截取赋值返回了Arrays.copyOf(r, i),只取索引长度的数组复制返回;
public <T> T[] toArray(T[] a) {
// Estimate size of array; be prepared to see more or fewer elements
int size = size();
T[] r = a.length >= size ? a :
(T[])java.lang.reflect.Array
.newInstance(a.getClass().getComponentType(), size);
Iterator<E> it = iterator();
for (int i = 0; i < r.length; i++) {
if (! it.hasNext()) { // fewer elements than expected
if (a == r) {
r[i] = null; // null-terminate
} else if (a.length < i) {
return Arrays.copyOf(r, i);
} else {
System.arraycopy(r, 0, a, 0, i);
if (a.length > i) {
a[i] = null;
}
}
return a;
}
r[i] = (T)it.next();
}
// more elements than expected
return it.hasNext() ? finishToArray(r, it) : r;
}
先获取容器的尺寸;
初始化一个泛型数组,如果传进来的数组的长度大于或等于当前容器的尺寸,则直接将传进来的数组复赋值给泛型数组,否则就反射实例化一个数组,指定容器的大小为数组的大小;
这时的泛型数组有两种情况:
- 等同于传进来的数组a
- 实例化的数组,大小等同于容器的大小
范型数组for循环跑起来;
容器迭代;但凡有下一个元素,就将元素赋值给数组,如果没有下一个元素了,容器迭代完了,if-else有三个判断
- 当前的泛型数组r是不是传进来的数组a,是的话则将下一个索引对应的值制空;
- 传进来的数组长度是不是小于泛型数组长度,是的话则不出意外的将当前容器对应的值拷贝成一个新数组返回;
- 最后一个就是我猜测的:本来传进来的数组a的长度要小于 容器大小,但是在容器迭代的时候,有别的线程对这个进行操作,移除了n个元素,此时的a的长度length是有可能大于或等于i的,然后将泛型数组中的值从前到后拷贝到a中,同时将当前索引对应的值制空;
- 最后将a返回;
这样就成了一个现象:
传进来的数组的大小如果小于容器的大小,则将容器中的值返回,否则的话就将容器中的值复制到a中,从索引0开始,最后将下一个索引制null(也就是容器的长度是2,则a的下标为2的值为null),最后a返回。
最后一个return和之前的toArray()一样,可能受到多线程的影响,增删了容器元素,使之for循环结束后,迭代器还有元素;