常用的HashMap到底是个什么结构

0x00 前言

HashMap 是最常用的容器之一,应该没什么疑问了。可你到底了解他吗?网上已经有很多文章来总结 HashMap 了,我来写这篇,主要是为了记录自己阅读之后的一点点小感悟,如若有错误的地方,请大家指正。下文分析基于 jdk1.8

0x01 一句话介绍

HashMap 内部是一个 Node 类数组,每个节点存放对应的数据。

0x02 概述

先来介绍下 HashMap ,主要依据来自 HashMap 的注释(熟悉的同学可以直接跳过到0x03部分)。

1、HashMap 实现了 Map 接口,拥有 Map 的所有操作,具有以下特点:

  • 允许 null 的 key 和 value 。

  • 大致上,他和 HashTable 相同,但是 HashTable 是线程安全的,而 HashMap 非线程安全。出于此原因,HashMap 在性能上明显会优于 HashTable 。

  • 不保证顺序,原因在于其内在的原理,他是根据 key 的 hash 值来计算位置的,所以,顺序自然是无法保证的了。(到底怎么算的,往下看。)

2、HashMap 的 get , put 在hash值比较均匀的情况下,操作都是常数级别的时间复杂度。一个非常重要的点是,capacity 不能设置太高,load factor 不能设置的太低。(这两个变量又是干嘛的呢,这里先卖个关子✧(≖ ◡ ≖✿)嘿嘿)。

3、因为他不是线程安全的,所以可以通过 Collections.synchronizedMap 来包装,从而变成一个线程安全的 Map。

4、拥有 fail-fast 特性。简单来说,就是在遍历的时候,发现元素被改变,就抛出异常。

0x03 解释几个变量

构造函数里面的 initialCapacity

这个参数的意思比较明显,就是初始的 Map 长度。默认是 16。

Node<K,V>[] table

Map 中真正存放元素的地方,可以看到他是一个 Node 数组。Node 结构比较简单,就是一个 key-value 组成的一个链表,其中还有 hash变量,和 next 变量。

float loadFactor

顾名思义,负载因子。默认值是0.75,是一个空间和时间上的权衡。具体怎么来的,可能是一个复杂的逻辑推算。

int threshold

阈值,Map 所能容纳的键值对数量。是根据 Map 中的数组长度*loadFactor计算出来的。看到这个,应该就可以想到,如果 loadFactor设置的太小,会有什么问题了。没错,如果设置太小,容量就会很小,导致空间上的一个浪费,大部分的位置都是空的,没有被充分利用。反之,如果设置太大,就会导致元素放置非常拥挤,查询起来效率就会变低。

0x04 方法分析

构造函数

HashMap 有好几个构造函数,来看一个比较重要的吧。

    public HashMap(int initialCapacity, float loadFactor) {        // 如果传递进来的初始化数组的大小小于0,就是不合法,直接抛异常。
        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;        // 根据 tableSizeFor 方法进行数组长度的对齐。
        this.threshold = tableSizeFor(initialCapacity);
    }    
    // 数组长度的对齐。
    static final int tableSizeFor(int cap) {        int n = cap - 1;        // 经过以下的变化,数组的长度一定是2^n了。 
        n |= n >>> 1;  // 1
        n |= n >>> 2;  // 2
        n |= n >>> 4;  // 3
        n |= n >>> 8;  // 4
        n |= n >>> 16; // 5
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

在这里给方法tableSizeFor举个

猜你喜欢

转载自blog.51cto.com/14288256/2388275