HashMap简单分析

来源:互联网 发布:linux 发送arp包 编辑:程序博客网 时间:2024/06/06 10:07

HashMap简单分析

基于哈希表的Map接口实现。此实现提供所有可选的映射操作,并允许使用null值和null键。(除了非同步和允许使用null之外,HashMap类和Hashtable大致相同。)此类不保证映射顺序,特别是不保证该顺序永恒不变。 ——该描述来自JDK_API_1.6

  • HashMap概述

    在了解HashMap之前必须先了解什么是Hash表;简单介绍几种常见的数据结构

    1. 数组:采用一段连续的单元来存储数据;常见的特点是查找快,插入慢
    2. 链表:采用单个节点连接来存储数据,一般单向链表:上一个节点存储的下一个节点索引;常见的特点是查找慢,插入快
    3. 哈希表:根据关键码值进行直接访问的数据结构,也就是说,它通过把关键码值映射到表中的某一个位置,再次访问的时候通过关键值进行查询,插入和删除;但是当我们存储相同Hash值的元素时,我们这个时候就要面临解决Hash冲突,这里就不详细叙述Hash冲突的解决方式,可以自行百度或Google

    HashMap在Java中是使用哈希表来存储键值对(Entry),然而在是通过Key来决定Entry在Hash表中的位置,当Key出现了Hash冲突时,Java中采用的是“链表法”来解决冲突,如下图所示

    Markdown

  • HashMap源码分析

    HashMap.put()方法源码分析

    public V put(K key, V value) {    // 判断是key是否为null,如果是null值的话,那么就使用null键值插入方式    if (key == null)        return putForNullKey(value);    // 代码执行到此处,key不为null,获取key对象的hash值,然后再次hash    int hash = hash(key.hashCode());    // 获取key的hash值在Hash表中的位置    int i = indexFor(hash, table.length);    // 这个循环的作用是将Entry根据Hash值放入指定的Hash表中的并解决Hash冲突    for (Entry<K,V> e = table[i]; e != null; e = e.next) {        // 获取内部类Entry中具体的一个Entry的当前的Key        Object k;        // 比较这个具体Entry中的key的Hash值和当前添加key的Hash值,以及两个key值是否相等        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {            // 如果相等,那么将当前value赋值给oldValue,将添加进来的value赋值给当前key对应的value,最后将被覆盖的oldValue返回出去            V oldValue = e.value;            e.value = value;            // 这个方法是一个空实现,我也不知道它有啥作用,JDK源码中方法注释大致翻译一下:这个方法这样情况下被执行,这个情况是当调用put()方法时,且key在当前Entry中是存在的,然后覆盖当前Entry中的value值            e.recordAccess(this);            return oldValue;        }    }    // modCount作用是计算HashMap当前多少元素,之所以每次在添加元素都计数,    // 是因为HashMap在元素超过当前容量时,需要对当前HashMap进行reHash,对    // 当前HashMap进行扩容,然后对每一个元素进行重新填充,就是因为这个原因    //,API中提示不保证该顺序是永久不变的    modCount++;    // 执行到这里的时候,说明该HashMap没有相同key值的Entry,那么就直接添加一个Entry    addEntry(hash, key, value, i);    // 最后因为没有替换oldValue,然后返回的是一个null的value    return null;}void addEntry(int hash, K key, V value, int bucketIndex) {    // 首先拿出当前hash链表上的Entry,然后将原本的Entry挂在新创建的Entry下    Entry<K,V> e = table[bucketIndex];    table[bucketIndex] = new Entry<K,V>(hash, key, value, e);    // 当size大于HashMap的限制的时候,那么就开始扩容    if (size++ >= threshold)        resize(2 * table.length);}

    HashMap.get()方法分析

    public V get(Object key) {    // 判断当前key值是否为null,假如为null的时候那么就使用特殊获取value的方法    if (key == null)        // 特殊或缺value的方法        return getForNullKey();    // 假如key不为null,那么根据传递key值,算出相应的hash值,找到指定的Entry位置    int hash = hash(key.hashCode());    // 获取Hash值对应的Entry,取到Entry链表下的所有Entry,遍历,直到找到响应的key值,找到后获取相应Entry的value    for (Entry<K,V> e = table[indexFor(hash, table.length)];         e != null;         e = e.next) {        Object k;        if (e.hash == hash && ((k = e.key) == key || key.equals(k)))            return e.value;    }    // 最后没有直到相应的Entry,那么就返回null    return null;}
  • HashMap构造方法简单分析

    1. HashMap():创建一个初始容量为16,负载因子为0.75的HashMap
    2. HashMap(int initialCapacity):创建一个指定初始化容量initialCapacity大小,负载因子为0.75的HashMap
    3. HashMap(int initialCapacity, float loadFactor):创建一个指定初始化容量initialCapacity大小,负载因子为loadFactor的HashMap

    这里我们简单聊一下初始化容量和负载因子的作用,初始化容量其实可以从字面理解就是HashMap的最大长度,负载因子指的是HashMap的最大容量的实际使用率;作为负载因子来说,足够大的话,空间利用虽然充分,有可能出现查找效率低,之所以查找效率低,是因为一旦利用率大的话,哈希冲突可能越多,链表越长;负载因子越小,那么导致Hash列表过去稀疏,那么导致空间浪费

    上述博客参考:HashMap实现原理及源码分析,深入Java集合学习系列:HashMap的实现原理,java_api_1.6