Java集合-Map

本文详细介绍了Java中的Map接口,重点讲解了AbstractMap、HashMap和HashTable的实现原理。HashMap在JDK1.7和1.8的工作原理对比,包括负载因子、扩容策略和插入操作。同时,文章提到了HashTable的线程安全性以及TreeMap的升序特性。
摘要由CSDN通过智能技术生成

类图

Map类图

图解

Map

Map

特点

键值对的存储
元素需要重写hashCode()和equals()

api

增加:put(K key, V value);
删除:clear(); remove(Object key);
查看:get(Object key); size(); entrySet(); keySet(); values();
判断:containsKey(Object key);containsValue(Object value);
equals(Object o); isEmpty();

AbstractMap

特点

  1. 元素无序、唯一
  2. 底层结构:哈希表(数组 + 链表)

HashMap

特点

线程不安全,效率高
key可以存入null,并且唯一

属性

// 数组初始化长度 ==> 16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 数组最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
// 负载因子/加载因子的默认值:0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 底层主数组
transient Entry<K,V>[] table;
// 元素的数量
transient int size;
// 数组扩容的边界值/门槛值,默认为 0
int threshold;
// 装填因子、负载因子、加载因子,影响扩容边界值
final float loadFactor;

内部类

static class Node<K,V> implements Map.Entry<K,V> {
   
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
负载因子为什么是0.75

数组扩容边界值 = 数组长度 * 负载因子

  1. 如果设置为1:空间利用率高;但哈希碰撞的几率也变高,而链表的查询效率较低
  2. 如果设置为0.5:哈希碰撞的几率低,产生链表的几率低,查询效率高;但扩容频繁,空间利用率低
  3. 所以在 0.5 ~ 1 之间,取中间值:0.75
JDK1.7
工作原理

HashMapJDK7工作原理

  1. 元素: key,value
  2. 调用key的hashCode(),得到hash值,hash值对数组table长度进行取余hash & (length - 1) 等效于 hash % length(& 的效率比 % 要高),得到元素在数组中的位置(i)
  3. 通过节点的next属性,遍历table[i]上链表的节点e
  4. 比较e.key 和 key是否相等
    • e.hash == hash:首先判断hash值,hash不相等,则key一定不相等
    • e.key == key:判断key的地址值,地址值一样,则key一定相等
    • key.equals(e.k):最后调用equals() 判断key 是否相等
  5. 若相等,value替换oldValue,并return oldValue
  6. 判断是否需要扩容
  7. 采用头插法,获取table[i]上的节点e,封装元素new Entry(hash, k, v, e)next指向e,table[i]重新指向新节点,成为新的头节点
  8. return null
构造函数
// 无参构造
public HashMap() {
   
    // this(16, 0.75)
    this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
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);

    // Find a power of 2 >= initialCapacity
    int capacity = 1;
    while (capacity < initialCapacity)
	// 左移1位,等同于 * 2
	// 确保数组的长度为 2的整数倍
        capacity <<= 1;
    // 确定使用的负载因子
    this.loadFactor = loadFactor;
    // threshold = capacity * loadFactor = 16 * 0.75 = 12;
    // 确定数组扩容的边界值:12
    threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
    // 创建主数组,长度为16
    table = new Entry[capacity];
    useAltHashing = sun.misc.VM.isBooted() &&
            (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
    init();
}
  1. 默认数组长度为16,负载因子为0.75
  2. 调整数组长度,变为2的整数倍
  3. 确定数组扩容边界值:数组长度 * 负载因子
  4. 创建指定长度的Entry数组table

为什么数组长度必须是2的整数倍?

  1. h & (length - 1) 等效于 h % length 的前提就是 length == 2^n
  2. 减少哈希冲突
put()

添加元素

public V put(K key, V value) {
   
    // 对key == null处理,允许key的值为空
    if (key == null)
        return putForNullKey(value);
    // 获取key的哈希值
    int hash = hash(key);
    // 得到元素在数组中的位置
    int i = indexFor(hash, table.length);
    // 哈希碰撞
    // 对table[i]上的链表元素进行循环,key相等则替换value
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
   
        Object k;
		// e.hash == hash:首先判断hash值,hash不相等,则key一定不相等
		// e.key == key:判断key的地址值,地址值一样,则key一定相等
		// key.equals(k):最后调用equals() 判断key 是否相等
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
   
	    // 获取原value
            V oldValue = e.value;
	    // 替换成新value
            e.value = value;
            e.recordAccess(this);
	    // 返回原value
            return oldValue;
        }
    }

    modCount++;
    // 添加元素
    addEntry(hash, key, value, i);
    return null;
}
hash()

计算key的哈希值

final int hash(Object k) {
   
    int h = 0;
    if (useAltHashing) {
   
        if (k instanceof String) {
   
            return sun.misc.Hashing.stringHash32((String) k);
        }
        h = hashSeed;
    }
    // 二次散列,没有直接使用hashCode()的值,减少哈希冲突
    h ^= k.hashCode();

    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值