Java底层-Hash、HashMap的put方法详解

以前一直没有做技术沉淀,最近闲下来了面试了挺多公司,作为一名有着快6年开发经验的java程序员来说,我最近被面试官说得最多的一句话就是很多东西两年左右开发的都懂。
所以我就打算开始编写底层业务的说明,说做就做。
面试常问的问题1、HashMap、HashTable、TreeMap的区别以及实现原理。
首先我们先说Hash(哈希、散列)

Hash

Hash就是将任意长度的对象,通过散列算法变换成固定长度的输出,该输出的值称之为散列值(Hash),这种转换方式是一种压缩映射,也就是说hash值的空间通常远远小于输入的空间,不同的输入对象可能会散列成相同的hash值。所以不能从散列值来唯一的确定输入的值,如果出现不同输入对象生成相同的hash值时,称之为hash碰撞
下面简单讲解一下两个不同对象的hash算法:
Integer-int类型获取的hash值是其自身的value
String类型获取Hash值的方案是:

// 底层HashCode实现原理
// 首先设定一个默认的hash值 默认为0
int hash = 0;
// value 为char[] char数组(在Java中String的解决方案是将字符用字符数组形式进行设定
// 也就是说其实我们在设定字符串的时候是生成了一个字符数组)
// 如果当前的hash值是0 且值的长度大于0
if (hash == 0 && value.length > 0) {
  // 将当前的字符数组进行复制,用于有效且方便的重新计算当前的hash值
  char val[] = value;
  // 循环遍历当前的字符数组 并进行加法计算之前的值
  for (int i = 0; i < value.length; i++) {
    // 便于读者阅读,所以将当前算法进行详解
    // 在字符串中的算法设定中,当前的hash值是当前的字符,也就是说其实是将整个
    // char[] 数组进行设定为每一个对应的hash值
    int thisHash = val[i];
    // 历史hash 用于进行字符添加方案,需要进行历史hash值重新设定,* 31 也就是进行数据位移
    int oldHash = 31 * hash;
    // 最后将当前的hash 也就是 val[i] 与重新设定的历史hash 31 * hash 进行相加 生成新的hash值
    hash = thisHash + oldHash;
  }
}
return hash;

相对应的获取方案就不一一编写了,读者可以自行查询源码。
常见的hash函数设定方案有一下几种:
1、直接定址法: 直接以关键字k或者k加上某一个常数(k + c) 作为hash值。
2、数字分析法: 提取关键字中取值相对均匀的数字作为hash值。
3、 除留余数法: 用关键字k除以某个不大于hash表长度m的数p,将所得的余数作为hash值。
4、分段叠加法: 按照哈希地址位数将关键字分成位数相等的几部分,其中最后一部分可以比较短。然后将这几部分相加,舍弃最高进位后的结果就是该关键字的hash值。
5、平方取中法: 如果关键字各个部分分布都不均匀,可以先求出其平方值,然后按照需求取中间几位作为hash值。
6、伪随机数法: 采用一个伪随机数作为hash值。

HashMap

HashMap继承自AbstractMap,抽象Map,其是所有map的抽象父类。AbstractMap设定了Map的方法以及其获取值以及键的方案。
HashMap中设定了一个内部类为Node(节点),其引用了Map.Entry接口
设定的属性为hash、key、value、next
也就是在这里HashMap就将其存储方案进行了设定,其是将所有的键值都存放在Node之中,且为设定存储方案存入下一个值的next同样为当前的Node对象
也就是说HashMap的存储方案是以一个链表结构进行的数据存储
接下来HashMap为了方便其获取hash值,编写了一个静态方法hash(Object key)

static final int hash(Object key) {
  int h;
  return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

在这里我们能看出其在设定的时候key的设定也就是可以为null值,也就是说如果key为null是,将设定当前的hash为0 否则获取其本身的hash并进行向右偏移16位与当前的hash值进行位运算
从主观而言,我们了解了以上两点之后需要了解到的解释hashmap的put 与get方法了
先说说put方法,put方法只是一个对外我们常用的方法,而内置的关联方法为putVal方法

    /**
     * Implements Map.put and related methods
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict);

在我们使用put方法时实际内置调用的是putVal方法。我们可以过滤掉后面两个boolean类型参数,在put方法调用putVal方法时,onlyIfAbsent = false,evict = true。
在putVal方法调用时其声明了一个Node<K, V>[] tab;其是整个HashMap的核心数据链表对象
声明了一个Node<K, V> p; 其是用于作为当前键值对的数据存储对象。
声明了两个int整数 n、i
n用于设定整个链表的长度 而i用于设定其索引位置即index
其首先判断了tab是否为空 或 tab的长度等于0的情况 并对tab与链表长度参数n进行赋值
如果当前链表是空的或长度为0时则初始化tab并将其长度重新赋值

Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
  n = (tab = resize()).length;
// 然后判断当前链表的上一级hash是否存在,并获取上一级的Node节点赋值给p
if ((p = tab[i = (n - 1) & hash]) == null) {
  // 如果上一节点为空 则创建新的一个简单,并将当前的键值对设定给新的Node 即结束当前put方案
  tab[i] = newNode(hash, key, value, null);
} else {
  // 否则重新声明一个Node 以及key
  Node<K, V> e; K k;
  // 如果上一节点的hash等于当前存在的hash 且整个key与上一节点相同时 将上一节点p赋值给当前节点e
  if (p.hash == hash && ((k = p.key) == key || key != null && key.equals(k))){
    e = p;
  } else if (p instanceof TreeNode ){
      // 如果上一节点p是树状节点 TreeNode的实例时
      //  则将当前实例添加到树状节点中 (HashMap另一套存储方案 红黑树存储实现方案)
      e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
  } else {
    // 循环Node 进行数据遍历
    for (int binCount = 0; ; ++binCount) {
      // 直到当前节点为空时将当前节点赋值
      if ((e = p.next) == null) {
        p.next = newNode(hash, key, value, null);
        // 如果当前节点长度为8时重新创建一个Node树
        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
          treeifyBin(tab, hash);
          break;
       }
      // 如果当前的hash与上级hash相同时直接跳过 进行下一次循环获取树节点信息
      if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) {
        break;
      }
      p = e;
    }
  }
  // 如果当前节点不等于空则 且key存在的情况时重新value赋值
  if (e != null) { // existing mapping for key
    V oldValue = e.value;
    if (!onlyIfAbsent || oldValue == null)
      e.value = value;
    afterNodeAccess(e);
    return oldValue;
   }
}
++modCount;
// 如果当前的size过大时进行扩容
if (++size > threshold)
  resize();
afterNodeInsertion(evict);
return null;
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容

  • Java源码学习--HashMap 由于HashSet的实现原理是HashMap,所以我们先从HashMap开始学...
    慕北人阅读 175评论 0 0
  • 简介 在JDK1.8之前,HashMap采用数组+链表实现,即使用链表处理冲突,同一hash值的节点都存储在一个链...
    JourWon阅读 576评论 2 13
  • 学习资料; 《Java程序性能优化》 美团点评技术团队 Java 8系列之重新认识HashMap 张旭童大佬 面试...
    英勇青铜5阅读 2,806评论 3 97
  • 字典解释 名词“occasion”有很多个意思,比如“场合” “时机” “特别时刻”等。它的形容词“occasio...
    Aiden拾影阅读 297评论 0 0
  • 选人,而非选事生意经随时会失效企业随时会垮台赛道随时会变化人,有灵魂的活物,随变而化,掌驭一切越是剧变,人味愈显
    咸叔说阅读 298评论 1 1