HashMap1.7、1.8底层原理

HashMap

HashMap是否有线程安全?

看情况,如果只是用get,那么就没有线程安全问题。

如果put完之后,又get,在极端情况下,就有线程安全问题。因为hashmap有扩容机制,key的存储位置是通过hashcode在取模得到的,取模值取决于数组长度,因此当数组触发扩容的时候,取模值也要进行相应的增量,key的位置可能就要发生改变,取模值改变和get方法同时发生就可能会出现问题

说说HashMap在JDK8新增的红黑树?

红黑树是一种数据结构,属于二叉树的一种,叫平衡二叉树,他的特点是非叶子节点用来存储key值,叶子节点全用来存储数据,因此相比于其他树他的查询速率会快,而且支持区间查找。jdk8中新增的红黑树主要用于解决链表遍历的问题,我们都知道,链表插入快查询慢,如果在hashmap中一直在某个位置增加数据,恰巧不触发扩容的话,链表就会很长,查询就会变的很慢,当然没有上述情况也有这个问题。这样的话就需要引入红黑树,原先的数据结构链表就像是二叉树只有又节点呈线性,查询的效率就相比红黑树慢很多,红黑树可以通过自旋,减少树的高度,增快查询速率。但是红黑树并不是直接替换链表的,它设置了链表大小到8时才会转为红黑树,因为,链表还有插入快这一优点。

说说hashmap的底层数据结构

1.7数据+链表

1.8数据+链表+红黑树

HASHMAP在JDK新增的红黑树?

JDK8新增了什么,为什么?

hashmap数据结构:1.7之前数组+链表 1.8后增加了红黑树

​ 特点:数组插入慢查询快,链表插入删除快查询慢

​ 原理:put》通过哈希算法以key算出hashcode,再通过取模得到存储位置,如果相同的存储位置就发生哈希碰撞并变成链表形式;get》前面也是想put一样通过计算得到下标存储位置,如果找不到,就遍历当前位置的链表,先比较key、hash,如果没有就查看next,继续遍历直到有为止或到底。

哈希算法(散列):就是把任意长度值(key)通过散列算法编号成固定长度

的key(hashcode),通过这个地址进行访问的数据结构。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LMV0Pf4a-1595158025997)(C:\Users\wuhon\AppData\Roaming\Typora\typora-user-images\image-20200718230119383.png)]

扫描二维码关注公众号,回复: 11558125 查看本文章

哈希碰撞:当某个key得出的存储位置上已经有key存储过了,就会发生哈希碰撞,即不会覆盖,而是在那一列变成链表,新的key的next属性值就指向旧key

hashmap中key能不能为null?

可以的,因为java会自动提供一个单独的hash值 给空值,且重复null只会覆盖

源码实现hashmap

实现的是1.7的没有链表的hashmap,也没增加扩容机制

package hashmaptest;

/**
 * @author whongf
 * @create 2020-07-19-14:09
 */
public interface Map<K,V> {
    public V put(K k,V v);
    public V get(K k);
    public  int size();
    public interface Entry<K,V>{
        public K getkey();
        public  V getvalue();

    }
}

package hashmaptest;

/**
 * @author whongf
 * @create 2020-07-19-14:13
 */
public class HashMap<K,V> implements Map<K,V> {
    private  Entry[] table=null;
    int size=0;
    public HashMap(){
        table=new Entry[16];

    }

    @Override
    public V put(K k, V v) {
        int index =hash(k);
        Entry<K,V> entry=table[index];
        if (entry==null){
            table[index]=new Entry(k,v,index,null);
            size++;
        }else{
            table[index]=new Entry(k,v,index,entry);
        }
        return (V) table[index].getvalue();
    }
    private  int hash(K k){
        return Math.abs(k.hashCode()%16);
    }

    @Override
    public V get(K k) {
        int index=hash(k);
        Entry<K,V> entry=table[index];
        if (null==entry){
            return null;
        }else{
            return find(k,entry);
        }
    }
    private  V find (K k,Entry entry) {
        if(k==entry.getkey()||k.equals(entry.getkey())){
            return (V) entry.getvalue();
        }else{
            if (entry.next!=null){
                return find(k,entry.next);
            }
        }
        return null;
    }

    @Override
    public int size() {
        return size;
    }
    class Entry<K,V> implements Map.Entry<K,V>{

        K k;
        V v;
        int hash;
        Entry<K,V> next;

        public Entry(K k, V v, int hash, Entry<K, V> next) {
            this.k = k;
            this.v = v;
            this.hash = hash;
            this.next = next;
        }

        @Override
        public K getkey() {
            return k;
        }

        @Override
        public V getvalue() {
            return v;
        }
    }
}

package hashmaptest;

/**
 * @author whongf
 * @create 2020-07-19-14:56
 */
public class Test {
    public static void main(String[] args) {
        HashMap<String,String> map=new hashmaptest.HashMap<String, String>();
        map.put("学习","学习使我快乐");
        map.put("游戏","游戏有什么好玩");
        System.out.println(map.get("学习"));
        System.out.println(map.get("游戏"));

        for (int i=0;i<1000;i++){
            map.put("测试"+i,"结果"+i);
        }
        for (int i=0;i<1000;i++){
            System.out.println(map.get("测试" + i));
        }
    }

}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UGzIbTAB-1595158026034)(C:\Users\wuhon\AppData\Roaming\Typora\typora-user-images\image-20200719150530544.png)]

jdk1.8hashmap

前面讲过1.7之前原理put》通过哈希算法以key算出hashcode,再通过取模得到存储位置,如果相同的存储位置就发生哈希碰撞并变成链表形式;get》前面也是想put一样通过计算得到下标存储位置,如果找不到,就遍历当前位置的链表,先比较key、hash,如果没有就查看next,继续遍历直到有为止或到底。

我们发现如果put过多,链表有可能会变的非常长,上面又提到了数组和链表的差异,链表插入删除快,查询很慢。因此为了解决链表遍历问题,在jdk1.8是引入了红黑树,红黑树又称是平衡二叉树,原先是用的链表,链表的存储在二叉树就如同线性的,查询效率为o(n),通过红黑树的变色和自旋可以改变树的高度,大大减少了查询时长。

但是jdk1.8不是直接引入红黑树的,它是红黑树和链表的组合,默认用链表,当链表长度为8时,就会转换为treenode红黑树。为什么这样呢,原因上面有提过,链表插入删除很快,为o(1),而红黑树在插入的时候还需要有自旋这个操作,效率会变慢点,因此两者需要有个临界点进行转换权衡。

先是用的链表,链表的存储在二叉树就如同线性的,查询效率为o(n),通过红黑树的变色和自旋可以改变树的高度,大大减少了查询时长。

但是jdk1.8不是直接引入红黑树的,它是红黑树和链表的组合,默认用链表,当链表长度为8时,就会转换为treenode红黑树。为什么这样呢,原因上面有提过,链表插入删除很快,为o(1),而红黑树在插入的时候还需要有自旋这个操作,效率会变慢点,因此两者需要有个临界点进行转换权衡。

猜你喜欢

转载自blog.csdn.net/weixin_41487978/article/details/107450018