HashMap的简易原理及实现

采用数组/链表的方式,时间复杂度为0(n)

但当需要我们查找的数据量过大时,所需时间较多。当我们需要查找的时间复杂度比较小时可以采用哈希表。

1.哈希算法

哈希算法是其中最重要的部分,对于不同的数据进行哈希算法之后,得到一个不同的数字编码。

不同数据:大小 长度 不同,字符串长度不同等等。不同的数据计算出的hash值不一样,一定要保证相同的数据计算出现的编码是一样的,并且过程不可逆,无法从hash值判断出他是什么元素。

不同的数字编码:都会确定到一个范围之类 比如int范围。

但当数据量过大时可能会出现两个不同的数据计算出一样的编码。

2.哈希表存储方法

2.1存储方式:

K,V 要求K不重复。由K算出hash值。

2.2存储结构:

数组为主要结构 链表/树辅助存储,本篇采用链表的方式辅助。

2.3属性:

数组的长度,当前元素个数,数组被使用的空间大小,扩容(为原容量的两倍)初始容量为16 当空间大小>=数组的长度*0.75进行扩容。

2.4存储原理

由key计算hash值,使用hash值与数组当前最大下标进行&运算,映射一个下标,存入k,v节点

注:1.如果当前存在节点,根据比较hash值以及key来决定是否替换当前节点,如果链表中每一个都不相等,此时存在当前节点的最后一个,增加一个新节点。

2.5查找

通过计算hash值,映射下标,找到该下标是否有节点,有则逐一比较k是否一致,一致则返回。

3.练习:

package lcr0408;

import java.util.Arrays;
import java.util.HashMap;

class Node<K, V> {
    K key;
    V value;
    int hash;
    Node<K, V> next;

    public Node(K key, V value, int hash) {
        this.key = key;
        this.value = value;
        this.hash = hash;
    }
}

public class MyHashMap<K, V> {
    Node<K, V>[] table;
    int tableSize;
    int elementSize;
    int length;
    final int def_Length = 16;

    public MyHashMap() {
        table = new Node[def_Length];
        length = def_Length;
    }

    public V get(K key) {
       
    }
    //扩容2.0
    private void grow() {

    }

    public void put(K key, V val) {
           
    }



}

首先定义一个节点类,属性为K,V,以及hash值,指向下一个节点的next.改变构造方法直接传值。

接着定义MyHashMap的类,需要上面所说tablesize数组被使用的空间大小,elementsize元素多少,length数组长度。def_length数组初始长度。

3.1put存入方法

public void put(K key, V val) {
            System.out.println("放入key:" + key);

        Node<K, V> node = new Node<>(key, val, key.hashCode());
        int h = key.hashCode();
        int index = h & (length - 1);
        if (table[index] == null) {
            table[index] = node;
            tableSize++;
            elementSize++;
        } else {
            Node<K, V> first = table[index];
            if (first.hash == h && first.key == key || first.key.equals(key)) {
                first.value = val;
            } else {
                Node<K, V> temp = first;
                while (temp.next != null) {
                    temp = temp.next;
                    if (temp.hash == h && temp.key == key || temp.key.equals(key)) {
                        temp.value = val;
                        return;
                    }
                }

                temp.next = node;
//                System.out.println("放入node.key:" + node.key);
                elementSize++;

            }
        }
        if (tableSize >= length * 0.75) {
            grow();
            /*System.out.println("tableSIle"+tableSize);
            System.out.println("length:"+length);*/
        }
    }

存入需要的key,val后定义一个node用于接受,算出它的hash值h,已经映射的下标index。如果此时这个数组下标为空,则直接将node传入即可。不为空进行下一步运算,再次定义一个节点first将此头结点赋给他,便于遍历。遍历数组,并进行判断hash以及k相同则进行替代。没有则将放在最后一个节点后即可。此时后面先进行扩容的判断。

3.2get查找

 public V get(K key) {
        int h = key.hashCode();
        int index = h & (length - 1);

        System.out.println("length:" + length);

        Node<K, V> first = table[index];
        if (first != null) {
            V v = null;
            if (first.hash == h && first.key == key || first.key.equals(key)) {
                v = first.value;
            } else {
                Node<K, V> temp = first;
                while (temp.next != null) {
                    temp = temp.next;
                    if (temp.hash == h && temp.key == key || temp.key.equals(key)) {
                        v = temp.value;
                        break;
                    }
                }
            }
            return v;
        }
        return null;
    }

根据key算出哈希值以及下标,进行比较找到则返回,没有则返回null。

3.3扩容

private void grow() {
        int new_length = length + length;
        tableSize = 0;
        Node<K, V>[] newTable = new Node[new_length];
        for (int i = 0; i < length; i++) {
            if (table[i] != null) {
                Node<K, V> e = table[i];
                if (e.next == null) {
                    newTable[e.hash & (new_length - 1)] = e;
                    tableSize++;
                } else {//有链表
                    Node<K, V> lowHead = null, lowNext = null;
                    Node<K, V> hiHead = null, hiNext = null;
                    Node<K, V> next;
                    do {
                        next = e.next;
                        if ((e.hash & length) == 0) {//放在原下标处 与原长度相比较 只看重最新一位
                            if (lowNext == null) {
                                lowHead = e;
                            } else {
                                lowNext.next = e;
                            }
                            lowNext = e;

                        } else {
                            if (hiNext == null) {
                                hiHead = e;
                            } else {
                                hiNext.next = e;
                            }
                            hiNext = e;
                        }

                    } while ((e = next) != null);
                    if (lowHead != null) {
                        newTable[i] = lowHead;//放原本下标
                        tableSize++;
                    }
                    if (hiHead != null) {
                        newTable[i + length] = hiHead;//放原本下表加上原最大下标
                        tableSize++;
                    }
                }
            }
        }
        table = newTable;
        length = new_length;

    }

扩容最为关键,因为不仅需要创建一个新数组,传入原数组。但当我们创建新数组时大小扩大此时数组最大下标变化,此时需要重新计算每个的下标。而在这采用的方法是当我们进行&运算时 原数组与15(以扩容第一次为例)进行1111 扩容后与1 1111此时可以发现后四位是一样的 决定元素关键在于扩容的那一位即第一个1 只有两个结果:1.放于原下标 位置 2.放于原下标再加上原数组长度的位置。那么我们可以将其hash值与原长度1 0000 进行&运算 只会出现 0/1 0000。0放在原下标,另一个放于第二个结果即可。

首先定义一个新长度为原长度的两倍,将tablesize清零。创建新数组。接着遍历整个旧数组,节点不为0处,记录此节点 用e来表示。如果e的下一个为空则只有一个元素,直接传入新数组即可。不为空那么还有链表,此时定义两个链表,一个用lowHead表示传入原下标的,hiHead表示第二种。接着判断e的hash值与原长度&。为0传入lowHead中。结束后将lowHead hiHead分别放入新数组中即可。

  • 23
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值