Java集合框架02:ArrayList(下)

4、核心方法-add

1、boolean add(E)

/**
* Appends the specified element to the end of this list.
* 添加一个特定的元素到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) {
//确定内部容量是否够了,size是数组中数据的个数,因为要添加一个元素,所以size+1,先判断size+1的这个个数数组能否放得下,就在这个方法中去判断是否数组.length是否够用了。
    ensureCapacityInternal(size + 1); // Increments modCount!!
    elementData[size++] = e; //在数据中正确的位置上放上元素e,并且size++
    return true;
}

【分析:ensureCapacityInternal(xxx); 确定内部容量的方法】

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    private static int calculateCapacity(Object[] elementData, int minCapacity)
    {
    //看,判断初始化的elementData是不是空的数组,也就是没有长度
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    //因为如果是空的话,minCapacity=size+1;其实就是等于1,空的数组没有长度就存放不了,所以就将minCapacity变成10,也就是默认大小,但是在这里,还没有真正的初始化这个elementData的大小。
    return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    //确认实际的容量,上面只是将minCapacity=10,这个方法就是真正的判断elementData是否够用
    return minCapacity;
    }
    private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    //minCapacity如果大于了实际elementData的长度,那么就说明elementData数组的长度不够用,不够用那么就要增加elementData的length。这里有的同学就会模糊minCapacity到底是什么呢,这里给你们分析一下
    /*第一种情况:由于elementData初始化时是空的数组,那么第一次add的时候,
    minCapacity=size+1;也就minCapacity=1,在上一个方法(确定内部容量
    ensureCapacityInternal)就会判断出是空的数组,就会给将minCapacity=10,到这一步为止,还没有改变elementData的大小。
    第二种情况:elementData不是空的数组了,那么在add的时候,minCapacity=size+1;也就是minCapacity代表着elementData中增加之后的实际数据个数,拿着它判断elementData的length是否够用,如果length不够用,那么肯定要扩大容量,不然增加的这个元素就会溢出。*/
    if (minCapacity - elementData.length > 0)
    grow(minCapacity);
    }
    //arrayList核心的方法,能扩展数组大小的真正秘密。
    private void grow(int minCapacity) {
    // overflow-conscious code
    //将扩充前的elementData大小给oldCapacity
    int oldCapacity = elementData.length;
    //newCapacity就是1.5倍的oldCapacity
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    //这句话就是适应于elementData就空数组的时候,length=0,那么oldCapacity=0,newCapacity=0,所以这个判断成立,在这里就是真正的初始化elementData的大小了,就是为10.前面的工作都是准备工作。
    if (newCapacity - minCapacity < 0)
    newCapacity = minCapacity;
    //如果newCapacity超过了最大的容量限制,就调用hugeCapacity,也就是将能给的最大值给
    newCapacity
    if (newCapacity - MAX_ARRAY_SIZE > 0)
    newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    //新的容量大小已经确定好了,就copy数组,改变容量大小咯。
    elementData = Arrays.copyOf(elementData, newCapacity);
    }
    //这个就是上面用到的方法,很简单,就是用来赋最大值。
    private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
    throw new OutOfMemoryError();
    //如果minCapacity都大于MAX_ARRAY_SIZE,那么就Integer.MAX_VALUE返回,反之将MAX_ARRAY_SIZE返回。因为maxCapacity是三倍的minCapacity,可能扩充的太大了,就用minCapacity来判断了。
    //Integer.MAX_VALUE:2147483647 MAX_ARRAY_SIZE:2147483639 也就是说最大也就能给到第一个数值。还是超过了这个限制,就要溢出了。相当于arraylist给了两层防护。
    return (minCapacity > MAX_ARRAY_SIZE) ?
    Integer.MAX_VALUE :
    MAX_ARRAY_SIZE;
}    

\1. void add(int,E)

public void add(int index, E element) {
    //检查index也就是插入的位置是否合理。
    rangeCheckForAdd(index);
    ensureCapacityInternal(size + 1); // Increments modCount!!
    //这个方法就是用来在插入元素之后,要将index之后的元素都往后移一位,
    System.arraycopy(elementData, index, elementData, index + 1,
    size - index);
    //在目标位置上存放元素
    elementData[index] = element;
    size++;
}

【分析:rangeCheckForAdd(index)】

private void rangeCheckForAdd(int index) {
//插入的位置肯定不能大于size 和小于0
if (index > size || index < 0)
//如果是,就报这个越界异常
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

【System.arraycopy(…):就是将elementData在插入位置后的所有元素,往后面移一位.】

public static void arraycopy(Object src,
    int srcPos,
    Object dest,
    int destPos,
    int length)
src:源对象
srcPos:源对象对象的起始位置
dest:目标对象
destPost:目标对象的起始位置
length:从起始位置往后复制的长度。
//这段的大概意思就是解释这个方法的用法,复制src到dest,复制的位置是从src的srcPost开始,到srcPost+length-1的位置结束,复制到destPost上,从destPost开始到destPost+length-1的位置上,
Copies an array from the specified source array, beginning at the specified
position, to the specified position of the destination array. A subsequence
of array components are copied from
the source array referenced by src to the destination array referenced by
dest. The number of components copied is equal to the length argument. The
components at positions srcPos through srcPos+length-1
in the source array are copied into positions destPos through
destPos+length-1, respectively, of the destination array.
//告诉你复制的一种情况,如果A和B是一样的,那么先将A复制到临时数组C,然后通过C复制到B,用了一个第三方参数
If the src and dest arguments refer to the same array object, then the
copying is performed as if the components at positions srcPos through
srcPos+length-1 were first copied to
a temporary array with length components and then the contents of the
temporary array were copied into positions destPos through destPos+length-1
of the destination array.
//这一大段,就是来说明会出现的一些问题,NullPointerException和
IndexOutOfBoundsException 还有ArrayStoreException 这三个异常出现的原因。
If dest is null, then a NullPointerException is thrown.
If src is null, then a NullPointerException is thrown and the destination
array is not modified.
Otherwise, if any of the following is true, an ArrayStoreException is thrown
and the destination is not modified:
The src argument refers to an object that is not an array.
The dest argument refers to an object that is not an array.
The src argument and dest argument refer to arrays whose component types are
different primitive types.
The src argument refers to an array with a primitive component type and the
dest argument refers to an array with a reference component type.
The src argument refers to an array with a reference component type and the
dest argument refers to an array with a primitive component type.
Otherwise, if any of the following is true, an IndexOutOfBoundsException is
thrown and the destination is not modified:
The srcPos argument is negative.
The destPos argument is negative.
The length argument is negative.
srcPos+length is greater than src.length, the length of the source array.
destPos+length is greater than dest.length, the length of the destination
array.
//这里描述了一种特殊的情况,就是当A的长度大于B的长度的时候,会复制一部分,而不是完全失败。
Otherwise, if any actual component of the source array from position srcPos
through srcPos+length-1 cannot be converted to the component type of the
destination array by assignment conversion, an ArrayStoreException is
thrown.
In this case, let k be the smallest nonnegative integer less than length
such that src[srcPos+k] cannot be converted to the component type of the
destination array; when the exception is thrown, source array components
from positions
srcPos through srcPos+k-1 will already have been copied to destination array
positions destPos through destPos+k-1 and no other positions of the
destination array will have been modified. (Because of the restrictions
already itemized,
this paragraph effectively applies only to the situation where both arrays
have component types that are reference types.)
//这个参数列表的解释,一开始就说了,
Parameters:
src - the source array.
srcPos - starting position in the source array.
dest - the destination array.
destPos - starting position in the destination data.
length - the number of array elements to be copied

【总结】 正常情况下会扩容1.5倍,特殊情况下(新扩展数组大小已经达到了最大值)则只取最大值。 当我们调用add方法时,实际上的函数调用如下:

在这里插入图片描述

说明:程序调用add,实际上还会进行一系列调用,可能会调用到grow,grow可能会调用 hugeCapacity。

【举例】

List<Integer> lists = new ArrayList<Integer>;
lists.add(8);

说明:初始化lists大小为0,调用的ArrayList()型构造函数,那么在调用lists.add(8)方法时,会经过怎样 的步骤呢?下图给出了该程序执行过程和最初与最后的elementData的大小。

在这里插入图片描述

说明:我们可以看到,在add方法之前开始elementData = {};调用add方法时会继续调用,直至 grow,最后elementData的大小变为10,之后再返回到add函数,把8放在elementData[0]中。

【举例说明二】

List<Integer> lists = new ArrayList<Integer>(6);
lists.add(8);

说明:调用的ArrayList(int)型构造函数,那么elementData被初始化为大小为6的Object数组,在调用 add(8)方法时,具体的步骤如下:

说明:我们可以知道,在调用add方法之前,elementData的大小已经为6,之后再进行传递,不会进行 扩容处理。

5、核心方法-remove

其实这几个删除方法都是类似的。我们选择几个讲,其中fastRemove(int)方法是private的,是提供给 remove(Object)这个方法用的。

\1. remove(int):通过删除指定位置上的元素

public E remove(int index) {
    rangeCheck(index);//检查index的合理性
    modCount++;//这个作用很多,比如用来检测快速失败的一种标志。
    E oldValue = elementData(index);//通过索引直接找到该元素
    int numMoved = size - index - 1;//计算要移动的位数。
    if (numMoved > 0)
    //这个方法也已经解释过了,就是用来移动元素的。
    System.arraycopy(elementData, index+1, elementData, index,
    numMoved);
    //将--size上的位置赋值为null,让gc(垃圾回收机制)更快的回收它。
    elementData[--size] = null; // clear to let GC do its work
    //返回删除的元素。
    return oldValue;
}

\1. remove(Object):这个方法可以看出来,arrayList是可以存放null值得。

//感觉这个不怎么要分析吧,都看得懂,就是通过元素来删除该元素,就依次遍历,如果有这个元素,
就将该元素的索引传给fastRemobe(index),使用这个方法来删除该元素,
//fastRemove(index)方法的内部跟remove(index)的实现几乎一样,这里最主要是知道
arrayList可以存储null值
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;
  	  }
    }
    return false;
}

\1. clear():将elementData中每个元素都赋值为null,等待垃圾回收将这个给回收掉,所以叫clear

public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}

\1. removeAll(collection c)

public boolean removeAll(Collection<?> c) {
return batchRemove(c, false);//批量删除
}

\1. batchRemove(xx,xx):用于两个方法,一个removeAll():它只清楚指定集合中的元素,retainAll() 用来测试两个集合是否有交集。

//这个方法,用于两处地方,如果complement为false,则用于removeAll如果为true,则给
retainAll()用,retainAll()是用来检测两个集合是否有交集的。
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData; //将原集合,记名为A
int r = 0, w = 0; //r用来控制循环,w是记录有多少个交集
    boolean modified = false;
try {
for (; r < size; r++)
//参数中的集合C一次检测集合A中的元素是否有,
if (c.contains(elementData[r]) == complement)
//有的话,就给集合A
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
//如果contains方法使用过程报异常
if (r != size) {
//将剩下的元素都赋值给集合A,
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
//这里有两个用途,在removeAll()时,w一直为0,就直接跟clear一样,全是为
null。
//retainAll():没有一个交集返回true,有交集但不全交也返回true,而两个集合
相等的时候,返回false,所以不能根据返回值来确认两个集合是否有交集,而是通过原集合的大小是否
发生改变来判断,如果原集合中还有元素,则代表有交集,而元集合没有元素了,说明两个集合没有交
集。
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}

总结:remove函数,用户移除指定下标的元素,此时会把指定下标到数组末尾的元素向前移动一个单 位,并且会把数组最后一个元素设置为null,这样是为了方便之后将整个数组不被使用时,会被GC,可 以作为小的技巧使用。

6、其他方法

【set()方法】

说明:设定指定下标索引的元素值

public E set(int index, E element) {
// 检验索引是否合法
rangeCheck(index);
// 旧值
E oldValue = elementData(index);
// 赋新值
elementData[index] = element;
// 返回旧值
return oldValue;
}

【indexOf()方法】

说明:从头开始查找与指定元素相等的元素,注意,是可以查找null元素的,意味着ArrayList中可以存 放null元素的。与此函数对应的lastIndexOf,表示从尾部开始查找。

// 从首开始查找数组里面是否存在指定元素
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;
}

【get()方法】

public E get(int index) {
// 检验索引是否合法
rangeCheck(index);
return elementData(index);
}

说明:get函数会检查索引值是否合法(只检查是否大于size,而没有检查是否小于0),值得注意的 是,在get函数中存在element函数,element函数用于返回具体的元素,具体函数如下:

E elementData(int index) {
return (E) elementData[index];
}

说明:返回的值都经过了向下转型(Object -> E),这些是对我们应用程序屏蔽的小细节。

4、总结

1)arrayList可以存放null。

2)arrayList本质上就是一个elementData数组。

3)arrayList区别于数组的地方在于能够自动扩展大小,其中关键的方法就是grow()方法。

4)arrayList中removeAll(collection c)和clear()的区别就是removeAll可以删除批量指定的元素,而 clear是全是删除集合中的元素。

5)arrayList由于本质是数组,所以它在数据的查询方面会很快,而在插入删除这些方面,性能下降很 多,有移动很多数据才能达到应有的效果

6)arrayList实现了RandomAccess,所以在遍历它的时候推荐使用for循环。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值