HashMap的遍历

来源:互联网 发布:dede模板下载站源码 编辑:程序博客网 时间:2024/06/11 17:46

查看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
原创粉丝点击