自定义HashMap

自定义HashMap

  1. 数组和链表结构:HashMap 底层采用数组加链表的数据结构。数组用于存储键值对,而链表则用于解决哈希冲突。
  2. 哈希算法:当我们使用 put(key, value) 方法存储数据时,HashMap 首先会调用 hashCode() 方法计算 key 的哈希值,然后通过哈希算法将其转换成数组的一个下标,对应存储位置。
  3. 数据碰撞:如果该位置已经有数据,就会发生数据碰撞。此时,HashMap 遍历该位置上的链表,通过 equals() 方法比对每个数据的 key。如果 key 相同,会覆盖链表上该位置的数据;如果 key 不同,会根据 J采用头插法将数据存储在链表中。
  4. 链表转红黑树:当链表上的节点个数大于等于 时,链表会自动转化成红黑树,以提高查找效率。

Node保存数据信息

/*
        相当于HashMap 中的Entry<K,V>
*/
public class Node<K,V>{
    int hash;  //此Node计算出来的hash值,用这个hash值可以找到在数组中的位置
    K k;       //键
    V v;       //值
    Node<K,V> next;//如果hash冲突,生成链表的话,则next就是指向下一个节点,没有下一个节点就是null

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

    @Override
    public String toString() {
        return "Node{" +
                "hash=" + hash +
                ", k=" + k +
                ", v=" + v +
                ", next=" + next +
                '}';
    }

    public void setHash(int hash) {
        this.hash = hash;
    }

    public void setK(K k) {
        this.k = k;
    }

    public void setV(V v) {
        this.v = v;
    }

    public void setNext(Node<K, V> next) {
        this.next = next;
    }

    public int getHash() {
        return hash;
    }

    public K getK() {
        return k;
    }

    public V getV() {
        return v;
    }

    public Node<K, V> getNext() {
        return next;
    }
}

table数组:保存每个Node

public V get(K k)思路:

1.根据 k 得到 数组下标

2.根据下标得到table中取出Node

3.Node==null 说明数组中没有这个key 直接返回null

4.Node!=null 说明数组中存了这个key

​ (1)这个Node中的next为空,说明这不是链表,只是唯一的一个节点,则直接取这个节点v返回

​ (2)这个Node中的next不为空,说明这是个链表(这个链表中的node的k不同,但生成的hash值(即索引)相同),循环这个链表,比较每个node中的k与要找的k是不是equals

public void put(K k , V v )思路:

1.先根据k计算数组索引

2.取出这个索引位置的node

​ (1)node == null 说明没有冲突,直接放到数组的索引位置

​ (2)node != null 说明有冲突,

​ 1)k与node中的k相同,则将v覆盖

​ 2)k与node中的k不同,则表明不同key生成了相同hash,hash冲突,生成链表的节点 ,使用头插法

public class MyHashMap<K,V> {

    //  map的底层就是一个Node数组,每个元素都是一个Node,如果这个元素位置有hash冲突,则这个node变为链表
    private Node<K,V>[] table;
    /*
        数组的初始大小
     */
    private final int DEFAULT_INITIAL_CAPACITY=1<<4; //1<<4 = 1000  就是 16
    private int size = 0; //表示table中实际存的元素个数
    /*
        负载因子:减少生成链表的机会  因为table一旦存满了,则必会增加生成链表的机会
        所有在table还没存满前,就要扩容
     */
    private static final float DEFAULT_FACTOR=0.75f;
    /*
          阈值:数组容量*负载因子
     */
    private int threshold;

    public MyHashMap(){
        table=new Node[DEFAULT_INITIAL_CAPACITY ];
        threshold = (int) (DEFAULT_FACTOR*table.length);
    }
    public int size(){
        return this.size;
    }

    public V get(K k){
        //1.根据 K 得到 数组下标
        int index = index(table.length, k );
        //2.根据下标得到table中取出Node
        Node<K,V> node = table[index];
        //3.Node==null,说明map中还没有存这个key,直接返回null
        if (node==null){
            return null;
        }
        //4.Node!=null ,说明数组中有这个key
        //      (1)这个Node中的next为空,说明这不是链表,只是唯一的一个节点,则直接取这个节点v返回
        if (node.getNext()==null){
            return node.getV();
        }else {
            //      (2)这个Node中的next不为空,说明这是个链表(这个链表中的node的k不同,但生成的hash值(即索引)相同)
            //           循环这个链表,比较每个node中的k与要找的k是不是equals
            if (node.getK() == k ){
                return node.getV();
            }
            Node<K,V> next = node.next;
            while (next!=null){
                if (next.getK() == k){
                    return next.getV();
                }
                next=next.next;
            }
        }
        return null;
    }

    public void put(K k , V v ){
        //1.先根据k计算数组索引
        int index = index(table.length,k);
        //2.取出这个索引 位置的node
        Node<K,V> node =table[index];
        // 判断node==null 说明没有冲突
        //           直接放到数组的索引位置
        if ( node==null ){
            table[index] = new Node<>( hash(k) , k , v , null);
            size++;
        }
        if (node!=null ){
            //node!=null  说明冲突
            if ( node.getK()==k ){
                //      k与node中的k相同,则将v覆盖
                table[index] = new Node<>( hash(k), k, v , null);
            }else {
                //链表的头插法
                //  k与node中的k不同,则表明不同key生成了相同hash,hash冲突,生成链表的节点
                table[index] = new Node<>( hash(k), k, v ,   node   );
                ++size;
            }
        }
        System.out.println(  "元素个数:"+size+",阈值:"+threshold+",容量:"+table.length);
        if (size>threshold){
            System.out.println("扩容");
            resize();  // 扩容  这个数组  ,要将数组中的原来的数据移动
        }
    }
    //扩容
    private void resize(){
        //  容量为2的幂次
        Node[] tableOld = table;
        Node[] tableNew = new Node[tableOld.length<<1];
        for (int i=0; i<tableOld.length;i++){
            if ( tableOld[i]!=null){
                //原来node的hash值对新数组计算一次索引下标
                int newIndex = index(  tableNew.length, tableOld[i].k );
                tableNew[newIndex] = tableOld[i];
            }
        }
        table = tableNew;
        //更新阈值
        threshold = (int) (DEFAULT_FACTOR*table.length);
    }
    
    /*
        利用hash()来完成key生成hashcode的操作
        object中原来就有hashCode(), key.hashCode()就已经生成了hash码?
        a b c d e f 不够离散    再进行生成减少hash冲突
        解决方案:1.先key.hashCode  2.hashCode是32位参数,将它的高低16位混洗,( ^ )
     */
    static final int hash( Object key){
        int h = key.hashCode();//获取原来的hashCode
         /*
              ^ 异或运算(位运算,相异为1,相同为0)
              >> 右移16位
         */
        return (key==null)?0:( h^(h>>16)    );//打乱原来的hashCode并返回出去,减少hash冲突
    }
    /*
        计算在Node[]数组中的索引
        n:表示Node[]数组的长度  通常是2的幂次   -1: 全为1
            n=8  n-1=7   111
            n=16 n-1=15  1111
     */
    static int index(int n,Object key){
        //hash(key)%n;   //按位与操作比取模操作更高效
        /*
            等同于求余数(即取低位的数),其余数即为key在数组中的索引
            如 n=8 时 即取 hash(key)二进制的后面四位  取低位
         */
        return (n-1)&hash(key);// 按位与  取低位
    }

}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值