Java集合框架1HashMap

HashMap定义

  • 1 本文以jdk7为准进行说明
package java.util;
import java.io.*;
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Serializable{
        static final Entry<?,?>[] EMPTY_TABLE = {}; // 空桶
        transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE; // 桶容器
        static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;  // 默认容量
        static final float DEFAULT_LOAD_FACTOR = 0.75f;  // 默认负载因子
       
        transient int modCount;
        // 其他成员变量和方法。。。
}
  • 2 主要成员属性
    1)table属性是一个数组,数组的元素是Entry<?, ?>,Entry保存的是key-value键值对。
    2)DEFAULT_INITIAL_CAPACITY属性是默认容量,大小为16,是HashMap的默认size值。
    3)DEFAULT_LOAD_FACTOR 属性是默认负载因子,当HashMap的size超过容量 X 负载因子时,HashMap经被扩容。
    4)modCount成员属性用来实现“fast-fail”机制(也就是快速失败)。即在并发集合中,其进行迭代操作时,若有其他线程对其结构性的修改,这是迭代器会立马感知到,并且立刻抛出ConcurrentModificationException异常。

HashMap的数据结构

  • 1 当初始化 HashMap 时,系统会创建一个长度为16的Entry数组,数组里存储的元素Entry也被称为“桶(bucket)”,每个 bucket 都有其指定索引,对于HashMap及其子类而言,它们采用Hash算法来计算元素的索引值,即hash(key.hashCode())。
  • 2 如果同时有两个以上的Entry的key.hashCode()一样,那么通过Hash算法计算出来的hash值也一样,这样就发生了hash碰撞,会导致该索引位置同时有多个Entry元素;
  • 3 但是HashMap 的每个“桶”只能存储一个元素,这种情况下,会通过Entry里的next引用,将这几个Entry组成一个链表(Java8中,当该链表长度大于8时,会将链表转变成红黑树,来提高查询速度)。

HashMap源码分析

  • 1 put方法详解
// put方法
public V put(K key, V value) {
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }

    if (key == null) {
        return putForNullKey(value);
    }          

    int hash = hash(key);
    int i = indexFor(hash, table.length);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    modCount++;
    addEntry(hash, key, value, i);
    return null;
}
// addEntry方法
public void addEntry(int hash, K key, V value, int bucketIndex) {
    if ((size >= threshold) && (null != table[bucketIndex])) {
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }
    createEntry(hash, key, value, bucketIndex);
}

1)判断table是否为空,如果为空则扩充table,其中包括确保table的大小为2的整数倍。
2)如果key值为null,则特殊处理,调用putForNullKey(V value),hash值为0,存入table中,返回。
3)如果key值不为null,则计算key的hash值;
4)计算key在table中的索引index;
5)通过索引找到table[index]位置上的Entry“桶”,如果该桶为null,则直接进行增加操作;
6)如果该桶不为null,对其进行遍历,如果发现链表中有Entry的的key一致(hash和equals都一致),则用新值(value)替换旧值(oldValue),并保证key的唯一性;如果Entry“桶”内的所有key没有一个和传进来的key一致的,则进行增加操作。
7)增加操作前先判断是否需要扩容,然后将新加的Entry放到table数组中,之前在table数组中的Entry则被新加的Entry的next引用所指向。例如插入的顺序,原先table[index]的Entry链表是 old1->old2->old3,插入新值之后为new1->old1->old2->old3。

  • 2 get方法详解
public V get(Object key) {   
    if (key == null) {
        return getForNullKey();
    }
   
    int hash = hash(key.hashCode()); 
  
    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;
        }
    }

    return null;   
}   

1)判断key值是否为null,如果是则特殊处理(在table[0]的链表中寻找)
2)否则计算hash值,进而获得table中的index值
3)在table[index]的链表中寻找,根据hash值和equals()方法获得相应的value。
4)如果找不到,最后返回null。

使用自定义的类的对象作为键

  • 1 重写自定义类的equals()和hashCode()方法
  • 2 当对象插入到Map中之后不能再被改变

HashMap中的序列化问题

  • 1 HashMap实现了Serializable接口,但是对table的定义(transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;)却是transient(不再是对象序列化的一部分)的。
  • 2 HashMap的put与get基于hashCode的实现,而hashCode是native方法,对每一个不同的java环境来说,同一个key所计算的hashCode是不相同的,所以反序列化后table的index会发生变化,无法还原
  • 3 HashMap默认size到达阈值threshold之后进行扩容,很明显HashMap不可能保证每一个bullet都有数据,很多都为null,如果对这部分数据进行序列化则造成不必要的资源浪费。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 一、基本数据类型 注释 单行注释:// 区域注释:/* */ 文档注释:/** */ 数值 对于byte类型而言...
    龙猫小爷阅读 9,790评论 0 16
  • 实际上,HashSet 和 HashMap 之间有很多相似之处,对于 HashSet 而言,系统采用 Hash 算...
    曹振华阅读 7,222评论 1 37
  • 5.1、对于HashMap需要掌握以下几点 Map的创建:HashMap() 往Map中添加键值对:即put(Ob...
    rochuan阅读 4,140评论 0 0
  • 距离我申请公众号发送第一篇公众号推送到现在,已经过了131天20小时22分35秒。四个月来,我每天慢悠悠的上班,慢...
    喳西阅读 3,034评论 0 0
  • 桃花结 一朝春风入长安, 百花争放迷人眼。 喧市城外独吐言 十里桃花伴君眠。
    不今心阅读 2,383评论 3 4

友情链接更多精彩内容