您的位置:首页 > 其它

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 操作。

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一次就是遍历哈希表一次,效率太低了。还是那句话,如果可以用迭代器,绝对用迭代器效率最高。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息