源码解析系列:ArrayList(4) - set、get、clear、contains、toString方法和迭代器


前言

这一部分将会介绍源码中set、get、clear、contains、toString方法和迭代器,参考视频: ArrayList源码解析

ArrayList源码解析(1):ArrayList源码解析(1)
ArrayList源码解析(2):ArrayList源码解析(2)
ArrayList源码解析(3):ArrayList源码解析(3)
ArrayList源码解析(4):ArrayList源码解析(4)
ArrayList源码解析(5):ArrayList源码解析(5)


1. set方法

public E set(int index, E element):根据下标修改元素

public E set(int index, E element) {
	//检查下标是否越界
    rangeCheck(index);
	//通过下标获取元素
    E oldValue = elementData(index);
    //设置新的元素
    elementData[index] = element;
    //返回旧的值
    return oldValue;
}

private void rangeCheck(int index) {
	//下标越界就抛出异常
 	if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

总结流程:

  1. 检查下标是否越界
  2. 通过下标获取元素
  3. 设置新的元素
  4. 返回旧的值



2. get方法

public E get(int index):根据索引获取元素

public E get(int index) {
	//检查下标越界
    rangeCheck(index);
    //返回元素
    return elementData(index);
}

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

总结流程:

  1. 检查下标是否越界
  2. 通过下标查找返回值



3. toString()方法

public String toString():打印字符串方法

public String toString() {
	 //获取迭代器
     Iterator<E> it = iterator();
     //如果没有下一个,返回空
     if (! it.hasNext())
         return "[]";
	 //创建初一个StringBuilder
     StringBuilder sb = new StringBuilder();
     //下面使用append方法进行凭借,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(' ');
     }
 }



4. 迭代器

public Iterator iterator() 普通迭代器。下面在进行迭代器的源码分析的时候我们先通过两个案例来看看迭代器的用法和一些特殊的地方

4.1 使用迭代器遍历集合

public class sourceTest {
    public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("aaa");
        arrayList.add("bbb");
        arrayList.add("ccc");
        final Iterator<String> iterator = arrayList.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}

输出结果:
在这里插入图片描述


那么在这个基础上我们进行迭代器的方法的分析:

1、基础的源码

public Iterator<E> iterator() {
	//在iterator方法中是new了一个ArrayList自己的迭代器
	return new Itr();
}

//迭代器
private class Itr implements Iterator<E> {
        int cursor;       // 光标,用于指向数组
        int lastRet = -1; // 一开始记录为-1
        int expectedModCount = modCount; 	//把集合的修改次数赋值给预期修改次数	

        Itr() {}
}

2、hasNext和next方法

public boolean hasNext() {
	//判断光标有没有等于size,如果不等于证明此时还没有遍历完成
  	return cursor != size;
}

public E next() {
	//判断预期修改次数和实际修改次数
    checkForComodification();
    //获取光标的位置,光标是从0开始的
    int i = cursor;
    //如果i>-=size, 证明到达了结尾了
    if (i >= size)
        throw new NoSuchElementException();
    //获取集合中的数据元素
    Object[] elementData = ArrayList.this.elementData;
    //如果 i >= 元素的长度,证明产生了并发修改异常
    //上面既然 i < size 但是 i >= elementData.length, 证明了此时elementData已经其他线程被修改了,导致长度对不上
    if (i >= elementData.length)
        throw new ConcurrentModificationException();
    //都正常情况下,就光标指向下一个
    cursor = i + 1;
   	//然后获取当前的这一个元素,并且对lastRet赋值为i
    return (E) elementData[lastRet = i];
}

final void checkForComodification() {
	//如果预期修改次数不等于实际修改次数,发生并发修改异常
	//正常情况下遍历是不会出现这种情况的
 	if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}



4.2 使用迭代器遍历删除指定元素

public class sourceTest {
    public static void main(String[] args) {
        //创建集合对象
        List<String> list = new ArrayList<String>();
        //添加元素
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        //获取迭代器
        Iterator<String> it = list.iterator();
        //遍历集合
        while(it.hasNext()) {
            String s = it.next();
            if(s.equals("aaa")) {
                list.remove("aaa");
            }
        }
    }
}

结果输出:
在这里插入图片描述

可以看到发生了并发修改异常,那么为什么会发生这种异常呢?我们到源码中去看看

//首先要知道执行到list.iterator();的时候modCount也就是实际修改次数 = 3
//因为add方法每调用一次这个变量就+1
modCount++;

//在调用到list.iterator()的时候期望修改次数被修改为了3
expectedModCount = modCount = 3

//然后来到了next方法中
public E next() {
	//这一步就是检测有没有并发修改异常
    checkForComodification();
    int i = cursor;
    if (i >= size)
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length)
        throw new ConcurrentModificationException();
    cursor = i + 1;
    return (E) elementData[lastRet = i];
}

final void checkForComodification() {
	//如果实际修改次数 != 期望修改次数,就抛出异常
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

上面都是迭代器中的一些基本的代码,下面就是最关键的remove方法,这个方法在前面的文章也有介绍,就不详细说了,关键是fastRemove,在fastRemove中对modCount进行++了

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;
}

private void fastRemove(int index) {
	//在fastRemove中对实际修改次数++了
    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
}

在fastRemove中对modCount++之后,到了下一次判断的时候modCount=4,而expectedModCount=3,自然就不相等了,就会抛出异常。



4.3 使用迭代器遍历删除指定元素

还是和上面4.2的一样,只不过这里我们删除倒数第二个元素

public class sourceTest {
    public static void main(String[] args) {
        //创建集合对象
        List<String> list = new ArrayList<String>();
        //添加元素
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        //获取迭代器
        Iterator<String> it = list.iterator();
        //遍历集合
        while(it.hasNext()) {
            String s = it.next();
            if(s.equals("bbb")) {
                list.remove("bbb");
            }
        }
        System.out.println(list);
    }
}

结果输出:
在这里插入图片描述

其实源码和上面的一样,不同的地方就在当遍历到第二个的时候,由于使用了remove方法,这时候size从3变成了2,然后光标 cursor = 2,那么这时候hasNext方法就返回false了,直接退出。也就是说根本不会遍历到最后一行。我们可以做个测试,往list里面加入两个bbb,然后删除倒数第二个:

list.add("aaa");
list.add("bbb");
list.add("bbb");

结果输出:
在这里插入图片描述

可以看到最后一个根本不会遍历到。



4.4 使用迭代器自带的删除元素的方法

public class sourceTest {
    public static void main(String[] args) {
        //创建集合对象
        List<String> list = new ArrayList<String>();
        //添加元素
        list.add("aaa");
        list.add("bbb");
        list.add("bbb");
        //获取迭代器
        Iterator<String> it = list.iterator();
        //遍历集合
        while(it.hasNext()) {
            String s = it.next();
            if(s.equals("bbb")) {
                it.remove();
            }
        }
        System.out.println(list);
    }
}

输出结果:
在这里插入图片描述

可以看到,使用迭代器自带的remove方法就可以安全删除,再来看看源码:

public void remove() {
	//如果lastRet<0, 那么证明没有遍历到,抛异常
    if (lastRet < 0)
        throw new IllegalStateException();
    //查看期望修改次数是否等于实际修改次数
    checkForComodification();

    try {
    	//调用ArrayList的remove方法
        ArrayList.this.remove(lastRet);
        //然后lastRet赋值给光标
        cursor = lastRet;
        //重置为-1
        lastRet = -1;
        //关键一步来了,这里重新对expectedModCount进行赋值了,所以checkForComodification
        //方法中是不会不相同的,也就没有并发修改异常了
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

里面关键的地方就是重新赋值了expectedModCount = modCount,所以后面判断就不会成立。



5. clear方法

public void clear() {
	//修改次数++
	modCount++;
	
	// 赋值为null等待GC请理
	for (int i = 0; i < size; i++)
	    elementData[i] = null;
	//设置size = 0
	size = 0;
}

总结流程:

  1. 修改次数++
  2. 赋值为null等待GC清理
  3. size设置为0



6. contains方法

public boolean contains(Object o) {
	//求出下标
 	return indexOf(o) >= 0;
}

//求出下标
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++)
        	//也是遍历全部使用equals方法来判断
            if (o.equals(elementData[i]))
                return i;
    }
    //如果都没有找到就返回-1
    return -1;
}

总结:

  1. 求出下标
  2. 如果下标 >= 0, 就是找到了,如果等于-1就是没找到





如有错误,欢迎指出!!!!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值