java的集合体系

java集合的体系

转载自
https://blog.csdn.net/zhangqunshuai/article/details/80660974

集合的遍历与删除

转自
https://blog.csdn.net/demohui/article/details/77748809

相信大家对集合遍历再熟悉不过了,这里总结一下HashMap和List的遍历方法,以及它们该如何实现遍历删除。

这里对于每种遍历删除出现的问题的原因都给出了详解!

(一)List的遍历方法及如何实现遍历删除
我们造一个list出来,接下来用不同方法遍历删除,如下代码:

    List<String> list= new ArrayList<String>();
    famous.add("zs");
    famous.add("ls");
    famous.add("ww");
    famous.add("dz");

1、for循环遍历list:

        for(int i=0;i<list.size();i++){
              if(list.get(i).equals("ls"))
              list.remove(i);
            }

这是一种很常见的遍历方式,但是使用这种遍历删除元素会出现问题,原因在于删除某个元素后,list的大小发生了变化,而你的索引
也在变化,所以会导致你在遍历的时候漏掉某些元素。比如当你删除第一个元素后,继续根据索引访问第二个元素后,因为删除的原因,
后面的元素都往前移动了以为,所以实际访问的是第三个元素。因此,这种遍历方式可以用在读取元素,而不适合删除元素。

2、增强for循环:

       for(String x:list){
           if(x.equals("ls"))
           list.remove(x);
        }

这也是一种很常见的遍历方式,但是使用这种遍历删除元素也会出现问题,运行时会报ConcurrentModificationException异常
其实增强for循环是java语法糖的一种体现,如果大家通过反编译得到字节码,那么上面这段代码的内部实现如下所示:

for(Iterator<String> it = list.iterator();it.hasNext();){
         String s = it.next();
         if(s.equals("madehua")){
             list.remove(s);
         }
     }

下面就解释为什么会报ConcurrentModificationException异常。分析Iterator的源代码,重点分析整个调用该过程中的
函数(hasNext和remove):

private class Itr implements Iterator<E> {
            int cursor;       // index of next element to return
            int lastRet = -1; // index of last element returned; -1 if no such
            int expectedModCount = modCount;
 
 
            public boolean hasNext() {
                return cursor != size; // size为集合中元素的个数
            }
 
 
            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];
            }
 
 
            /* 此方法并没被调用,只是调用List.remove方法
            public void remove() {
                checkForComodification();
                try {
                    ArrayList.this.remove(lastRet); // size字段减1
                    cursor = lastRet;
                    lastRet = -1;
                    expectedModCount = modCount;
                } catch (IndexOutOfBoundsException ex) {
                    throw new ConcurrentModificationException();
                }
            }
            */
 
 
            final void checkForComodification() {   // 检查修改和当前版本号是否一致,不一致则抛出异常
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
            }
 
 
        }
 
 
        // List.remove
        @Override public boolean remove(Object object) {
            Object[] a = array;
            int s = size;
            if (object != null) {
                for (int i = 0; i < s; i++) {
                    if (object.equals(a[i])) {
                        System.arraycopy(a, i + 1, a, i, --s - i);
                        a[s] = null;  // Prevent memory leak
                        size = s;
                        modCount++; // 核心代码:修改了版本号。这样当checkForComodification的时候,modCount值就和expectedModCount不同
                        return true;
                    }
                }
            } else {
                for (int i = 0; i < s; i++) {
                    if (a[i] == null) {
                        System.arraycopy(a, i + 1, a, i, --s - i);
                        a[s] = null;  // Prevent memory leak
                        size = s;
                        modCount++;
                        return true;
                    }
                }
            }
            return false;
        }

接下来梳理一下流程,这时候你就会发现这个异常是在next方法的checkForComodification中抛出的。抛出的原因是
modCount !=expectedModCount。这里的modCount是指这个list对象从呢我出来到现在被修改的次数,当调用list
的add或者remove方法的时候,这个modCount都会自动增减;iterator创建的时候modCount被复制给了
expectedModcount,但是调用list的add和remove方法的时候不会同时自动增减expectedModcount,这样就导致
两个count不相等,从而抛出异常。大家如果理解了上面的执行流程,以后碰到类似这种问题,比如如果删除的是倒数
第二个元素却不会碰到异常。就会知道为什么了。
3、iterator遍历删除:

     Iterator<String> it = list.iterator();
     while(it.hasNext()){
       String x = it.next();
       if(x.equals("del")){
           it.remove();
      }
    }

这种方式是可以正常遍历和删除的。但是你可能看到上面代码感觉和增强for循环内部实现的代码差不多,其实差别就在于上面使用
一个使用list.remove(),一个使用it.remove()。
(二)HashMap的遍历删除及如何实现遍历删除
一样我们先造一个hashmap出来,如下:

    private static HashMap<Integer, String> map = new HashMap<Integer, String>();;
 
    public static void main(String[] args) {
        
         for(int i = 0; i < 10; i++){  
                map.put(i, "value" + i);  
            }  
      
    
    }

1、第一种遍历删除:

        for(Map.Entry<Integer, String> entry : map.entrySet()){  
             Integer key = entry.getKey();  
             if(key % 2 == 0){  
                 System.out.println("To delete key " + key);  
                 map.remove(key);  
                 System.out.println("The key " + + key + " was deleted");  
             }  

这种遍历删除依旧会报ConcurrentModificationException异常,

2、第二种遍历删除:

       Set<Integer> keySet = map.keySet();
         for(Integer key : keySet){
                if(key % 2 == 0){
                    System.out.println("To delete key " + key);
                    keySet.remove(key);
                    System.out.println("The key " + + key + " was deleted");
                }
         }

这种遍历删除依旧会报ConcurrentModificationException异常,

3、第三种遍历删除:

   Iterator<Map.Entry<Integer, String>> it = map.entrySet().iterator();
        while(it.hasNext()){
            Map.Entry<Integer, String> entry = it.next();
            Integer key = entry.getKey();
            if(key % 2 == 0){
             System.out.println("To delete key " + key);
             it.remove();    
             System.out.println("The key " + + key + " was deleted");
 
            }
        }

这种遍历是OK的

分析上述原因,如果大家理解了List的遍历删除,那么感觉HashMap的遍历删除是不是有类似之处啊。下面就分析一下原因:
如果查询源代码以上的三种的删除方式都是通过调用HashMap.removeEntryForKey方法来实现删除key的操作。
在removeEntryForKey方法内知识一场了key modCount就会执行一次自增操作,此时modCount就与expectedModCOunt不一致了
,上面三种remove实现中,只有第三种iterator的remove方法在调用完removeEntryForKey方法后同步了expectedModCount值与
modCount相同,所以iterator方式不会抛出异常。最后希望大家遇到问题到查询源代码,它会给你最好的解释!

————————————————
版权声明:本文为CSDN博主「demohui」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/demohui/article/details/77748809

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341