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一次就是遍历哈希表一次,效率太低了。还是那句话,如果可以用迭代器,绝对用迭代器效率最高。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值