一、HashMap的概述

HashMap可以说是Java中最常用的集合类框架之一,是Java语言中非常典型的数据结构。
      HashMap是基于哈希表的Map接口实现的,此实现提供所有可选的映射操作。存储的是的映射,允许多个null值和一个null键。但此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
   除了HashMap是非同步以及允许使用null外,HashMap 类与 Hashtable大致相同。
   此实现假定哈希函数将元素适当地分布在各桶之间,可为基本操作(get 和 put)提供稳定的性能。迭代collection 视图所需的时间与 HashMap 实例的“容量”(桶的数量)及其大小(键-值映射关系数)成比例。所以,如果迭代性能很重要,则不要将初始容量设置得太高(或将加载因子设置得太低)。
  HashMap 的实例有两个参数影响其性能:初始容量 和加载因子容量 是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。

通常,默认加载因子 (0.75) 在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 get 和 put 操作,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。
  注意,此实现不是同步的。 如果多个线程同时访问一个HashMap实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。这通常是通过同步那些用来封装列表的 对象来实现的。但如果没有这样的对象存在,则应该使用{@link Collections#synchronizedMap Collections.synchronizedMap}来进行“包装”,该方法最好是在创建时完成,为了避免对映射进行意外的非同步操作。

Map m = Collections.synchronizedMap(new HashMap(...));

二、构造函数

HashMap提供了三个构造函数:
HashMap():构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap。
HashMap(int initialCapacity):构造一个带指定初始容量和默认加载因子 (0.75) 的空 HashMap。
HashMap(int initialCapacity, float loadFactor):构造一个带指定初始容量和加载因子的空 HashMap。
这里提到了两个参数:初始容量加载因子。这两个参数是影响HashMap性能的重要参数,其中容量表示哈希表中桶的数量,初始容量是创建哈希表时的容量,加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度,它衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之愈小。对于使用链表法的散列表来说,查找一个元素的平均时间是O(1+a),因此如果负载因子越大,对空间的利用更充分,然而后果是查找效率的降低;如果负载因子太小,那么散列表的数据将过于稀疏,对空间造成严重浪费。系统默认负载因子为0.75,一般情况下我们是无需修改的。
HashMap是一种支持快速存取的数据结构,要了解它的性能必须要了解它的数据结构。

三、数据结构

我们知道在Java中最常用的两种结构是数组和模拟指针(引用),几乎所有的数据结构都可以利用这两种来组合实现,HashMap也是如此。实际上HashMap是一个“链表散列”,如下是它数据结构:

// Entry是单向链表。 它是 “HashMap链式存储法”对应的链表。  
// 实现了Map.Entry接口,即getKey(),getValue(),setValue(V value),equals(Object o),hashCode()这些函数  
static class Entry implements Map.Entry {  
    final K key;  
    V value;  
    // 指向下一个节点  
    Entry next;  
    final int hash;  
 
    // 构造函数
    // 输入参数包括"哈希值(h)", "键(k)", "值(v)", "下一节点(n)"  
    Entry(int h, K k, V v, Entry n) {  
        value = v;  
        next = n;  
        key = k;  
        hash = h;  
    }  
 
    public final K getKey() {  
        return key;  
    }  
    public final V getValue() {  
        return value;  
    }    
    public final V setValue(V newValue) {  
        V oldValue = value;  
        value = newValue;  
        return oldValue;  
    }    
    // 判断两个Entry是否相等  
    // 若两个Entry的“key”和“value”都相等,则返回true。  
    // 否则,返回false  
    public final boolean equals(Object o) {  
        if (!(o instanceof Map.Entry))  
            return false;  
        Map.Entry e = (Map.Entry)o;  
        Object k1 = getKey();  
        Object k2 = e.getKey();  
        if (k1 == k2 || (k1 != null && k1.equals(k2))) {  
            Object v1 = getValue();  
            Object v2 = e.getValue();  
            if (v1 == v2 || (v1 != null && v1.equals(v2)))  
                return true;  
        }  
        return false;  
    }    
    // 实现hashCode()  
    public final int hashCode() {  
        return (key==null   ? 0 : key.hashCode()) ^  
               (value==null ? 0 : value.hashCode());  
    }    
    public final String toString() {  
        return getKey() + "=" + getValue();  
    }    
    // 当向HashMap中添加元素时,绘调用recordAccess()。  
    // 这里不做任何处理  
    void recordAccess(HashMap m) {  
    }    
    // 当从HashMap中删除元素时,绘调用recordRemoval()。  
    // 这里不做任何处理  
    void recordRemoval(HashMap m) {  
    }  
}

从上图我们可以看出HashMap底层实现还是数组,只是数组的每一项都是一条链。其中参数initialCapacity就代表了该数组的长度。下面为HashMap构造函数的源码:

// 找出“大于Capacity”的最小的2的幂,使Hash表的容量保持为2的次方倍
    // 算法的思想:通过使用逻辑运算来替代取余,这里有一个规律,就是当N为2的次方(Power of two),那么X%N==X&(N-1)。
    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1; // >>> 无符号右移,高位补0
        n |= n >>> 2; // a|=b的意思就是把a和b按位或然后赋值给a
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }
    // 构造一个带指定初始容量和加载因子的空HashMap
    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.75)的空 HashMap
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
    // 构造一个具有默认初始容量 (16)和默认加载因子 (0.75)的空 HashMap
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
    // 构造一个映射关系与指定 Map相同的新 HashMap,容量与指定Map容量相同,加载因子为默认的0.75
    public HashMap(Map m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

从源码中可以看出,每次新建一个HashMap时,都会初始化一个table数组。table数组的元素为Entry节点。

// Entry是单向链表。  
    // 它是 “HashMap链式存储法”对应的链表。  
    // 它实现了Map.Entry 接口,即实现getKey(), getValue(), setValue(V value), equals(Object o), hashCode()这些函数  
    static class Entry implements Map.Entry {  
        final K key;  
        V value;  
        // 指向下一个节点  
        Entry next;  
        final int hash;  
         // 构造函数。  
        // 输入参数包括"哈希值(h)", "键(k)", "值(v)", "下一节点(n)"  
        Entry(int h, K k, V v, Entry n) {  
            value = v;  
            next = n;  
            key = k;  
            hash = h;  
        }  
        ......
    }
其中Entry为HashMap的内部类,它包含了键key、值value、下一个节点next,以及hash值,这是非常重要的,正是由于Entry才构成了table数组的项为链表。
上海尚学堂java培训原作,陆续有HashMap等java技术文章奉献,请多关注。

Java中最常用的集合类框架之 HashMap的更多相关文章

  1. java中最常用jar包的用途说明

    java中最常用jar包的用途说明,适合初学者 jar包 用途 axis.jar SOAP引擎包 commons-discovery-0.2.jar 用来发现.查找和实现可插入式接口,提供一些一般类实 ...

  2. java中的常用类(二)

    java中的常用类(二) Math类 Math类的声明:public final class Math extends Object Math类是与数学计算有关的类,里面的方法都是静态方法,直接使用类 ...

  3. 如何在JAVA中实现一个固定最大size的hashMap

    如何在JAVA中实现一个固定最大size的hashMap 利用LinkedHashMap的removeEldestEntry方法,重载此方法使得这个map可以增长到最大size,之后每插入一条新的记录 ...

  4. java中最常用jar包的用途

    jar包用途 axis.jarSOAP引擎包commons-discovery-0.2.jar用来发现.查找和实现可插入式接口,提供一些一般类实例化.单件的生命周期管理的常用方法.jaxrpc.jar ...

  5. java中一些常用的英语

     abstract (关键字  ) 抽象  ['.bstr.kt]  access vt.访问,存取  ['.kses]'(n.入口,使用权)  algorithm n.算法  ['.lg.rie ...

  6. java 中的常用类

    Java 中的包装类 相信各位小伙伴们对基本数据类型都非常熟悉,例如 int.float.double.boolean.char 等. 基本数据类型是不具备对象的特性的,比如基本类型不能调用方法.功能 ...

  7. JAVA中正则表达式常用的四个方法

    JAVA中正则表达式处理字符串的四个常用方法:匹配.分割.替换.截取.其跟字符串的常用函数相似,但是使用正则表达式会更简单.更加简洁.下面是具体的例子: public class TestRegex ...

  8. java中并发下的集合类

    java中常见的集合类大部分是非线程安全的,在多线程情况下会报并发修改异常(ConcurrentModificationException) 并发下的ArrayList类: //集合类不安全的例子 p ...

  9. java中日期常用

    Java中日期的几种常见操作 —— 取值.转换.加减.比较 Java 的开发过程中免不了与 Date 类型纠缠,准备总结一下项目经常使用的日期相关操作,JDK 版本 1.7,如果能够帮助大家节约那么几 ...

随机推荐

  1. laravel的路由设置,路由参数和路由命名(三)

    laravel中必须先配置路由,才能使用.不像tp中不配置也能使用,因为tp可以通过pathinfo进行自动解析. 一.简单的路由设置 我们一般在routes/web.php文件中配置网页端路由. / ...

  2. JAVA多线程之线程间的通信方式

    (转发) 收藏 记 周日,北京的天阳光明媚,9月,北京的秋格外肃穆透彻,望望窗外的湛蓝的天,心似透过栏杆,沐浴在这透亮清澈的蓝天里,那朵朵白云如同一朵棉絮,心意畅想....思绪外扬, 鱼和熊掌不可兼得 ...

  3. Vue添加jquer插件

    一.现象 综合开发需要,需要引用使用 二.解决 1.先安装jquer插件,命令运行: npm i jquery --save-dev (tips:  i  也就是 install --save-dev ...

  4. nsq 初试

    nsqlookupd tcp 4160 http 4161nsqd 4150nsqadmin 4171 1:安装$ brew install nsq1) 停止nsq默认的启动$ brew servic ...

  5. 关于签名sign的坑

    在有些支付文档或其他文档都会要求签名验证 /** * description:签名 * function name:sign * @param $data * @return string */fun ...

  6. 转载 linux基本操作

    转载地址 https://segmentfault.com/a/1190000014840829 前言 只有光头才能变强 这个学期开了Linux的课程了,授课的老师也是比较负责任的一位.总的来说也算是 ...

  7. JS獲取URL的參數

    function GetQueryString(name) { var reg = new RegExp("(^|&)"+ name +"=([^&]*) ...

  8. Springboot & Mybatis 构建restful 服务三

    Springboot & Mybatis 构建restful 服务三 1 前置条件 成功执行完Springboot & Mybatis 构建restful 服务二 2 restful ...

  9. RAM和ROM

    RAM:随机存取存储器(英语:Random Access Memory,缩写:RAM),也叫主存,是与CPU直接交换数据的内部存储器.[1]它可以随时读写(刷新时除外,见下文),而且速度很快,通常作为 ...

  10. netcore程序部署及守护

    一.程序发布 1.在本机编译无误的情况下,选择发布成文件系统.注意如果使用了swagger 需要将生成的xml文档说明复制到发版包里面.否则会报错.(可以在项目的csproj 中加入 <Prop ...