映射表Map

Map也叫映射表或者字典,Map中存储的元素是一个键值对key-value,他们共同包装在Entry<K,V>对象中。我们能通过key直接获取value,就像查字典一样。

JDK中,Map有两种实现方式,一是散列技术,二是红黑树。


常见的Map实现

HashMap通过散列技术实现。Map中的key对象必须定义equals以及hashCode方法。该容器的访问效率很高,几乎等同于数组。一般情况下,没有特殊需求,这应该是默认选项。

LinkedHashMapHashMap的子类,性能略差于HashMap。相比HashMap,该容器能以插入的顺序遍历元素。实现原理是使用链表将各个Node串起来,因此,其迭代效率比HashMap更加高效。他的key要求同HashMap一样。

TreeMap通过红黑树结构实现,因此,其内元素是有序的。元素的key对象必须实现Comparable接口,这样红黑树才能对元素进行排序。由于TreeMap是有序的,也就多出了一些与有序性相关的方法,比如firstKeylastKeysubMap(fromKey,toKey)headMap(toKey)等等。他的查询效率比散列低很多,为\(log(n)\)。

ConcurrentHashMap是一种线程安全的HashMap,主要通过synchronized同步块和CAS的方式实现,不涉及同步锁

WeakHashMap中,如果某个键没有被引用,那么该键可以被GC回收。用于特殊用途。

IdentityHashMap,在进行对比key的时候,使用==而不是equals。用于特殊用途。

小结一下,散列的效率比红黑树要高,但是红黑树的元素是有序的。使用散列Map的key必须覆盖equalshashCode,使用红黑树Map必须实现Comparable接口


散列技术与hashCode

前面说过,Map的实现方式之一是利用散列技术,HashMapLinkedHashMap都使用的散列技术。

数组的索引访问时间为$O(1)$,因为数组的内存是连续的且类型固定。因此,只要获取数组首地址和偏移量(索引),即可直接计算出元素的地址。

散列就是利用数组这一特点,如图1,他将数组作为存储元素的基础。通过使用给定函数$f(n)$计算出key的hashcode,之后再对hashcode二次处理,一般是对数组长度取模,便可得到数组的索引。

另外,不同的key可以计算出相同的hashcode,但是散列函数最好能做到散列值均匀分布在数组中。最差的情况是所有散列值都一样,这将严重拖垮Map的存取速度。

综上,我们可以这样描述HashMap的存取过程。

在调用put(key,value)时,key-value首先会包装在Node<K,V>中。对key调用hash(key)计算散列值,之后散列值对数组长度取模,算得一个数组的索引位置,称为插槽(slot)。插槽位不装实际的数据,而是哨兵节点head。之后每一个相同hashCode的Node都会插入该head的链表中。同时,为了优化查询速度,当链表节点过多的时候,会将链表转化为红黑树。

注意,当元素数超过\(arr-length*load-factor\)的时候,会触发Map的resize。这时候,数组拓展一倍,所有的元素重新散列。因此,这也是HashMap中最损害性能的一部分。为了减少这种情况的发生,最好在最初就对Map的容量需求有个大致的估计。

查询即get(key)的过程和put是相似的。首先通过hashCode拿到元素的插槽位,然后遍历链表或者红黑树,通过equals方法逐个对比。这也是,为什么散列一定要覆盖hashCodeequals的原因。hashCodeequals必须唯一确定一个元素。

图1 HashMap的数据结构


hashCode的写法

前面说过,散列的核心步骤就是计算key的hashCode,因此设计好的hash方法也就非常的必要了。

  • hashCode的结果具有一致性,即无论何时计算得到的结果都一样。这就要求,计算hashCode依赖的值是不可变的
  • hashCode不该依赖具有唯一性的对象信息,如果hashCode依赖的信息每个对象只此一份,我们就永远无法在外部创造一个相同的key来获取value了
  • hashCode必须基于对象的内容生成散列码
  • hashCode产生的散列码最好能均匀分布

以上hashCode设计的几点原则,一下给出Josh Bloch给出的重写hashCode的建议

域类型 计算
boolean c = (f? 0 : 1)
byte/char/short/int c = (int)f
long c = (int)(f ^ f>>>32)
float c = Float.floatToIntBits(f)
double long l = Double.doubleToLongBits(f); c = (int)(l ^ l>>>32)
Object c = f.hashCode()
数组 每个元素使用以上规则

以上是基本类型和对象转换hashCode的方法,当我们计算一个对象hashCode的时候因该将他的每一个域(不可变)都考虑进去。将result初始化为17,result乘以37再加上当前域的hash值再赋予result,即:

result = 37 * result +c

比如我们自定义对象CountingString作为key,hashCode便如下定义

public class CountedString {
private static List<String> created = new ArrayList<>(); private String s;
private int id = 0; public CountedString(String s) {
this.s = s;
created.add(s);
for (String ss : created) {
if (ss.equals(s))//如果s,则他们的id编号必不相同
id++;
}
} @Override
public String toString() {
return "[s:"+s+"],[id:"+id+"],[hashCode:"+hashCode()+"]";
} @Override
public int hashCode() {
int result = 17;
result = result * 37 + s.hashCode();
result = result * 37 + id;
return result;
} @Override
public boolean equals(Object obj) {
return obj instanceof CountedString &&
(s.equals(((CountedString) obj).s)) &&
(id == ((CountedString) obj).id);
}
}

借助框架

最后,实现好hashCode和equals是很需要技巧的,我们可以借助Apache Commons3等一类的框架,他们都自带了不错的工具。

参考

  • Effective Java 3
  • Java编程思想

HashMap简介以及hashCode写法的建议的更多相关文章

  1. Hash及HashMap简介

    Hash简介: Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入(又叫做预映射pre-image)通过散列算法变换成固定长度的输出,该输出就是散列值.这种转换是一种压缩映射 ...

  2. 10条影响CSS渲染速度的写法与建议

    1.*{} #zishu *{} 尽量避开由于不同浏览器对HTML标签的解释有差异,所以最终的网页效果在不同的浏览器中可能是不一样的,为了消除这方面的风险,设计者通常会在CSS的一个始就把所有标签的默 ...

  3. 关于hashMap中 计算hashCode的逻辑推理(二)

    hashMap中,为了使元素在数组中尽量均匀的分布,所以使用取模的算法来决定元素的位置.如下: //方法一: static final int hash(Object key){//jdk1.8 in ...

  4. 重新 java 对象的 equals 和 hashCode 方法的建议和示例代码

    equals 方法 equals 方法需要满足的规范: 自反性: 对于任意非空引用 x, x.equals(x) 应该返回 true; 对称性: 对于任意引用, 当且仅当 x.equals(y) == ...

  5. HashMap 简介

    HashMap 前置条件 了解数组 了解链表 jdk version: 1.8 个人分3步来了解HashMap 通过数据结构图 通过为了完成这样的数据结构我们该怎么做 HashMap 实际put方法源 ...

  6. HashMap和HashTable简介和区别

    一.HashMap简介 HashMap是基于哈希表实现的,每一个元素是一个key-value对,其内部通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长. HashMap是非线程安全的, ...

  7. HashMap/HashSet,hashCode,哈希表

    hash code.equals和“==”三者的关系 1) 对象相等则hashCode一定相等: 2) hashCode相等对象未必相等. == 是比较地址是否相等,JAVA中声明变量都是引用嘛,不同 ...

  8. 转发 java数据结构之hashMap详解

    概要 这一章,我们对HashMap进行学习.我们先对HashMap有个整体认识,然后再学习它的源码,最后再通过实例来学会使用HashMap.内容包括:第1部分 HashMap介绍第2部分 HashMa ...

  9. Java 集合系列10之 HashMap详细介绍(源码解析)和使用示例

    概要 这一章,我们对HashMap进行学习.我们先对HashMap有个整体认识,然后再学习它的源码,最后再通过实例来学会使用HashMap.内容包括:第1部分 HashMap介绍第2部分 HashMa ...

随机推荐

  1. 【SSH进阶之路】Hibernate映射——一对多关联映射(七)

    上上篇博文[SSH进阶之路]Hibernate映射——一对一单向关联映射(五),我们介绍了一对一的单向关联映射,单向是指只能从人(Person)这端加载身份证端(IdCard),但是反过来,不能从身份 ...

  2. Java设置文件权限

    今天遇到一个问题: java写的API,ppt转图片生成的目录及文件 在使用php调用API完成后,再使用php进行删除时,遇到了删除失败的问题(php删除的部分  查看) 部署的环境是Ubuntu ...

  3. [ARM-Linux开发]linux 里 /etc/passwd 、/etc/shadow和/etc/group 文件内容解释

    linux 里 /etc/passwd ./etc/shadow和/etc/group 文件内容解释 一./etc/passwd 是用户数据库,其中的域给出了用户名.加密口令和用户的其他信息 /etc ...

  4. 使用 pthread_cancel 引入的死锁问题

    先来说一下 pthread_cancel 基本概念. pthread_cancel 调用并不是强制终止线程,它只提出请求.线程如何处理 cancel 信号则由目标线程自己决定,可以是忽略.可以是立即终 ...

  5. TCP/IP学习笔记13--IP地址的构成,广播地址,IP多播,子网掩码

    现在,我是蔚蓝的 :在此岸或彼岸,我都是蔚蓝的.  ---李瑾 IP对应的是OSI模型中的网络层,TCP对应的是传输层.每一个参与通信的主机都会有一个IP地址. IP地址(IPv4地址)含4个字节,每 ...

  6. PHP 23种设计模式

    学习PHP,对设计模式永远是逃不掉的:今天把php23种设计模式及其demo好好整理如下: 记录PHP关于23种设计模式的简单Demo. Demo地址:https://segmentfault.com ...

  7. Apache Kafka安全| Kafka的需求和组成部分

    1.目标 - 卡夫卡安全 今天,在这个Kafka教程中,我们将看到Apache Kafka Security 的概念  .Kafka Security教程包括我们需要安全性的原因,详细介绍加密.有了这 ...

  8. bootstrap.min.css.map作用

    我先说一下什么是source map文件. source map文件是js文件压缩后,文件的变量名替换对应.变量所在位置等元信息数据文件,一般这种文件和min.js主文件放在同一个目录下. 比如压缩后 ...

  9. PAT(B) 1085 PAT单位排行(Java:20分)

    题目链接:1085 PAT单位排行 (25 point(s)) 题目描述 每次 PAT 考试结束后,考试中心都会发布一个考生单位排行榜.本题就请你实现这个功能. 输入格式 输入第一行给出一个正整数 N ...

  10. microk8s 搭建

    一.简述 microk8s不通过虚拟机但与主机隔离方式,快速轻巧安装Kubernetes.通过在单个快照包中打包Kubernetes,Docker.io,iptables和CNI的所有上游二进制文件来 ...