11.剑指JavaOffer-HashMap

经典问题:这三者的区别

image

HashMap的put的逻辑

image

阀值默认8,最低树化容量:64

HashMap 的长度为什么是2的幂次方

Hash 值的范围值-2147483648到2147483647,一个40亿长度的数组,内存是放不下的,用之前还要先做对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标。这个数组下标的计算方法是“ (n - 1) & hash”。(n代表数组长度)。这也就解释了 HashMap 的长度为什么是2的幂次方。“取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。
length = 16 16 -1 = 15 15二进制 1111 再相与(&)hash效果很好

如何有效减少碰撞

  • 扰动函数:促使元素位置分布均匀,减少碰撞几率(hash函数保证了同时包含高16位和低16位的特性,使得更加的不确定性)

  • 使用final对象,并采用合适的equals()和hashcode(),如String Integer ,因为hashcode不会变

追问,为什么若重写equals(Object obj)方法,就必须重写hashcode()方法,确保通过equals(Object obj)方法判断结果为true的两个对象具备相等的hashcode()返回值?

  • 如果重写了equals(),两个对象判断相等了。但是没有重写hashCode(),有可能两对象相等却hashCode不同,则HashSet这种存唯一值的可能会存了两个一样的对象。

为什么使用HashCode?

  • 通过hashCode比较比equals方法快,当get时先比较hashCode,如果hashCode不同,直接返回false。

hash函数

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

图示:


image.png

1.高16bt不变,低16bit和高16bit做了一个异或(得到的hashcode转化为32位的二进制,前16位和后16位低16bit和高16bit做了一个异或)
2.(n-1)&hash=->得到下标

if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
image

HashMap也可以这样写实现线程安全:

Map safeHashMap = Collections.synchronizedMap(HashMap);

但是和Hashtable一样,都是串行的,效率低下。

concurrentHashMap key和Value不可以为null

image

CAS乐观锁,synchronized悲观锁

image

concurrentHashMap 比java 8之前的 segment(分段加锁),锁更细

  • 首先使用无锁操作CAS插入头节点,失败则循环重试

  • 若头节点已存在,则尝试获取头节点的同步锁,再进行操作

size方法和mappingCount()方法的异同,两者计算是否准确

JDK1.7 和 JDK1.8 对 size 的计算是不一样的。 1.7 中是先不加锁计算三次,如果三次结果不一样在加锁。

JDK1.8 size 是通过对 baseCount 和 counterCell 进行 CAS 计算,最终通过 baseCount 和 遍历 CounterCell 数组得出 size。

JDK 8 推荐使用mappingCount 方法,因为这个方法的返回值是 long 类型,不会因为 size 方法是 int 类型限制最大值。

size()最大值是 Integer 类型的最大值,但是 Map 的 size 可能超过 MAX_VALUE, 所以还有一个方法 mappingCount(),JDK 的建议使用 mappingCount() 而不是size()

原文链接:https://blog.csdn.net/qq_27037443/article/details/94441885

多线程下环境如何进行扩容

HashMap扩容

concurrentHashMap扩容

TreeMap 是有序的散列表,它是通过红黑树实现的。它一般用于单线程中存储有序的映射。(了解一下)

手撕一个Hashmap(无红黑树)

首先,接口类


public interface BtyMap<K,V> {

public V put(K k,V v);

public V get(K k);

  interface Entry<K,V>{

      public K getKey();

      public V getValue();

  }

}

具体实现类:

public class ButyHashMap<K,V> implements BtyMap<K, V>{

    private Entry<K, V>[] arr = null;

    int size;

    public ButyHashMap() {

        arr = new Entry[16];

    }

    public int size() {

        return this.size;

    }

    class Entry<K,V> implements BtyMap.Entry<K, V>{

        private K key;

        private V value;

        private Entry<K, V> next;

        public Entry(){

        }

        public Entry(K key, V value,Entry<K, V> next) {
            this.key = key;
            this.value = value;
            this.next=next;
        }

        @Override
        public K getKey(){return key;}

        @Override
        public V getValue(){return value;}

    }

    @Override
    public V put(K key, V value) {
        V oldValue= null;
        int index = key.hashCode() % arr.length;
        if (arr[index] == null) {
            arr[index] = new Entry<K,V>(key, value,null);
            size++;
        } else {
            Entry<K, V> entry=arr[index];
            Entry<K, V> e = entry;
            while(e != null){
                if(key == e.getKey()||key.equals(e.getValue())){
                    oldValue = e.value;
                    e.value = value;
                    return oldValue;
                }
                e = e.next;
            }
            arr[index] = new Entry(key, value, entry);
            size++;
        }
        return oldValue;
    }

    @Override
    public V get(K key){
        int index=key.hashCode() % arr.length;
        
        if(arr[index]==null)
            return null;
        else{
            Entry<K, V> entry = arr[index];
            while(entry!=null){
                if(key == entry.getKey()||key.equals(entry.getKey())){
                    return entry.value;
                }

                entry=entry.next;
            }
        }
        return null;
    }
}

红黑树规则

红黑树本质上还是二叉查找树,额外的引入了5个约束条件:

1.节点只能是红色或黑色

2.根节点必须是黑色

3.所有NIL节点都是黑色的

4.一条路径上不能出现相邻的两个红色节点

5.在任何递归子树内,根节点到叶子节点的所有路径上包含相同数目的黑色节点

红黑树的插入与旋转
那些年,面试被虐过的红黑树

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • HashMap全方位剖析 常见HashMap面试问答 HashMap是不是有序的? 不是有序的。 有没有有序的Ma...
    从林战士们阅读 882评论 0 5
  • 转自:卓庆森https://www.cnblogs.com/zhuoqingsen/p/8577646.html ...
    Java_xiaoman阅读 6,201评论 0 5
  • 【同读一本书刘姣】 2016-03-002-040 -《谈话的力量》 【正文】 “身体接触是无声地告诉对方:...
    城市格调刘姣阅读 422评论 1 1
  • ”我一直注意着哲学家,阅读了他们的大量文字,此刻我暗自思量,大部分自觉的思维肯定属于本能活动,就连哲学家的思维也是...
    鱼1967阅读 195评论 0 3
  • 从今天起,每天1个RIA便签,坚持100天哦。 由《精进》开始,直面自己逃避已久的写作问题。 当你处于人生中两难抉...
    付晓阅读 263评论 0 3