HashMap的遍历

查看Map接口,我们发现Map提供三种collection视图:

  • 键集 Set<> keySet():返回此映射中包含的键的 Set 视图。该 set 受映射支持,所以对映射的更改可在此 set 中反映出来,反之亦然。如果对该 set 进行迭代的同时修改了映射(通过迭代器自己的 remove 操作除外),则迭代结果是不确定的。set 支持元素移除,通过 Iterator.remove、 Set.remove、 removeAll、 retainAll 和 clear 操作可从映射中移除相应的映射关系。它不支持 add 或 addAll 操作。

  • 值集Collection<> values():返回此映射中包含的值的 Collection 视图。该 collection 受映射支持,所以对映射的更改可在此 collection 中反映出来,反之亦然。如果对该 collection 进行迭代的同时修改了映射(通过迭代器自己的 remove 操作除外),则迭代结果是不确定的。collection 支持元素移除,通过 Iterator.remove、 Collection.remove、 removeAll、 retainAll 和 clear 操作可从映射中移除相应的映射关系。它不支持 add 或 addAll 操作。

  • 键-值映射关系集Set<> keySet():返回此映射中包含的键的 Set 视图。该 set 受映射支持,所以对映射的更改可在此 set 中反映出来,反之亦然。如果对该 set 进行迭代的同时修改了映射(通过迭代器自己的 remove 操作除外),则迭代结果是不确定的。set 支持元素移除,通过 Iterator.remove、 Set.remove、 removeAll、 retainAll 和 clear 操作可从映射中移除相应的映射关系。它不支持 add 或 addAll 操作。


1 HashMap的迭代器

在java中一提到 util 中的遍历,首先应该想到的就是它的迭代器,其他方法的迭代往往要么性能比迭代器低,要么本质上还是调用的迭代器。所以,我们先来分析一下HashMap的迭代器。

    private abstract class HashIterator<E> implements Iterator<E> {
        Entry<K,V> next;        // next entry to return
        int expectedModCount;   // For fast-fail
        int index;              // current slot
        Entry<K,V> current;     // current entry

        //新建一个迭代器对象
        HashIterator() {
            expectedModCount = modCount;
            if (size > 0) { // advance to first entry
                //table就是一个哈希表中的【桶】,可以简单理解为一个数组
                Entry[] t = table;
                //从第一个【桶】开始查找,找到第一个不为空的桶,使index等于这个桶的下标,next指向这个桶作为初始值
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
        }

        public final boolean hasNext() {
            return next != null;
        }

        final Entry<K,V> nextEntry() {
            //fast-fail,非同步的机制,如果不明白暂时忽略
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Entry<K,V> e = next;
            if (e == null)
                throw new NoSuchElementException();

            //这个地方很重要。首先,next = e.next 访问的是Entry链,如果next不为空,则返回next了;如果next为空,则像初始化的时候那样,利用index找到下一个不为空的桶。
            if ((next = e.next) == null) {
                Entry[] t = table;
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }

            //这里的current到没有什么特殊的,iterator中的基本机制,维护一个current entry,用于迭代器的remove
            current = e;
            return e;
        }

        public void remove() {
            if (current == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Object k = current.key;
            current = null;
            HashMap.this.removeEntryForKey(k);
            expectedModCount = modCount;
        }
    }

但是看到这里,我们就会去找HashMap中的public iterator() 方法啊,只有实现了这个方法才能使用HashMap的迭代器啊,可是,我们没有找到!HashMap没有迭代器!?当然不是。准确来说,HashMap有多个迭代器!

(1)ValueIterator

    private final class ValueIterator extends HashIterator<V> {
        public V next() {
            return nextEntry().value;
        }
    }

(2)KeyIterator

    private final class KeyIterator extends HashIterator<K> {
        public K next() {
            return nextEntry().getKey();
        }
    }

(3)EntryIterator

    private final class EntryIterator extends HashIterator<Map.Entry<K,V>> {
        public Map.Entry<K,V> next() {
            return nextEntry();
        }
    }

这三个迭代器正好对应了前面提到的HashMap的三个视图。不难发现,它们三个迭代器其实都是在调用HashIterator的代码得到一个entry,知不是三个迭代器分别返回K,V,K-V对而已。【所以,这三个迭代器的性能是一样的。】



2 视图方法

2.1 entrySet()

    public Set<Map.Entry<K,V>> entrySet() {
        return entrySet0();
    }

    private Set<Map.Entry<K,V>> entrySet0() {
        Set<Map.Entry<K,V>> es = entrySet;
        return es != null ? es : (entrySet = new EntrySet());
    }

    private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
        public Iterator<Map.Entry<K,V>> iterator() {
            return newEntryIterator();
        }
        public boolean contains(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<K,V> e = (Map.Entry<K,V>) o;
            Entry<K,V> candidate = getEntry(e.getKey());
            return candidate != null && candidate.equals(e);
        }
        public boolean remove(Object o) {
            return removeMapping(o) != null;
        }
        public int size() {
            return size;
        }
        public void clear() {
            HashMap.this.clear();
        }
    }

从上面的代码不难看出,EntrySet几乎只是一个Set的最小实现。Set类不像Map类有get方法,Set中保存的数据的读取主要考迭代器。所以,这里的Set视图并不是在内存中开辟出新的位置,再把数据复制过来,而是返回一个精心设计的迭代器而已。所以,又不得不佩服编写Java文档的人的高智商。这里的【视图】一词用的是极好的,学过数据库基础的人应该都了解这个概念。

顺便提一下的是,这个Set视图中,数据的唯一性并不是它本身在维护,而是HashMap在构建的时候,要求key唯一维护起来的。

其实,分析过Set接口和它的几个实现类(比如HashSet)之后,不能发现,Set接口本来就不具备保证唯一性的能力,它更像是Java的一种规定。

然后ketSet() 和entrySet() 类似,都是返回一个set视图,所以我在这里就不赘述了。


2.2 values()

values() 跟上面提到的两个不一样的地方是,HashMap中,值是可能相等的,所以,它返回的不是Set,而是Collection。当然,其实也没有太大差别,依然只用用迭代器访问。就像前面分析的一样,这里的Set和Collection的展示意义大于实际意义,为的是规定上的一致性。



3 运用实例

(1)for each map.entrySet()

Map<String, String> map = new HashMap<String, String>();
for (Entry<String, String> entry : map.entrySet()) {
    entry.getKey();
    entry.getValue();
}

(2)显示调用map.entrySet()的集合迭代器

Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
    Map.Entry<String, String> entry = iterator.next();
    entry.getKey();
    entry.getValue();
}

上面这两种遍历方法是推荐的。至于利用foreach或者显示调用迭代器获取键集或值集我就不啰嗦了,一样的,性能也一样。

(3)用临时变量保存map.entrySet()

Set<Entry<String, String>> entrySet = map.entrySet();
for (Entry<String, String> entry : entrySet) {
    entry.getKey();
    entry.getValue();
}

跟(1)几乎一模一样,没什么问题,就是有点low。因为对于Set<>的变量,你实际上什么操作也不能做,何必多打字呢。

(4)for each map.keySet(),再调用get获取

Map<String, String> map = new HashMap<String, String>();
for (String key : map.keySet()) {
    map.get(key);
}

这属于花样作死。用key去get value,每get一次就是遍历哈希表一次,效率太低了。还是那句话,如果可以用迭代器,绝对用迭代器效率最高。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于HashMap遍历,有几种常用的方法可以实现: 1. 使用EntrySet遍历:通过调用HashMap的entrySet()方法获取到HashMap中所有的键值对组成的Set集合,然后通过遍历Set集合中的每个元素,来获取键和值。 ```java HashMap<String, Integer> map = new HashMap<>(); // 添加键值对 map.put("A", 1); map.put("B", 2); map.put("C", 3); // 使用entrySet遍历 for (Map.Entry<String, Integer> entry : map.entrySet()) { String key = entry.getKey(); Integer value = entry.getValue(); System.out.println("Key: " + key + ", Value: " + value); } ``` 2. 使用KeySet遍历:通过调用HashMap的keySet()方法获取到HashMap中所有的键组成的Set集合,然后通过遍历Set集合中的每个键,来获取对应的值。 ```java HashMap<String, Integer> map = new HashMap<>(); // 添加键值对 map.put("A", 1); map.put("B", 2); map.put("C", 3); // 使用keySet遍历 for (String key : map.keySet()) { Integer value = map.get(key); System.out.println("Key: " + key + ", Value: " + value); } ``` 3. 使用Java 8的forEach方法遍历:利用HashMap的forEach方法,可以使用Lambda表达式对每个键值对进行操作。 ```java HashMap<String, Integer> map = new HashMap<>(); // 添加键值对 map.put("A", 1); map.put("B", 2); map.put("C", 3); // 使用forEach遍历 map.forEach((key, value) -> System.out.println("Key: " + key + ", Value: " + value)); ``` 以上是几种常用的HashMap遍历方法,根据具体的需求选择适合的方式。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值