Java集合基础之浅谈HashMap

本文深入解析HashMap的数据存储机制,包括其内部的Node节点类和哈希表实现,探讨HashMap如何确保Key的唯一性,以及其迭代器的工作原理。同时,文章还讨论了HashMap的扩容机制和线程安全性问题。

补发:2019-7-20 学习日记之HashMap

1、HashMap的数据存储
HashMap类内部有一个Node节点类,类比C++中链表的实现原理,可以猜测到它的作用也是如此。
HashMap部分源码

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;

    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }

另外,HashMap类中还有这样一个成员table表:

transient Node<K,V>[] table;

显然,我们能感觉到HashMap使用了数组+链表的方式实现了存储数据,而数据(对象)存放的位置由它们的hashCode决定,也就是传说中的哈希表;

然后我大致浏览了一下HashMap和Map接口的源码,发现原来在Map接口中有这么一个接口

interface Entry<K,V> {
  
    K getKey();

    V getValue();

    V setValue(V value);
    }

这个接口的实现类的任务便是保存一个键值对,上面的Node类也实现了这个接口;那么HashMap如何保证Key唯一,才不存放重复的Entry<K,V>呢?
答:HashMap存储对象的过程可以大致这样理解:
1、判断Key是否已经存在,判断过程如下:
a)根据Key获取hashCode值,判断是否相同
b)若不同,则不为同一对象
若相同,则调用Key的equals方法进一步判断,若结果为true则为同一对象,反之亦然。
2、若Key未存在,则根据hashCode放到 table 数组中的某个位置;
然后,若table中已经存在的Key0的hashCode和新Key的值一样,即a情况出现,但b步骤
调用equals方法返回false时,Node的链表功能就有用了,新的 K,V对Key将放在Key0之后。
显然,我们应该尽量避免a情况(hashCode冲突)出现,因为链表的查找是很慢的,而数组查找很快。

2、关于HashMap迭代器的问题
首先,HashMap中没有迭代器,它继承于Map接口而不是Collection。
其次,先引出一个问题,既然Map存放的是K,V对,给我们一个Map,当我们想要知道它里面的Value值时,我们只要通过Key去get就可以了。那么问题来了,我们如何知道Map中有哪些Key呢?
So框架设计者在HashMap中设置了一个集合set

transient Set<Map.Entry<K,V>> entrySet;

我们可以通过:
1、获取 entrySet
2、获取 entrySet的迭代器
来得到 Entry<K,V>对象,从而调用getKey(),getValue(),来完成HashMap的遍历;
java1.8 api
那么还有一个小问题,既然迭代器只能迭代一次,那么通过HashMap中的 entrySet()方法得到的
Set被迭代了之后,再次获取,是不是不能迭代了?

HashMap<String,Integer> map
        = new HashMap<String,Integer>();
map.put("a",1);
map.put("b",2);
map.put("c",3);

Set set = map.entrySet();
Iterator it = set.iterator();
while(it.hasNext()){//开始迭代

    Entry entry = (Entry) it.next();
}
//第二次获取 Set集合 和 Iterator迭代器
Set set2 = map.entrySet();
Iterator it2 = set.iterator();// 还能迭代出数据吗?

答案肯定是能二次获取并迭代;
看源码:
获取Set

public Set<Map.Entry<K,V>> entrySet() {
    Set<Map.Entry<K,V>> es;
    return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;// 注意这里!new 出新的 EntrySet
}

final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
    public final int size()                 { return size; }
    public final void clear()               { HashMap.this.clear(); }
    public final Iterator<Map.Entry<K,V>> iterator() {
        return new EntryIterator();  // 注意这里!new 出新的 EntryIterator
    }

可以看到调用 HashMap的entrySet()方法时,每次反回的都是new出来新的 EntrySet,并且 EntrySet的 iterator() 方法返回的也都是新的迭代器,所以……懂了

最后,附上HashMap其它相关的知识:

总结:
1、HashMap数据结构用的是数组+链表(哈希表),存储的单元是Node<K,V>结点
2、遍历HashMap可以通过 entrySet()方法先获取EntrySet集合,再获取EntrySet的迭代器,再迭代即可实现遍历
3、今后在重写一个类的equals()方法时,应也重写其hashCode()方法和toString()方法,三点一线

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值