java数据结构之HashMap

1 篇文章 0 订阅

官方文档说明

基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null值和 null键。除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
此实现假定哈希函数将元素适当地分布在各桶之间,可为基本操作(get 和 put)提供稳定的性能。迭代 collection 视图所需的时间与 HashMap 实例的“容量”(桶的数量)及其大小(键-值映射关系数)成比例。所以,如果迭代性能很重要,则不要将初始容量设置得太高(或将加载因子设置得太低)。
HashMap 的实例有两个参数影响其性能:初始容量 和加载因子。容量 是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。
通常,默认加载因子 (.75) 在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 get 和 put 操作,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。如果很多映射关系要存储在 HashMap 实例中,则相对于按需执行自动的 rehash 操作以增大表的容量来说,使用足够大的初始容量创建它将使得映射关系能更有效地存储。

数据结构图

如下图所示:
这里写图片描述

从上图可以看到,每个桶中的节点是按照链表的方式排列的。

模拟数据

假设映射结构为《Integer,String》,并且n=4,也就是hash桶的数量是4.我们模拟一下put方法的过程。

  • 1:我们放入第一组映射<0,a>。对应桶的算法是(n-1)&hash(key),也就是3&hash(0) = 3&0 = 0.也就是说此映射会被创建成一个节点(Node)对象,然后放到图中0的位置。
  • 2:我们放入第二组映射<1,b>。对应桶的算法是(n-1)&hash(key),也就是3&hash(1) = 3&1 = 1.也就是说此映射会被创建成一个节点(Node)对象,然后放到图中1的位置。
  • 3:我们放入第三组映射<2,b>。对应桶的算法是(n-1)&hash(key),也就是3&hash(2) = 3&2 = 2.也就是说此映射会被创建成一个节点(Node)对象,然后放到图中2的位置。
  • 4:我们放入第四组映射<3,b>。对应桶的算法是(n-1)&hash(key),也就是3&hash(3) = 3&3 = 3.也就是说此映射会被创建成一个节点(Node)对象,然后放到图中3的位置。
  • 5:我们放入第五组映射<4,b>。对应桶的算法是(n-1)&hash(key),也就是3&hash(4) = 3&4 = 0.也就是说此映射会被创建成一个节点(Node)对象,然后放到图中0的位置,又因为0的位置不为null,然后调用equals方法判断两个key是否相同,如果相同则替换旧的节点,如果不相同则放到01的位置。那么很明显4和0不相同,会放到01的位置。
  • 6:一次类推。

get和remove方法,同样是采用hash和equals方法来定位映射位置,然后采取相应的操作。

数据结构变化

上文说HashMap不保证该顺序恒久不变,但是我们看数据结构图,如果遍历此HashMap,就是从上到下,从左到右依次遍历,顺序更改是固定的啊!原因如下:
如果hash算法不能很好的把数据分散到各个桶中,那么其中某一个桶中的链表就可能会很大。这个时候,put,get,remove操作都要遍历整个链表,性能就会所以HashMap的实现对此进行了优化。如果桶的数量大于64,且某个桶的链表的长度大于8,那么就把这个桶中的数据结构就会重构为一颗红黑二叉树。数据结构就会变为如下所示:

这里写图片描述

所以如果桶2的数据太多了话,就会重构成一颗树,那么数据的顺序就变化了,所以说说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;
     }
 }
树节点
 static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
     TreeNode<K,V> parent;  // red-black tree links
     TreeNode<K,V> left;
     TreeNode<K,V> right;
     TreeNode<K,V> prev;    // needed to unlink next upon deletion
     boolean red;
     TreeNode(int hash, K key, V val, Node<K,V> next) {
         super(hash, key, val, next);
     }
 }

成员变量

transient Node<K,V>[] table;//hash结构
int threshold;//重构临界值,此值是根据容量和加载因子计算得到
final float loadFactor;//加载因子

构造函数

public HashMap(int initialCapacity, float loadFactor) {
    //此处省略无关代码段
    ......

    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
}

rehash

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    //省略无关代码
    ......

    if (++size > threshold)
        resize();//rehash
    afterNodeInsertion(evict);
    return null;
}

rehash操作会使table的容量几乎翻倍。

结构变化

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
       //无关代码
       ......
       for (int binCount = 0; ; ++binCount) {
            if ((e = p.next) == null) {
                p.next = newNode(hash, key, value, null);
                if (binCount >= TREEIFY_THRESHOLD - 1)
                    treeifyBin(tab, hash);//指定的hash桶,变为树结构
                break;
            }

        }

        //无关代码
        ......
        return null;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值