Stack过时的类,使用Deque重新实现。

HashCode和equals的关系

HashCode为hash码,用于散列数组中的存储时HashMap进行散列映射。

equals方法适用于比较两个对象是否相同,Object方法的equals方法默认为比较两个对象的地址是否相同

    public boolean equals(Object obj) {
return (this == obj);
}

在实际开发中hashcode()和equals()都需要自己重写并且需要保证一下原则

  • 两个对象equals(),那么这两个对象的hashcode()一定相等
  • 两个对象hashcode相等,其不一定equals(),因为hashcode函数仅仅是做分散,最终还是会发生一定的冲突

可以考虑在集合中,判断两个对象是否相等的规则是:

    第一步,如果hashCode()相等,则查看第二步,否则不相等;

    第二步,查看equals()是否相等,如果相等,则两obj相等,否则还是不相等。

public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
System.out.println("hashcode o1 :"+o1.hashCode());
System.out.println("hashcode o2 :"+o2.hashCode());
System.out.println("o1 equals o2 ?"+o1.equals(o2));
}

hashcode o1 :1826771953

hashcode o2 :1406718218

o1 equals o2 ?false

HashMap源码解析:

HashMap的定义:

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable

​ 继承抽象AbstractMap,实现了Map。

HashMap中重要常量:

//默认容量

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

//最大容量

static final int MAXIMUM_CAPACITY = 1 << 30;

//默认加载因子

static final float DEFAULT_LOAD_FACTOR = 0.75f;

//链表转成红黑树的阈值

static final int TREEIFY_THRESHOLD = 8;

//红黑树转为链表的阈值

static final int UNTREEIFY_THRESHOLD = 6;

//存储方式由链表转成红黑树的容量的最小阈值

static final int MIN_TREEIFY_CAPACITY = 64;

//HashMap中存储的键值对的数量

transient int size;

//扩容阈值,当size>=threshold时,就会扩容

int threshold;

//HashMap的加载因子

final float loadFactor;

需要指出的是这里loadFactor加载因子在初始化后就不能变更。加载因子也可以叫做扩充因子----毕竟只是拿来判断是否扩容的嘛(#.#)。

初始化HashMap

Map<K,V> map = new HashMap<K,V>();

或者 Map<K,V> map = new HashMap<K,V>(31)

实际调用的代码

public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

this(initialCapacity,DEAULT_LOAD_FACTOR);

public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}

在初始化中会判断,初始化参数是否小于0,抛出IllegalArgumentException();如果HashMap的最大容量MAXIMUM_CAPACITY(也就是2的32次方 ,为什么是2的32次方呢? ),在确定threhold扩容阈值。

细心的同学可能注意到HashMap中所有常量的定义都是int型,Java中int型是32位的。

接下来就是对tableSizeFor的解释:

/**
* Returns a power of two size for the given target capacity.
*/
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

源代码中注释的写的是获取与cap最相近的2的幂。

例如 32 会产生32 而33就会产生64,这个式子很神奇,为什么正确,我也不太明了o(╥﹏╥)o。

Hash()方法

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

^是异或操作 1^1 =0 ;1^0 = 1; 0^1 =1 ;0^0=0; 多位的异或,比如3^2 = 1 (11^10 = 01);

这里需要解释的是,如果(h = key.hashCode())^(h>>>16) 是取h的低16位与高16为进行异或作为低16位与h的高16位进行拼接,得到最后的hash值。 据说能够提高hash的分散程度。嘤嘤嘤。

还有提到一点就是HashSet内部是使用HashMap实现的;这个在解析HashSet的时候会详细提到;

/**
HashSet的Add的方法
*/
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}

HashSet核心方法

putVal()方法

源码:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}

我们慢慢来分析。首先看入参:

  • hash:表示key的hash值
  • key:待存储的key值
  • value:待存储的value值,从这个方法可以知道,HashMap底层存储的是key-value的键值对,不只是存储了value
  • onlyIfAbsent:这个参数表示,是否需要替换相同的value值,如果为true,表示不替换已经存在的value
  • evict:如果为false,表示数组是新增模式

我们看到put时所传入的参数put(hash(key), key, value, false, true),可以得到相应的含义。

作者:端木轩

链接:https://www.jianshu.com/p/7dcff1fd05ad

來源:简书

简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

HashMap中的数据结构

在继续下一步分析之前,我们首先需要看一下HashMap底层的数据结构。

HashMap的数据结构

我们可以看到,HashMap底层是数组加单向链表或红黑树实现的(这是JDK 1.8里面的内容,之前的版本纯粹是数组加单向链表实现)。

回到最骚气的putVal()

Node<K,V>[] tab; Node<K,V> p; int n, i;

Node<K,V>[] tab 用于引用table也就是hash表;

Node<K,V> p 用于指向需要指向红黑树或者链表;

int n 用于存储当前table的长度;

int i 用于存储当前访问的table索引;

if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;

如果表为空或这表的长度为零重新分配表;

if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);

如果当前访问的桶为空,初始化一个新的节点

Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;

当前的hash值等于。。。

else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

当前访问的桶是红黑树,将该值放入红黑树中;

for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}

如果桶是空的,将节点添加到链表后面,如果当前链表长度大于TREEIFY_THRESHOLD将当前链表转化为红黑树。

if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}

如果当前key不为空,并且开启了替换模式,将值直接替掉;

++modCount;
if (++size > threshold)
resize();

当前值大小大于阈值,将当前大小重新定制;到此,最核心的putVal()就浅显的将完成了,虽然还有很多的疑惑为解决;

// Callbacks to allow LinkedHashMap post-actions
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }

在代码中这三个方法体时为空的,用于LinkedHashMap的操作;

参考内容:

作者:端木轩

链接:https://www.jianshu.com/p/7dcff1fd05ad

來源:简书

简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

HashMap源码解析 非原创的更多相关文章

  1. HashMap源码解析和设计解读

    HashMap源码解析 ​ 想要理解HashMap底层数据的存储形式,底层原理,最好的形式就是读它的源码,但是说实话,源码的注释说明全是英文,英文不是非常好的朋友读起来真的非常吃力,我基本上看了差不多 ...

  2. 【转】Java HashMap 源码解析(好文章)

    ­ .fluid-width-video-wrapper { width: 100%; position: relative; padding: 0; } .fluid-width-video-wra ...

  3. Java中的容器(集合)之HashMap源码解析

    1.HashMap源码解析(JDK8) 基础原理: 对比上一篇<Java中的容器(集合)之ArrayList源码解析>而言,本篇只解析HashMap常用的核心方法的源码. HashMap是 ...

  4. 最全的HashMap源码解析!

    HashMap源码解析 HashMap采用键值对形式的存储结构,每个key对应唯一的value,查询和修改的速度很快,能到到O(1)的平均复杂度.他是非线程安全的,且不能保证元素的存储顺序. 他的关系 ...

  5. 详解HashMap源码解析(下)

    上文详解HashMap源码解析(上)介绍了HashMap整体介绍了一下数据结构,主要属性字段,获取数组的索引下标,以及几个构造方法.本文重点讲解元素的添加.查找.扩容等主要方法. 添加元素 put(K ...

  6. HashMap 源码解析

    HashMap简介: HashMap在日常的开发中应用的非常之广泛,它是基于Hash表,实现了Map接口,以键值对(key-value)形式进行数据存储,HashMap在数据结构上使用的是数组+链表. ...

  7. 给jdk写注释系列之jdk1.6容器(4)-HashMap源码解析

    前面了解了jdk容器中的两种List,回忆一下怎么从list中取值(也就是做查询),是通过index索引位置对不对,由于存入list的元素时安装插入顺序存储的,所以index索引也就是插入的次序. M ...

  8. 【Java深入研究】9、HashMap源码解析(jdk 1.8)

    一.HashMap概述 HashMap是常用的Java集合之一,是基于哈希表的Map接口的实现.与HashTable主要区别为不支持同步和允许null作为key和value.由于HashMap不是线程 ...

  9. HashMap 源码解析(一)之使用、构造以及计算容量

    目录 简介 集合和映射 HashMap 特点 使用 构造 相关属性 构造方法 tableSizeFor 函数 一般的算法(效率低, 不值得借鉴) tableSizeFor 函数算法 效率比较 tabl ...

随机推荐

  1. nextcloud 安装

    nextcloud 优化 设置php.ini vim /etc/php/7.0/apache2/php.ini 添加以下代码: opcache.enable= opcache.enable_cli= ...

  2. java.lang.IllegalStateException: getOutputStream() has already been called for this response解决方案

    异常产生原因:web容器生成的servlet代码中有out.write(""),这个和JSP中调用的response.getOutputStream()产生冲突.即Servlet规 ...

  3. FreeSWITCH 使用SSL-WebSocket-WebRTC

    阿里上买的域名, 申请了个免费ssl, 然后开始折腾,,,, 申请了ssl证书, 但是不提供 .pem 格式的下载(*/ω\*) 然后 把一堆 提供的 都下载下来了,  然后 又到网上 搜 crt/c ...

  4. 学习h5(开始)

    webstorm 下载地址:http://www.sdifenzhou.com/6176.html webstorm注册码: 43B4A73YYJ-eyJsaWNlbnNlSWQiOiI0M0I0QT ...

  5. Madgwick算法详细解读

    Madgwick算法详细解读 极品巧克力 前言 接上一篇文章<Google Cardboard的九轴融合算法>. Madgwick算法是另外一种九轴融合的方法,广泛应用在旋翼飞行器上,效果 ...

  6. CentOS 7 更换 阿里云/清华大学 yum 软件源

    阿里云参考:https://opsx.alibaba.com/mirror?lang=zh-CN 清华参考:https://mirrors.tuna.tsinghua.edu.cn/help/cent ...

  7. ubuntu下学习linux

    ---恢复内容开始--- 查看当前正在运行的进程(ps命令, grep 搜索命令) ps -ef # -e 显示所有进程,环境变量 -f 全格式 也可以用: ps -e -f # 显示所有关于java ...

  8. C primer 编程练习 (不断更新)

    目前在看<C Primer>,以后会经常在这篇博客里更新课后的编程练习题 第二章:编程练习 2.1 #include <stdio.h> int main(void) { pr ...

  9. configparser模块读写ini配置文件

    在自动化测试过程中,为了提高脚本的可读性和降低维护成本,将一些通用信息写入配置文件,将重复使用的方法写成公共模块进行封装,使用时候直接调用即可. 这篇博客,介绍下python中利用configpars ...

  10. Hadoop Shell

    1.常用的一些Shell 再好的博客,都不如官方文档好用: http://hadoop.apache.org/docs/r1.0.4/cn/hdfs_shell.html