HashMap的遍历
2016-03-20 01:55
309 查看
查看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 操作。
但是看到这里,我们就会去找HashMap中的public iterator() 方法啊,只有实现了这个方法才能使用HashMap的迭代器啊,可是,我们没有找到!HashMap没有迭代器!?当然不是。准确来说,HashMap有多个迭代器!
(1)ValueIterator
(2)KeyIterator
(3)EntryIterator
这三个迭代器正好对应了前面提到的HashMap的三个视图。不难发现,它们三个迭代器其实都是在调用HashIterator的代码得到一个entry,知不是三个迭代器分别返回K,V,K-V对而已。【所以,这三个迭代器的性能是一样的。】
从上面的代码不难看出,EntrySet几乎只是一个Set的最小实现。Set类不像Map类有get方法,Set中保存的数据的读取主要考迭代器。所以,这里的Set视图并不是在内存中开辟出新的位置,再把数据复制过来,而是返回一个精心设计的迭代器而已。所以,又不得不佩服编写Java文档的人的高智商。这里的【视图】一词用的是极好的,学过数据库基础的人应该都了解这个概念。
顺便提一下的是,这个Set视图中,数据的唯一性并不是它本身在维护,而是HashMap在构建的时候,要求key唯一维护起来的。
其实,分析过Set接口和它的几个实现类(比如HashSet)之后,不能发现,Set接口本来就不具备保证唯一性的能力,它更像是Java的一种规定。
然后ketSet() 和entrySet() 类似,都是返回一个set视图,所以我在这里就不赘述了。
(2)显示调用map.entrySet()的集合迭代器
上面这两种遍历方法是推荐的。至于利用foreach或者显示调用迭代器获取键集或值集我就不啰嗦了,一样的,性能也一样。
(3)用临时变量保存map.entrySet()
跟(1)几乎一模一样,没什么问题,就是有点low。因为对于Set<>的变量,你实际上什么操作也不能做,何必多打字呢。
(4)for each map.keySet(),再调用get获取
这属于花样作死。用key去get value,每get一次就是遍历哈希表一次,效率太低了。还是那句话,如果可以用迭代器,绝对用迭代器效率最高。
键集 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一次就是遍历哈希表一次,效率太低了。还是那句话,如果可以用迭代器,绝对用迭代器效率最高。
相关文章推荐
- c语言实现hashmap(转载)
- 设计模式之行为型模式 - 调用行为的传递问题
- 文件遍历排序函数
- Ruby中的迭代器详解
- Ruby中Block和迭代器的使用讲解
- Lua 学习笔记之C API 遍历 Table实现代码
- Lua中的迭代器浅析
- Lua中的迭代器和泛型for介绍
- C#特性-迭代器(上)及一些研究过程中的副产品
- C#迭代器模式(Iterator Pattern)实例教程
- C#遍历文件夹后上传文件夹中所有文件错误案例分析
- C#中遍历Hashtable的4种方法
- Erlang中遍历取出某个位置的最大值代码
- C++实现图的邻接矩阵存储和广度、深度优先遍历实例分析
- C++实现图的邻接表存储和广度优先遍历实例分析
- C++非递归队列实现二叉树的广度优先遍历
- php遍历目录方法小结
- 一个目录遍历函数
- php遍历删除整个目录及文件的方法
- PHP遍历文件夹与文件类及处理类用法实例