精华内容
下载资源
问答
  • 这是我正在运行的代码:HashMap> items = new HashMap>();for (final TicketItem key : items.keySet()) {Log.i(key.equals(item));Log.i(key.hashCode() + " " + item.hashCode());}Log.i(it...

    我已经阅读了关于这个问题的所有答案,似乎没有任何工作.

    这是我正在运行的代码:

    HashMap> items = new HashMap>();

    for (final TicketItem key : items.keySet()) {

    Log.i(key.equals(item));

    Log.i(key.hashCode() + " " + item.hashCode());

    }

    Log.i(items.size());

    Log.i("C: " + items.containsKey(item));

    items.remove(item);

    Log.i("C: " + items.containsKey(item));

    Log.i(items.size());

    for (final TicketItem key : items.keySet()) {

    Log.i(key.equals(item));

    Log.i(key.hashCode() + " " + item.hashCode());

    }

    我无法提供工作代码,因为它确实是一个巨大的项目.我已经将所有内容都打印出来(正如您在我的示例代码中看到的那样),因此您知道HashMap使用的值是正确的.

    日志:

    03-22 18:58:10.125: I/POSDoes(29790): true

    03-22 18:58:10.125: I/POSDoes(29790): -823765791 -823765791

    03-22 18:58:10.125: I/POSDoes(29790): false

    03-22 18:58:10.125: I/POSDoes(29790): 1543283745 -823765791

    03-22 18:58:10.125: I/POSDoes(29790): false

    03-22 18:58:10.125: I/POSDoes(29790): 427224321 -823765791

    03-22 18:58:10.125: I/POSDoes(29790): false

    03-22 18:58:10.125: I/POSDoes(29790): -616760351 -823765791

    03-22 18:58:10.125: I/POSDoes(29790): 4

    03-22 18:58:10.125: I/POSDoes(29790): C: true

    03-22 18:58:10.130: I/POSDoes(29790): C: true

    03-22 18:58:10.130: I/POSDoes(29790): 4

    03-22 18:58:10.130: I/POSDoes(29790): true

    03-22 18:58:10.130: I/POSDoes(29790): -823765791 -823765791

    03-22 18:58:10.130: I/POSDoes(29790): false

    03-22 18:58:10.130: I/POSDoes(29790): 1543283745 -823765791

    03-22 18:58:10.130: I/POSDoes(29790): false

    03-22 18:58:10.130: I/POSDoes(29790): 427224321 -823765791

    03-22 18:58:10.130: I/POSDoes(29790): false

    03-22 18:58:10.130: I/POSDoes(29790): -616760351 -823765791

    正如您所看到的,哈希码匹配,并且equals方法返回true,但是当打印出包含时,我在调用remove方法之前和之后都收到了true.为什么这不起作用?

    更新

    我打印出item.equals(key)的结果相同.

    for (final TicketItem key : items.keySet()) {

    Log.i(item.equals(key));

    Log.i(key.equals(item));

    Log.i(key.hashCode() + " " + item.hashCode());

    }

    Log.i(items.size());

    Log.i("C: " + items.containsKey(item));

    items.remove(item);

    Log.i("C: " + items.containsKey(item));

    Log.i(items.size());

    for (final TicketItem key : items.keySet()) {

    Log.i(item.equals(key));

    Log.i(key.equals(item));

    Log.i(key.hashCode() + " " + item.hashCode());

    }

    日志:

    03-22 19:09:14.360: I/POSDoes(30458): true

    03-22 19:09:14.360: I/POSDoes(30458): true

    03-22 19:09:14.360: I/POSDoes(30458): 1543283745 1543283745

    03-22 19:09:14.365: I/POSDoes(30458): false

    03-22 19:09:14.365: I/POSDoes(30458): false

    03-22 19:09:14.365: I/POSDoes(30458): 427224321 1543283745

    03-22 19:09:14.365: I/POSDoes(30458): false

    03-22 19:09:14.365: I/POSDoes(30458): false

    03-22 19:09:14.365: I/POSDoes(30458): -616760351 1543283745

    03-22 19:09:14.365: I/POSDoes(30458): 3

    03-22 19:09:14.365: I/POSDoes(30458): C: true

    03-22 19:09:14.365: I/POSDoes(30458): C: true

    03-22 19:09:14.365: I/POSDoes(30458): 3

    03-22 19:09:14.365: I/POSDoes(30458): true

    03-22 19:09:14.365: I/POSDoes(30458): true

    03-22 19:09:14.365: I/POSDoes(30458): 1543283745 1543283745

    03-22 19:09:14.365: I/POSDoes(30458): false

    03-22 19:09:14.365: I/POSDoes(30458): false

    03-22 19:09:14.365: I/POSDoes(30458): 427224321 1543283745

    03-22 19:09:14.370: I/POSDoes(30458): false

    03-22 19:09:14.370: I/POSDoes(30458): false

    03-22 19:09:14.370: I/POSDoes(30458): -616760351 1543283745

    更新2

    这是equals()的实现

    @Override

    public boolean equals(Object obj) {

    if (this == obj)

    return true;

    if (!super.equals(obj))

    return false;

    if (this.getClass() != obj.getClass())

    return false;

    TicketItem other = (TicketItem) obj;

    if (!getValues().equals(other.getValues()))

    return false;

    return true;

    }

    getValues()返回一个具有任意(但相等!)值的LinkedHashMap.

    更新3

    equals方法正确传递(没有问题).然而,似乎哈希码正在改变.当我调用remove函数时,哈希映射中保存的所有哈希都返回相等.生成的哈希码实际上只是一个不同的LinkedHashMap的哈希码.这意味着TicketItem的hashCode()函数使用LinkedHashMap的hashCode()函数.

    展开全文
  • 第2种方法 - 普通for循环倒序删除(结果:正确删除) 第3种方法 - for-each循环删除(结果:抛出异常) 第4种方法 - Iterator遍历,使用ArrayList.remove()删除元素(结果:抛出异常) 第5种方法 - Iterator遍历...

    目录

    ArrayList遍历时删除元素的几种姿势

    第1种方法 - 普通for循环正序删除(结果:会漏掉元素判断)

    第2种方法 - 普通for循环倒序删除(结果:正确删除)

    第3种方法 - for-each循环删除(结果:抛出异常)

    第4种方法 - Iterator遍历,使用ArrayList.remove()删除元素(结果:抛出异常)

    第5种方法 - Iterator遍历,使用Iterator的remove删除元素(结果:正确删除)

    HashMap遍历时删除元素的几种姿势

    第1种方法 - for-each遍历HashMap.entrySet,使用HashMap.remove()删除(结果:抛出异常)

    第2种方法-for-each遍历HashMap.keySet,使用HashMap.remove()删除(结果:抛出异常)

    第3种方法-使用HashMap.entrySet().iterator()遍历删除(结果:正确删除)

    PS:ConcurrentModificationException是什么?

    正确方式

    1.删除ArrayList中的元素

    2.删除HashMap中的元素


    首先初始化一个数组arrayList,假设我们要删除等于3的元素。
       public static void main(String[] args) {
            ArrayList<Integer> arrayList = new ArrayList();
            arrayList.add(1);
            arrayList.add(2);
            arrayList.add(3);
            arrayList.add(3);
            arrayList.add(4);
            arrayList.add(5);
            removeWayOne(arrayList);
        }
    

    ArrayList遍历时删除元素的几种姿势

    首先结论如下:

    第1种方法 - 普通for循环正序删除(结果:会漏掉元素判断)

    第2种方法 - 普通for循环倒序删除(结果:正确删除)

    第3种方法 - for-each循环删除(结果:抛出异常)

    第4种方法 - Iterator遍历,使用ArrayList.remove()删除元素(结果:抛出异常)

    第5种方法 - Iterator遍历,使用Iterator的remove删除元素(结果:正确删除)

    下面让我们来详细探究一下原因吧!

    第1种方法 - 普通for循环正序删除(结果:会漏掉元素判断)

    for (int i = 0; i < arrayList.size(); i++) {
    	if (arrayList.get(i) == 3) {//3是要删除的元素
    		arrayList.remove(i);
    		//解决方案: 加一行代码i = i - 1; 删除元素后,下标减1
    	}
        System.out.println("当前arrayList是"+arrayList.toString());
    }
    //原ArrayList是[1, 2, 3, 3, 4, 5]
    //删除后是[1, 2, 3, 4, 5]
    复制代码

    输出结果:

    当前arrayList是[1, 2, 3, 3, 4, 5]
    当前arrayList是[1, 2, 3, 3, 4, 5]
    当前arrayList是[1, 2, 3, 4, 5]
    当前arrayList是[1, 2, 3, 4, 5]
    当前arrayList是[1, 2, 3, 4, 5]
    复制代码

    可以看到少删除了一个3,

    原因在于调用remove删除元素时,remove方法调用System.arraycopy()方法将后面的元素移动到前面的位置,也就是第二个3会移动到数组下标为2的位置,而在下一次循环时,i+1之后,i会为3,不会对数组下标为2这个位置进行判断,所以这种写法,在删除元素时,被删除元素a的后一个元素b会移动a的位置,而i已经加1,会忽略对元素b的判断,所以如果是连续的重复元素,会导致少删除。

    解决方案

    可以在删除元素后,执行i=i-1,使得下次循环时再次对该数组下标进行判断。

    第2种方法 - 普通for循环倒序删除(结果:正确删除)

     for (int i = arrayList.size() -1 ; i>=0; i--) {
        if (arrayList.get(i).equals(3)) {
            arrayList.remove(i);
        }
        System.out.println("当前arrayList是"+arrayList.toString());
    }
    复制代码

    输出结果:

    当前arrayList是[1, 2, 3, 3, 4, 5]
    当前arrayList是[1, 2, 3, 3, 4, 5]
    当前arrayList是[1, 2, 3, 4, 5]
    当前arrayList是[1, 2, 4, 5]
    当前arrayList是[1, 2, 4, 5]
    当前arrayList是[1, 2, 4, 5]
    复制代码

    这种方法可以正确删除元素,因为调用remove删除元素时,remove方法调用System.arraycopy()将被删除元素a后面的元素向前移动,而不会影响元素a之前的元素,所以倒序遍历可以正常删除元素。

    第3种方法 - for-each循环删除(结果:抛出异常)

    public static void removeWayThree(ArrayList<Integer> arrayList) {
        for (Integer value : arrayList) {
            if (value.equals(3)) {//3是要删除的元素
                arrayList.remove(value);
            }
        System.out.println("当前arrayList是"+arrayList.toString());
        }
    }
    复制代码

    输出结果:

    当前arrayList是[1, 2, 3, 3, 4, 5]
    当前arrayList是[1, 2, 3, 3, 4, 5]
    当前arrayList是[1, 2, 3, 4, 5]
    Exception in thread "main" java.util.ConcurrentModificationException
    	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    	at java.util.ArrayList$Itr.next(ArrayList.java:851)
    	at com.test.ArrayListTest1.removeWayThree(ArrayListTest1.java:50)
    	at com.test.ArrayListTest1.main(ArrayListTest1.java:24)
    复制代码

    会抛出ConcurrentModificationException异常,主要在于for-each的底层实现是使用ArrayList.iterator的hasNext()方法和next()方法实现的,我们可以使用反编译进行验证,对包含上面的方法的类使用以下命令反编译验证

    javac ArrayTest.java//生成ArrayTest.class文件
    javap -c ArrayListTest.class//对class文件反编译
    复制代码

    得到removeWayThree方法的反编译代码如下:

     public static void removeWayThree(java.util.ArrayList<java.lang.Integer>);
        Code:
           0: aload_0
           1: invokevirtual #12   // Method java/util/ArrayList.iterator:()Ljava/util/Iterator;
           4: astore_1
           5: aload_1
           6: invokeinterface #13,  1 // InterfaceMethod java/util/Iterator.hasNext:()Z   调用Iterator.hasNext()方法
          11: ifeq          44
          14: aload_1
          15: invokeinterface #14,  1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;调用Iterator.next()方法
          20: checkcast     #9                  // class java/lang/Integer
          23: astore_2
          24: aload_2
          25: iconst_3
          26: invokestatic  #4                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
          29: invokevirtual #10                 // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z 
          32: ifeq          41
          35: aload_0
          36: aload_2
          37: invokevirtual #15                 // Method java/util/ArrayList.remove:(Ljava/lang/Object;)Z
          40: pop
          41: goto          5
          44: return
    复制代码

    可以很清楚得看到Iterator.hasNext()来判断是否还有下一个元素,和Iterator.next()方法来获取下一个元素。而因为在删除元素时,remove()方法会调用fastRemove()方法,其中会对modCount+1,代表对数组进行了修改,将修改次数+1。

     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) {
        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
    }
    
    
    复制代码

    而当删除完元素后,进行下一次循环时,会调用下面源码中Itr.next()方法获取下一个元素,会调用checkForComodification()方法对ArrayList进行校验,判断在遍历ArrayList是否已经被修改,由于之前对modCount+1,而expectedModCount还是初始化时ArrayList.Itr对象时赋的值,所以会不相等,然后抛出ConcurrentModificationException异常。

    那么有什么办法可以让expectedModCount及时更新呢?

    可以看到下面Itr的源码中,在Itr.remove()方法中删除元素后会对 expectedModCount更新,所以我们在使用删除元素时使用Itr.remove()方法来删除元素就可以保证expectedModCount的更新了,具体看第5种方法。

    private class Itr implements Iterator<E> {
            int cursor;       // 游标
            int lastRet = -1; // index of last element returned; -1 if no such
            int expectedModCount = modCount;//期待的modCount值
    
            public boolean hasNext() {
                return cursor != size;
            }
    
            @SuppressWarnings("unchecked")
            public E next() {
                checkForComodification();//判断expectedModCount与当前的modCount是否一致
                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];
            }
    
            public void remove() {
                if (lastRet < 0)
                    throw new IllegalStateException();
                checkForComodification();
                try {
                    ArrayList.this.remove(lastRet);
                    cursor = lastRet;
                    lastRet = -1;
                    expectedModCount = modCount;//更新expectedModCount
                } catch (IndexOutOfBoundsException ex) {
                    throw new ConcurrentModificationException();
                }
            }
    
            final void checkForComodification() {
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
            }
        }
    复制代码

    第4种方法 - Iterator遍历,使用ArrayList.remove()删除元素(结果:抛出异常)

    Iterator<Integer> iterator = arrayList.iterator();
    while (iterator.hasNext()) {
        Integer value = iterator.next();
        if (value.equals(3)) {//3是要删除的元素
        		arrayList.remove(value);
        }
        System.out.println("当前arrayList是"+arrayList.toString());
    }
    复制代码

    输出结果:

    当前arrayList是[1, 2, 3, 3, 4, 5]
    当前arrayList是[1, 2, 3, 3, 4, 5]
    当前arrayList是[1, 2, 3, 4, 5]
    Exception in thread "main" java.util.ConcurrentModificationException
    	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    	at java.util.ArrayList$Itr.next(ArrayList.java:851)
    	at com.test.ArrayListTest1.removeWayFour(ArrayListTest1.java:61)
    	at com.test.ArrayListTest1.main(ArrayListTest1.java:25)
    复制代码

    第3种方法在编译后的代码,其实是跟第4种是一样的,所以第四种写法也会抛出ConcurrentModificationException异常。这种需要注意的是,每次调用iterator的next()方法,会导致游标向右移动,从而达到遍历的目的。所以在单次循环中不能多次调用next()方法,不然会导致每次循环时跳过一些元素,我在一些博客里面看到了一些错误的写法,比如这一篇《在ArrayList的循环中删除元素,会不会出现问题?》文章中:

    ![image-20200101124822998](/Users/ruiwendaier/Library/Application Support/typora-user-images/image-20200101124822998.png)

    先调用iterator.next()获取元素,与elem进行比较,如果相等,再调用list.remove(iterator.next());来移除元素,这个时候的iterator.next()其实已经不是与elem相等的元素了,而是后一个元素了,我们可以写个demo来测试一下

    ArrayList<Integer> arrayList = new ArrayList();
    arrayList.add(1);
    arrayList.add(2);
    arrayList.add(3);
    arrayList.add(4);
    arrayList.add(5);
    arrayList.add(6);
    arrayList.add(7);
    
    Integer elem = 3;
    Iterator iterator = arrayList.iterator();
    while (iterator.hasNext()) {
        System.out.println(arrayList);
        if(iterator.next().equals(elem)) {
        		arrayList.remove(iterator.next());
        }
    } 
    复制代码

    输出结果如下:

    [1, 2, 3, 4, 5, 6, 7]
    [1, 2, 3, 4, 5, 6, 7]
    [1, 2, 3, 4, 5, 6, 7]
    [1, 2, 3, 5, 6, 7]
    Exception in thread "main" java.util.ConcurrentModificationException
    	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    	at java.util.ArrayList$Itr.next(ArrayList.java:851)
    	at com.test.ArrayListTest1.main(ArrayListTest1.java:29)
    复制代码

    可以看到移除的元素其实不是3,而是3之后的元素,因为调用了两次next()方法,导致游标多移动了。所以应该使用Integer value = iterator.next();将元素取出进行判断。

    第5种方法 - Iterator遍历,使用Iterator的remove删除元素(结果:正确删除)

    Iterator<Integer> iterator = arrayList.iterator();
    while (iterator.hasNext()) {
        Integer value = iterator.next();
        if (value.equals(3)) {//3是需要删除的元素
            iterator.remove();
        }
    }
    复制代码

    输出结果:

    当前arrayList是[1, 2, 3, 3, 4, 5]
    当前arrayList是[1, 2, 3, 3, 4, 5]
    当前arrayList是[1, 2, 3, 4, 5]
    当前arrayList是[1, 2, 4, 5]
    当前arrayList是[1, 2, 4, 5]
    当前arrayList是[1, 2, 4, 5]
    复制代码

    可以正确删除元素。

    跟第3种和第4种方法的区别在于是使用iterator.remove();来移除元素,而在remove()方法中会对iterator的expectedModCount变量进行更新,所以在下次循环调用iterator.next()方法时,expectedModCount与modCount相等,不会抛出异常。

    HashMap遍历时删除元素的几种姿势

    首先结论如下:

    第1种方法 - for-each遍历HashMap.entrySet,使用HashMap.remove()删除(结果:抛出异常)。

    第2种方法-for-each遍历HashMap.keySet,使用HashMap.remove()删除(结果:抛出异常)。

    第3种方法-使用HashMap.entrySet().iterator()遍历删除(结果:正确删除)。

    下面让我们来详细探究一下原因吧!

    HashMap的遍历删除方法与ArrayList的大同小异,只是api的调用方式不同。首先初始化一个HashMap,我们要删除key包含"3"字符串的键值对。

    HashMap<String,Integer> hashMap = new HashMap<String,Integer>();
    hashMap.put("key1",1);
    hashMap.put("key2",2);
    hashMap.put("key3",3);
    hashMap.put("key4",4);
    hashMap.put("key5",5);
    hashMap.put("key6",6);
    复制代码

    第1种方法 - for-each遍历HashMap.entrySet,使用HashMap.remove()删除(结果:抛出异常)

    for (Map.Entry<String,Integer> entry: hashMap.entrySet()) {
            String key = entry.getKey();
            if(key.contains("3")){
                hashMap.remove(entry.getKey());
            }
         System.out.println("当前HashMap是"+hashMap+" 当前entry是"+entry);
    
    }
    复制代码

    输出结果:

    当前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 当前entry是key1=1
    当前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 当前entry是key2=2
    当前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 当前entry是key5=5
    当前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 当前entry是key6=6
    当前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4} 当前entry是key3=3
    Exception in thread "main" java.util.ConcurrentModificationException
    	at java.util.HashMap$HashIterator.nextNode(HashMap.java:1429)
    	at java.util.HashMap$EntryIterator.next(HashMap.java:1463)
    	at java.util.HashMap$EntryIterator.next(HashMap.java:1461)
    	at com.test.HashMapTest.removeWayOne(HashMapTest.java:29)
    	at com.test.HashMapTest.main(HashMapTest.java:22)
    复制代码

    第2种方法-for-each遍历HashMap.keySet,使用HashMap.remove()删除(结果:抛出异常)

    Set<String> keySet = hashMap.keySet();
    for(String key : keySet){
        if(key.contains("3")){
            keySet.remove(key);
        }
        System.out.println("当前HashMap是"+hashMap+" 当前key是"+key);
    }
    复制代码

    输出结果如下:

    当前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 当前key是key1
    当前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 当前key是key2
    当前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 当前key是key5
    当前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 当前key是key6
    当前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4} 当前key是key3
    Exception in thread "main" java.util.ConcurrentModificationException
    	at java.util.HashMap$HashIterator.nextNode(HashMap.java:1429)
    	at java.util.HashMap$KeyIterator.next(HashMap.java:1453)
    	at com.test.HashMapTest.removeWayTwo(HashMapTest.java:40)
    	at com.test.HashMapTest.main(HashMapTest.java:23)
    复制代码

    第3种方法-使用HashMap.entrySet().iterator()遍历删除(结果:正确删除)

    Iterator<Map.Entry<String, Integer>> iterator  = hashMap.entrySet().iterator();
    while (iterator.hasNext()) {
        Map.Entry<String, Integer> entry = iterator.next();
        if(entry.getKey().contains("3")){
            iterator.remove();
        }
        System.out.println("当前HashMap是"+hashMap+" 当前entry是"+entry);
    }
    复制代码

    输出结果:

    当前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4, deletekey=3} 当前entry是key1=1
    当前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4, deletekey=3} 当前entry是key2=2
    当前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4, deletekey=3} 当前entry是key5=5
    当前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4, deletekey=3} 当前entry是key6=6
    当前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4, deletekey=3} 当前entry是key4=4
    当前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4} 当前entry是deletekey=3
    复制代码

    第1种方法和第2种方法抛出ConcurrentModificationException异常与上面ArrayList错误遍历-删除方法的原因一致,HashIterator也有一个expectedModCount,在遍历时获取下一个元素时,会调用next()方法,然后对 expectedModCount和modCount进行判断,不一致就抛出ConcurrentModificationException异常。

    abstract class HashIterator {
        Node<K,V> next;        // next entry to return
        Node<K,V> current;     // current entry
        int expectedModCount;  // for fast-fail
        int index;             // current slot
    
        HashIterator() {
            expectedModCount = modCount;
            Node<K,V>[] t = table;
            current = next = null;
            index = 0;
            if (t != null && size > 0) { // advance to first entry
                do {} while (index < t.length && (next = t[index++]) == null);
            }
        }
    
        public final boolean hasNext() {
            return next != null;
        }
    
        final Node<K,V> nextNode() {
            Node<K,V>[] t;
            Node<K,V> e = next;
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            if ((next = (current = e).next) == null && (t = table) != null) {
                do {} while (index < t.length && (next = t[index++]) == null);
            }
            return e;
        }
    
        public final void remove() {
            Node<K,V> p = current;
            if (p == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            current = null;
            K key = p.key;
            removeNode(hash(key), key, null, false, false);
            expectedModCount = modCount;
        }
    }
    
    复制代码

    PS:ConcurrentModificationException是什么?

    根据ConcurrentModificationException的文档介绍,一些对象不允许并发修改,当这些修改行为被检测到时,就会抛出这个异常。(例如一些集合不允许一个线程一边遍历时,另一个线程去修改这个集合)。

    一些集合(例如Collection, Vector, ArrayList,LinkedList, HashSet, Hashtable, TreeMap, AbstractList, Serialized Form)的Iterator实现中,如果提供这种并发修改异常检测,那么这些Iterator可以称为是"fail-fast Iterator",意思是快速失败迭代器,就是检测到并发修改时,直接抛出异常,而不是继续执行,等到获取到一些错误值时在抛出异常。

    异常检测主要是通过modCount和expectedModCount两个变量来实现的,

    • modCount 集合被修改的次数,一般是被集合(ArrayList之类的)持有,每次调用add(),remove()方法会导致modCount+1

    • expectedModCount 期待的modCount,一般是被Iterator(ArrayList.iterator()方法返回的iterator对象)持有,一般在Iterator初始化时会赋初始值,在调用Iterator的remove()方法时会对expectedModCount进行更新。(可以看看上面的ArrayList.Itr源码)

    然后在Iterator调用next()遍历元素时,会调用checkForComodification()方法比较modCount和expectedModCount,不一致就抛出ConcurrentModificationException。

    单线程操作Iterator不当时也会抛出ConcurrentModificationException异常。

    因为ArrayList和HashMap的Iterator都是上面所说的“fail-fast Iterator”,Iterator在获取下一个元素,删除元素时,都会比较expectedModCount和modCount,不一致就会抛出异常。

    所以当使用Iterator遍历元素(for-each遍历底层实现也是Iterator)时,需要删除元素,一定需要使用 Iterator的remove()方法 来删除,而不是直接调用ArrayList或HashMap自身的remove()方法,否则会导致Iterator中的expectedModCount没有及时更新,之后获取下一个元素或者删除元素时,expectedModCount和modCount不一致,然后抛出ConcurrentModificationException异常。

    正确方式

    1.删除ArrayList中的元素

    public static void removeItemFive(ArrayList<Integer> arrayList) {
            Iterator<Integer> iterator = arrayList.iterator();
            while (iterator.hasNext()) {
                Integer value = iterator.next();
                if (value.equals(3)) {//3是需要删除的元素
                    iterator.remove();
                }
                System.out.println("当前arrayList是" + arrayList.toString());
            }
    }

    2.删除HashMap中的元素

    public static void removeHashMapItem(HashMap<String, Integer> hashMap) {
            Iterator<Map.Entry<String, Integer>> iterator = hashMap.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<String, Integer> entry = iterator.next();
                if (entry.getKey().contains("3")) {
                    iterator.remove();
                }
    
            }
            System.out.println("当前HashMap是" + hashMap);
    }

     

    展开全文
  • Java HashMap类型 删除HashMap里面的值

    千次阅读 2019-02-28 23:11:38
    java HashMap&amp;amp;amp;lt;String,Object&amp;amp;amp;gt; map= new HashMap&amp;amp;amp;lt;String, Object&amp;amp;amp;gt;(); HashMap&amp;amp;amp;lt;Strin(key)g, Object(value)&...

    java HashMap<String,Object> map= new HashMap<String, Object>();

    HashMap<String(key), Object(value)> map = new HashMap<String,Object>();
    map.put(“user”, user1);//存值
    map.get(user)//取值
    1.是一种典型的map泛型实现方式 map们你可以将它们理解成有名字的object数组 利用泛型中的第一个也就是key值可以轻松找到相对应的value
    2. map就是一个容器,主要调用put()存、get()取 方法
    3. map.put(“user”, user1); user1是之前声明的一个类的对象,比如为user1,那么就是把user这个对象放到了map容器中了,其中user只是对象的代号,在调用的时候取出,代码为map.get(user); 参数是前面写的代号,得到的是user1这个对象。

    Java 删除HashMap里面的元素
    第一种 删除想要删除的元素
    HashMap<String, Object> paramMap = new HashMap<String, Object>();
    paramMap.remove(name); //直接删除你要删除的元素 name

    第二种 删除全部元素 与 删除想要删除的元素
    HashMap<String, Object> paramMap = new HashMap<String, Object>();
    for (Iterator<Map.Entry<String, Object>> it = paramMap.entrySet().iterator(); it.hasNext(); ) {//删除HashMAP元素status1
    Map.Entry<String, Object> item = it.next();//此句不能少,否则,删除当前节点,指针无法找到下一节点
    if(“status1”.equals(item.getKey())){//判断为true就删除status1 key键值
    it.remove();//没有判断条件删除全部元素 键值对
    }
    }
    2. getKey()方法获取HashMap<String(key值),Object(value值)> 里面的 key键值
    3.getValue()方法 获取HashMap<String(key值),Object(value值)> 里面value键值

    展开全文
  • HashMap

    2020-12-03 00:10:08
    HashMap 概述 HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。 HashMap 继承于AbstractMap,实现了Map、Cloneable、java.io.Serializable接口。 HashMap 的实现不是同步的,这意味着它不是线程安全的...

    HashMap

    概述

    HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
    HashMap 继承于AbstractMap,实现了Map、Cloneable、java.io.Serializable接口。
    HashMap 的实现不是同步的,这意味着它不是线程安全的。它的key、value都可以为null。此外,HashMap中的映射不是有序的。

    HashMap 的实例有两个参数影响其性能:“初始容量” 和 “加载因子”。容量 是哈希表中桶的数量,初始容量 只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。
    通常,默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 get 和 put 操作,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。

    “拉链法”解决哈希冲突

    参考博客:https://blog.csdn.net/qq_32595453/article/details/80660676

    hash : 翻译为“散列”,就是把任意长度的输入,通过散列算法,变成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来唯一的确定输入值,由此引出hash冲突。

    hash冲突就是键(key)经过hash函数得到的结果作为地址去存放当前的键值对(key-value)(这个是hashmap的存值方式),但是却发现该地址已经有人先来了,一山不容二虎,就会产生冲突。这个冲突就是hash冲突了。

    一句话说就是:如果两个不同对象的hashCode相同,这种现象称为hash冲突。

    HashMap,HashSet其实都是采用的拉链法来解决哈希冲突的,就是在每个位桶实现的时候,我们采用链表(jdk1.8之后采用链表+红黑树)的数据结构来去存取发生哈希冲突的输入域的关键字(也就是被哈希函数映射到同一个位桶上的关键字)。首先来看使用拉链法解决哈希冲突的几个操作:

    ①插入操作:在发生哈希冲突的时候,我们输入域的关键字去映射到位桶(实际上是实现位桶的这个数据结构,链表或者红黑树)中去的时候,我们先检查带插入元素x是否出现在表中,很明显,这个查找所用的次数不会超过装载因子(n/m:n为输入域的关键字个数,m为位桶的数目),它是个常数,所以插入操作的最坏时间复杂度为O(1)的。

    ②查询操作:和①一样,在发生哈希冲突的时候,我们去检索的时间复杂度不会超过装载因子,也就是检索数据的时间复杂度也是O(1)的

    ③删除操作:如果在拉链法中我们想要使用链表这种数据结构来实现位桶,那么这个链表一定是双向链表,因为在删除一个元素x的时候,需要更改x的前驱元素的next指针的属性,把x从链表中删除。这个操作的时间复杂度也是O(1)的。

    拉链法的优点

    与开放定址法相比,拉链法有如下几个优点:

    ①拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;

    ②由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;

    ③开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而拉链法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间;

    ④在用拉链法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。

    拉链法的缺点

    指针需要额外的空间,故当结点规模较小时,开放定址法较为节省空间,而若将节省的指针空间用来扩大散列表的规模,可使装填因子变小,这又减少了开放定址法中的冲突,从而提高平均查找速度。

    put函数

    put函数大致的思路为:

    1. 对key的hashCode()做hash,然后再计算index;

    2. 如果没碰撞直接放到bucket里;

    3. 如果碰撞了,以链表的形式存在buckets后;

    4. 如果碰撞导致链表过长(大于等于 TREEIFY_THRESHOLD ),就把链表转换成红黑树;

    (红黑树:http://www.cnblogs.com/skywang12345/p/3245399.html)

    1. 如果节点已经存在就替换old value(保证key的唯一性)

    2. 如果bucket满了(超过 load factor*current capacity ),就要resize。

    具体代码的实现如下:

    public V put(K key, V value) { 
    
    // 对key的hashCode()做hash 
    
    return putVal(hash(key), key, value, false, true); 
    
    }
    
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent, 
    
    boolean evict) { 
    
    Node<K,V>[] tab; Node<K,V> p; int n, i; 
    
    // tab为空则创建 
    
    if ((tab = table) == null || (n = tab.length) == 0) 
    
    n = (tab = resize()).length; 
    
    // 计算index,并对null做处理 
    
    if ((p = tab[i = (n - 1) & hash]) == null) 
    
    tab[i] = newNode(hash, key, value, null); 
    
    else {
    
    Node<K,V> e; K k; 
    
    // 节点存在 
    
    if (p.hash == hash && 
    
    ((k = p.key) == key || (key != null && key.equals(k) 
    
    ))) 
    
    e = p; 
    
    // 该链为树 
    
    else if (p instanceof TreeNode) 
    
    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, k 
    
    ey, value); 
    
    // 该链为链表 
    
    else {
    
    for (int binCount = 0; ; ++binCount) { 
    
    if ((e = p.next) == null) { 
    
    p.next = newNode(hash, key, value, null); 
    
    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 
    
    for 1st 
    
    treeifyBin(tab, hash); 
    
    break; 
    
    }
    
    if (e.hash == hash && 
    
    ((k = e.key) == key || (key != null && key.e 
    
    quals(k)))) 
    
    break; 
    
    p = e; 
    
    } 
    
    }
    
    // 写入 
    
    if (e != null) { // existing mapping for key 
    
    V oldValue = e.value; 
    
    if (!onlyIfAbsent || oldValue == null) 
    
    e.value = value; 
    
    afterNodeAccess(e); 
    
    return oldValue; 
    
    } 
    
    }
    
    ++modCount; 
    
    // 超过load factor*current capacity,resize 
    
    if (++size > threshold) 
    
    resize(); 
    
    afterNodeInsertion(evict); 
    
    return null; 
    
    }
    

    get函数

    大致思路如下:

    1. bucket里的第一个节点,直接命中;

    2. 如果有冲突,则通过key.equals(k)去查找对应的entry

      若为树,则在树中通过key.equals(k)查找,O(logn);

      若为链表,则在链表中通过key.equals(k)查找,O(n)。

    具体代码的实现如下:

    public V get(Object key) { 
    
    Node<K,V> e; 
    
    return (e = getNode(hash(key), key)) == null ? null : e.valu 
    
    e;
    
    }
    
    final Node<K,V> getNode(int hash, Object key) { 
    
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k; 
    
    if ((tab = table) != null && (n = tab.length) > 0 && 
    
    (first = tab[(n - 1) & hash]) != null) { 
    
    // 直接命中 
    
    if (first.hash == hash && // always check first node 
    
    ((k = first.key) == key || (key != null && key.equal 
    
    s(k)))) 
    
    return first; 
    
    // 未命中 
    
    if ((e = first.next) != null) { 
    
    // 在树中get 
    
    if (first instanceof TreeNode) 
    
    return ((TreeNode<K,V>)first).getTreeNode(hash, 
    
    key); 
    
    // 在链表中get 
    
    do {
    
    if (e.hash == hash && 
    
    ((k = e.key) == key || (key != null && key.e 
    
    quals(k)))) 
    
    return e; 
    
    } while ((e = e.next) != null); 
    
    } 
    
    }
    
    return null; 
    
    } 
    

    总结

    1. 什么时候会使用HashMap?他有什么特点?

    是基于Map接口的实现,存储键值对时,它可以接收null的键值,是非同步的,HashMap存储着Entry(hash, key, value, next)对象。

    2. 你知道HashMap的工作原理吗?

    通过hash的方法,通过put和get存储和获取对象。存储对象时,我们将K/V传给put方法时,它调用hashCode计算hash从而得到bucket位置,进一步存储,HashMap会根据当前bucket的占用情况自动调整容量(超过 Load Facotr 则resize为原来的2倍)。获取对象时,我们将K传给get,它调用hashCode计算hash从而得到bucket

    位置,并进一步调用equals()方法确定键值对。如果发生碰撞的时候,Hashmap通过链表将产生碰撞冲突的元素组织起来,在Java 8中,如果一个bucket中碰撞冲突的元素超过某个限制(默认是8),则使用红黑树来替换链表,从而提高速度。

    3. 你知道getput的原理吗?**equals()hashCode()**的都有什么作用?

    通过对key的hashCode()进行hashing,并计算下标( (n-1) & hash ),从而获得buckets的位置。如果产生碰撞,则利用key.equals()方法去链表或树中去查找对应的节点

    4. 你知道hash的实现吗?为什么要这样实现?

    在Java 1.8的实现中,是通过hashCode()的高16位异或低16位实现的: (h =k.hashCode()) ^ (h >>> 16) ,主要是从速度、功效、质量来考虑的,这么做可以在bucket的n比较小的时候,也能保证考虑到高低bit都参与到hash的计算中,同时不会有太大的开销。

    5. 如果HashMap的大小超过了负载因子**(** load factor **)**定义的容量,怎么办?

    如果超过了负载因子(默认0.75),则会重新resize一个原来长度两倍的HashMap,并且重新调用hash方法。

    展开全文
  • Hashmap

    2021-03-14 00:40:22
    布隆过滤器、bitmap、HashSet、HashMap、ConcurrentHashMap JDK的HashMap HashMap不能由get是否为空来判断是否存在某个键 如果key存在,那么覆盖,不存在则存入整个键值对。 HashMap的一些参数 默认初始化长度为16...
  • HashMap修改属性后访问不到或删除失败 以下是重写方法并测试的例子 package collectionsFramework.map; import java.util.Objects; public class Student { private String name; private int stuNo; public ...
  • hashMap

    2019-05-28 17:34:47
    原文:Java 8系列之重新认识HashMap,有删改。 JDK1.8对HashMap底层的实现进行了优化,例如引入红黑树的数据结构和扩容的优化等。本文结合JDK1.7和JDK1.8的区别,深入探讨HashMap的结构实现和功能原理,文章末尾...
  • HashMap之快速失败

    千次阅读 2017-10-23 00:04:23
    HashMap之快速失败为什么HashMap通过迭代器自身的remove或add方法就不会出现迭代器失败HashMap所有集合类视图所返回迭代器都是快速失败(fast-fail)的。 在HashMap中,有一个变量modCount来指示集合被修改的次数...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 94,200
精华内容 37,680
关键字:

hashmap删除失败