05 集合-Map(二)

HashTable

继承Dictionary类,实现Map接口,是JDK1.0时期创建的,对数据键值对,进行哈希存储管理, 所有的对外方法被synchronized修饰,可以看作是HashMap在远古时期的线程安全的容器。

HashTable的底层实现是个Entry数组,Entry对象管理了键值对的key和value,在有哈希冲突的时候,在冲突位置生成基于Entry对象的链表,这是一个单向链表。

基于Entry数组,生成索引的方法如下,0x7FFFFFFF是int的最大值,2的31次方值减一,也就是2147483647

int hash = key.hashCode(); 
int index = (hash & 0x7FFFFFFF) % tab.length;

构造方法创建HashTable对象,默认底层数组长度是11,扩容因子是0.75

public Hashtable() {
    this(11, 0.75f);
}
public Hashtable(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal Load: "+loadFactor);

    if (initialCapacity==0)
        initialCapacity = 1;
    this.loadFactor = loadFactor;
    table = new Entry<?,?>[initialCapacity];
    threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}

put方法存储数据

  1. 强制 要求value不能为null。并根据key计算出本次数据存储要放置的索引位置。
  2. 获取此位置已有的对象,并进行链表遍历,匹配哈希值相等,且key也相等的entry,覆盖它的value。
  3. 没匹配且当前存储的数据个数 大于等于待扩容条件,那么就进行扩容。
    1. 新的Entry数组的长度是就数组长度 *2的结果 加1,如果新容量超过了2147483639,且旧容量等于2147483639时,不做扩容,结束。旧容量不等时,那新容量就是2147483639。
    2. 重新设置扩容触发条件数字等于新容量 * 扩容因子0.75,
    3. 遍历旧数组,数组索引位置有链表对象,再遍历此链表,链表里每个节点的哈希值按照上述方法计算在新数组里的索引位置,并将该节点放在链表的头部。
    4. 重新计算本次新数据在扩容后数组的索引位置,并覆盖之前的计算结果
  4. 创建本次新数据生成的Entry对象,并放在指定索引位置的链表头部

get方法获取数据:

  1. 计算输入的key的哈希值对应的在底层数组的索引位置。
  2. 找到此位置的链表,并遍历,找到哈希值相等,key也相等的entry对象,返回它的value

remove方法移除指定key:

  1. 计算输入的key的哈希值对应的在底层数组的索引位置。
  2. 找到此位置的链表,并遍历,找到哈希值相等,key也相等的entry对象
  3. 将原本指向该Entry对象的操作指向该Entry对象的后置对象。

IdentityHashMap

继承AbstractMap,实现Map接口,它是一种比较特殊的哈希Map,数据只存在底层的数组中,包括key和value。同时,它支持输入的key为null,会特殊处理成一个new Object对象代替。

构造方法创建IdentityHashMap时,会时默认的底层数组长度为64,如果构造方法的参数里指定了长度,那么实际数组长度也就是指定长度 *2;

IdentityHashMap计算输入的key所在索引位置的方法:

private static int hash(Object x, int length) {
    int h = System.identityHashCode(x);
    // Multiply by -127, and left-shift to use least bit as part of hash
    return ((h << 1) - (h << 8)) & (length - 1);
}

value基于当前key的索引和当前数组长度算出的索引位置:

private static int nextKeyIndex(int i, int len) {
    return (i + 2 < len ? i + 2 : 0);
}

put方法存数据流程

  1. 强制对Key进行判断,如果是null,就改为默认值new Object;
  2. 计算出当前输入的key,所在当前数组的索引位置;
  3. 如果当前数组此索引位置有对象item,根据当前key的索引值,基于当前数组长度,计算出value的索引位置。
    1. 如果此时item等于输入的key,那么value就放在第三步得到的索引+1的位置。
  4. 如果当前数组此索引位置没对象,或者有对象,但是与key不想等,设置当前存储的键值对个数+1,如果当前个数*3大于数组长度,执行扩容方法。
  5. 扩容成功后,重新从第2步开始执行。
  6. 最终在索引i位置存key,在索引i+1位置存value。

扩容流程:

  1. 新数组长度一般是旧数组长度的2倍,在到最大数时进行特殊处理。
  2. 从上述存储可知,这个数组是key-value-key-value这样排下去的,那么i的位置是key时,i+1的位置就是value,
  3. 将key基于新数组长度进行计算得到新的索引位置,存储key后接着存储vlaue
  4. 遇到冲突的情况时,跳过2个位置进行存储,直到能放置。

get方法获取数据:

  1. 计算key所在的索引位置,如果此位置为null,返回null
  2. 如果此位置的对象等于输入的key,那么就返回value
  3. 不满足上面2个条件时,对索引进行跳2位,重新从第1步开始比较。

WeakHashMap

弱哈希,继承AbstractMap,实现Map接口。

WeakHashMap的实现,与HashMap的区别在于,它对key的处理是弱引用,HashMap是强引用。

WeakHashMap的底层也是Entry数组 + Entry链表,实现方式可参考HashTable的介绍。

因为是若引用,所以在垃圾回收时,很容易被回收掉,虽然降低了OOM的风险,但是会导致原本应该存在的数据现在不存在了,所以WeakHashMap的底层在处理时,在获取数据后一定要判断是否为null。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值