实习HashMap1.7

1、作为一条通用的规则,loadfactor 设置为0.75.这是时间和空间的权衡选择,要是特别大那么可能存在这样的情况:hash冲突特别严重但是就是没达到阈值,而如果小点的话就能减轻这种情况。所以 loadfacor 特别大应该会影响get 和put 操作的时间复杂度。特别小的时候又会造成空间的浪费。
“collection views method” 会fail fast 抛出并发修改异常。

2、如何找到大于等于某个数的最小2的幂次方数?

    private static int roundUpToPowerOf2(int number) {
        // assert number >= 0 : "number must be non-negative";
        return number >= MAXIMUM_CAPACITY
                ? MAXIMUM_CAPACITY
                : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
    }

3、inflateTable

    private void inflateTable(int toSize) {
        // Find a power of 2 >= toSize
        int capacity = roundUpToPowerOf2(toSize);

        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        table = new Entry[capacity];
        initHashSeedAsNeeded(capacity);
    }

inflateTable 在HashMap 准备使用的时候比如通过一个已经存在的Map 来构造一个Map、put的时候如果是EMPTY_TABLE 则都会进行inflateTable()。

    public HashMap(Map<? extends K, ? extends V> m) {
        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                      DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
        inflateTable(threshold);
        putAllForCreate(m);
    }
    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;
    }

那么inflateTable中除了创建table ,initHashSeedAsNeeded是什么作用?主要是初始化hashSeed
Holder是一个内部类,包装这一个只有在虚拟机起来之后才能初始化的值ALTERNATIVE_HASHING_THRESHOLD,通过java -Jdk.map.althashing.threshold=512 Myapplication 来设置。The alternative hash function improves the performance of these map implementations when a large number of key hash collisions are encountered.

    final boolean initHashSeedAsNeeded(int capacity) {
        boolean currentAltHashing = hashSeed != 0;
        boolean useAltHashing = sun.misc.VM.isBooted() &&
                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        boolean switching = currentAltHashing ^ useAltHashing;
        if (switching) {
            hashSeed = useAltHashing
                ? sun.misc.Hashing.randomHashSeed(this)
                : 0;
        }
        return switching;
    }

在这里插入图片描述
改进的hash function
当我们调用initHashSeedAsNeeded 之后,hashSeed 就可能被更新,那么生成key的哈希值的时候就能使用优化后的hash函数。

    final int hash(Object k) {
        int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

        h ^= k.hashCode();

        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

下面我们再看一下get

    public V get(Object key) {
        if (key == null)
        //从索引为0 的位置上的链搜
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);

        return null == entry ? null : entry.getValue();
    }
    
    final Entry<K,V> getEntry(Object key) {
        if (size == 0) {
            return null;
        }

        int hash = (key == null) ? 0 : hash(key);
        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 != null && key.equals(k))))
                return e;
        }
        return null;
    }

再看一下get

    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;
    }
    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);
    }
    void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }

学习目标

了解HashMap 1.7 整体结构,从put get 操作了解HashMap 工作流程,及其存在的问题。

整体结构

从一个算法题讲起

如何得到大于或者等于某个正数的2的幂次方数?

public int getBiggerOrEqual(int size){
	//假设size 是 大于0的
  // 大致分为 2 类数据 一类是本身就是符合要求的2 的幂次方数 另一类就是不符合 其二进制数有多个bit 为1 
  if(size == 1){
  	return 1;
  }
  return Integer.highestOneBit(((size - 1 )<< 1));
}

jdk8的做法是

public int getBiggerOrEqual(int size){
	size = size - 1;
  size |= size >>> 1;
  size |= size >>> 2;
  size |= size >>> 4;
  size |= size >>> 8;
  size |= size >>> 16;
  //通过上面的移位以及 或 的操作 此时 size 最高位之后变为全 1 的 
  return size + 1 ;
}

思考:jdk 在这方面类似的做法是 CurrentHashMap 在计算总数(不准确)的时候使用了LongAdder 的思想,但是同样没有使用封装好的代码 而是根据场景进行了适配。

hashSeed 有什么作用

通过Holder 来持有一个在VM 启动后才初始化的变量ALTERNATIVE_HASHING_THRESHOLD,该变量是一个替换哈希函数的阈值。该值可以通过我们 java -Djdk.map.althashing.threshold=512 MyApplication 来设置,之后扩容时我们的capacity 大于等于512 就可能发生rehash。

private static class Holder {

        /**
         * Table capacity above which to switch to use alternative hashing.
         */
        static final int ALTERNATIVE_HASHING_THRESHOLD;

        static {
            String altThreshold = java.security.AccessController.doPrivileged(
                new sun.security.action.GetPropertyAction(
                    "jdk.map.althashing.threshold"));

            int threshold;
            try {
                threshold = (null != altThreshold)
                        ? Integer.parseInt(altThreshold)
                        : ALTERNATIVE_HASHING_THRESHOLD_DEFAULT;

                // disable alternative hashing if -1
                if (threshold == -1) {
                    threshold = Integer.MAX_VALUE;
                }

                if (threshold < 0) {
                    throw new IllegalArgumentException("value must be positive integer.");
                }
            } catch(IllegalArgumentException failed) {
                throw new Error("Illegal value for 'jdk.map.althashing.threshold'", failed);
            }

            ALTERNATIVE_HASHING_THRESHOLD = threshold;
        }
    }

final boolean initHashSeedAsNeeded(int capacity) {
        boolean currentAltHashing = hashSeed != 0;
        boolean useAltHashing = sun.misc.VM.isBooted() &&
                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        boolean switching = currentAltHashing ^ useAltHashing;
        if (switching) {
            hashSeed = useAltHashing
                ? sun.misc.Hashing.randomHashSeed(this)
                : 0;
        }
        return switching;
    }

总的来说就是为了优化大量哈希碰撞下的哈希函数。The alternative hash function improves the performance of these map implementations when a large number of key hash collisions are encountered.

分析多线程环境下resize成环问题

在这里插入图片描述

JDK1.7的特点

整体结构:数组+单链表

扩容时机 :当插入元素时需要新建entry 时候,若当前size >= threshold && null != table[index] 时候才扩容。扩容后元素通过 (哈希值) & (tableSize - 1)放置元素,若没有经过rehash 则元素是只能放到两个桶位位置,一个是低桶位(新增参与运算的位 0)一个是高桶位(新增参与运算的位为1) 二者差值 为旧table的length

扩容前后

数组长度 - 1

key (新增参与运算的位 的情况)

key 对应的下标值

扩容前(容量 16)

0000 0000 0000 0000 0000 0000 0000 1111

1101 1111 1110 11111 11111 1000 1111 1010

0000 0000 0000 0000 0000 0000 0000 1010(10)

扩容后(容量32)

0000 0000 0000 0000 0000 0000 0001 1111

1101 1111 1110 11111 11111 1000 1111 1010

0000 0000 0000 0000 0000 0000 0001 1010(26)

插入元素时采用的方式:头插法。原因:刚插入的数据更容易被使用。

先扩容后插入

hash函数:在key 为String 并且hashSeed != 0 情况下使用优化的hash函数,否则经过多次移位扰动。

loadfactor : 作为一条通用的规则,loadfactor 设置为0.75.这是时间和空间的权衡选择,要是特别大那么可能存在这样的情况:hash冲突特别严重但是就是没达到阈值,而如果小点的话就能减轻这种情况。所以 loadfacor 特别大应该会影响get 和put 操作的时间复杂度。特别小的时候又会造成空间的浪费。

“collection views method” 会fail fast 抛出并发修改异常。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值