• 陆离 置顶
    2019-05-30
    对于arraylist和linkedlist的性能以前一直都是人云亦云,大家都说是这样那就这样吧,我也从来没有自己去验证过,没想过因操作位置的不同差异还挺大。
    当然这里面有一个前提,那就是arraylist的初始大小要足够大。
    思考题是第一个是正确的,第二个虽然用的是foreach语法糖,遍历的时候用的也是迭代器遍历,但是在remove操作时使用的是原始数组list的remove,而不是迭代器的remove。
    这样就会造成modCound != exceptedModeCount,进而抛出异常。

    作者回复: 陆离同学一直保持非常稳定的发挥,答案非常准确!

    
     37
  • 刘天若Warner 置顶
    2019-05-30
    老师,为什么第二种就会抛出`ConcurrentModificationException`异常呢,我觉得第一种迭代器会抛这个异常啊

    作者回复: for(:)循环[这里指的不是for(;;)]是一个语法糖,这里会被解释为迭代器,在使用迭代器遍历时,ArrayList内部创建了一个内部迭代器iterator,在使用next()方法来取下一个元素时,会使用ArrayList里保存的一个用来记录List修改次数的变量modCount,与iterator保存了一个expectedModCount来表示期望的修改次数进行比较,如果不相等则会抛出异常;

    而在在foreach循环中调用list中的remove()方法,会走到fastRemove()方法,该方法不是iterator中的方法,而是ArrayList中的方法,在该方法只做了modCount++,而没有同步到expectedModCount。

    当再次遍历时,会先调用内部类iteator中的hasNext(),再调用next(),在调用next()方法时,会对modCount和expectedModCount进行比较,此时两者不一致,就抛出了ConcurrentModificationException异常。

    所以关键是用ArrayList的remove还是iterator中的remove。

    
     21
  • 皮皮
    2019-05-30
    第一种写法正确,第二种会报错,原因是上述两种写法都有用到list内部迭代器Iterator,而在迭代器内部有一个属性是exceptedmodcount,每次调用next和remove方法时会检查该值和list内部的modcount是否一致,不一致会报异常。问题中的第二种写法remove(e),会在每次调用时modcount++,虽然迭代器的remove方法也会调用list的这个remove(e)方法,但每次调用后还有一个exceptedmodcount=modcount操作,所以后续调用next时判断就不会报异常了。

    作者回复: 关键在用谁的remove方法。

    
     9
  • Loubobooo
    2019-06-01
    这一道我会。如果有看过阿里java规约就知道,在集合中进行remove操作时,不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁。
    <!-- 规约第七条 -->

    作者回复: 👍

    
     4
  • 建国
    2019-06-10
    老师,您好,linkList查找元素通过分前后半段,每次查找都要遍历半个list,怎么就知道元素是出于前半段还是后半段的呢?

    作者回复: 这个是随机的,因为分配的内存地址不是连续的。

     3
     3
  • JasonZ
    2019-06-09
    linkedlist使用iterator比普通for循环效率高,是由于遍历次数少,这是为什么?有什么文档可以参考么?

    作者回复: 因为for循环需要遍历链表,每循环一次就需要遍历一次指定节点前的数据,源码如下:

    // 获取双向链表中指定位置的节点
        private Entry<E> entry(int index) {
            if (index < 0 || index >= size)
                throw new IndexOutOfBoundsException("Index: "+index+
                                                    ", Size: "+size);
            Entry<E> e = header;
            // 获取index处的节点。
            // 若index < 双向链表长度的1/2,则从前先后查找;
            // 否则,从后向前查找。
            if (index < (size >> 1)) {
                for (int i = 0; i <= index; i++)
                    e = e.next;
            } else {
                for (int i = size; i > index; i--)
                    e = e.previous;
            }
            return e;
        }

    而iterator在第一次拿到一个数据后,之后的循环中会使用Iterator中的next()方法采用的是顺序访问。

     5
     3
  • ABC
    2019-05-31
    谢谢老师明白了,如果第二种写法换成for(;;)就会直接调用ArrayList的remove()方法就不会报错了。

    在第二种写法里面用foreach相当于是使用ArrayList内部的Itr类进行遍历,但删除数据又是用的ArrayList里面的remove()方法。从而导致状态不一致,引发报错。
    
     3
  • 业余草
    2019-05-31
    请问:List<A> list = new ArrayList<>();
    for(int i=0;i++;i<1000){
     A a = new A();
     list.add(a);
    }
    和
    和 这个 List<A> list = new ArrayList<>();
    A a;
    for(int i=0;i++;i<1000){
     a = new A();
     list.add(a);
    }
    效率上有差别吗?不说new ArrayList<>(); 初始化问题。单纯说创建对象这一块。谢谢!
    展开

    作者回复: 没啥区别的,可以实际操作试试

    
     3
  • L.
    2019-08-06
    老师,随机访问到底是什么意思?怎么个随机法?谢谢~

    作者回复: 这里指的是不需要通过遍历寻址,可以通过index直接访问到内存地址。

    
     2
  • 涛哥
    2019-07-17
    arrayList,for循环访问快是因为内存连续,可以整个缓存行读取进cpu缓存中,遍历下个的时候无需去内存中获取。并不是实现什么随机获取接口

    作者回复: 是的,是因为连续内存。在代码中,程序是不知道底层开辟的内存情况,所以需要一个类似序列化的接口标志,这个接口仅仅是一个标志,并不是实现。

     1
     2
  • DebugDog
    2019-05-30
    写法一正确。
    虽然都是调用了remove方法,但是两个remove方法是不同的。
    写法二是有可能会报ConcurrentModificationException异常。
    所以在ArrayList遍历删除元素时使用iterator方式或者普通的for循环。

    作者回复: 对的,使用普通循环也需要注意。

    
     2
  • mickle
    2019-05-30
    第二种不行吧,会报并发修改异常的
    
     2
  • 老杨同志
    2019-05-30
    写法一正确,写法二会快速失败
    
     2
  • 张三丰
    2019-11-06
    first/last 方式可以在初始化 LinkedList 的时候节省 new 一个 Entry;

    来回看老师的专栏,这是第三遍了,每次都会有新的理解,同时也有新的疑问产生,比如上边的那句话今天一直没有想明白。。。。麻烦老师详细解答一下。
    
     1
  • TerryGoForIt
    2019-05-31
    老师您好,我比较好奇的是为什么 ArrayList 不像 HashMap 一样在扩容时需要一个负载因子呢?

    作者回复: HashMap有负载因子是既要考虑数组太短,因哈希冲突导致链表过长而导致查询性能下降,也考虑了数组过长,新增数据时性能下降。这个负载因子是综合了数组和链表两者的长度,不能太大也不能太小。而ArrayList不需要这种考虑。

    
     1
  • 晓杰
    2019-05-30
    写法2不正确,使用for循环遍历元素的过程中,如果删除元素,由于modCount != expectedModCount,会抛出ConcurrentModificationException异常

    作者回复: 对的!

    
     1
  • csyangchsh
    2019-05-30
    测试代码不严谨,建议使用JMH。

    作者回复: 厉害了,感谢建议。这里很多同学没有了解过JMH测试框架,所以没有使用。

    
     1
  • 每天晒白牙
    2019-05-30
    需要用迭代器方式删除
    for循环遍历删除会抛并发修改异常

    作者回复: 是的,不要使用迭代器循环时用ArrayList的remove方法,具体分析可以看留言区。

    
     1
  • gogo
    2019-12-24
    modCount属于ArrayList
    expectedModCount属于Iterator

    增强for循环 本质是iterator遍历
    iterator循环 iterator遍历

    增强for循环 调用list.remove() 不会修改到iterator的expectedModCount, 从而导致 迭代器的expectedModCount != ArrayList的modCound; 迭代器会抛出 concurrentModifiedException

    而iterator遍历 的时候 用iterator. remove(); modCount 会被同步到expectedModCount中去,ArrayList的modCount == Iterator的exceptedModCount,所以不会抛出异常。


    老师对其他同学的评论以及我的理解就是这样。
    展开

    作者回复: 赞

    
    
  • 天天向上
    2019-12-22
    对于linkedList的遍历、remove、get方法,都会用到java.util.LinkedList#node方法,而老师讲到的删除位置的查找,遍历的问题,都是因为这个方法中的内容。
    
    
我们在线,来聊聊吧