Java实现HashMap

1.基本概念

定义与特点

HashMap是一个散列表,存储键值对(key,value),每一个键对应一个唯一的值,并且允许null作为键或值。
HashMap是不是线程安全的,当多个线程同时访问和修改同一个 HashMap 时,可能会导致数据不一致或其他并发问题。

数据结构

JDK1.7:数组+链表
JDK1.8:数组+链表+红黑树 链表过长(长度>8)时,将链表转化为红黑树
本篇文章中使用数组+链表实现HashMap,当发生哈希冲突时,元素被存在链表中

//保存链表,MNode是自定义的节点类
    private LinkedList<MNode>[] linkArr;

2.工作原理

哈希函数

hashmap基于哈希表实现,他通过计算key的哈希值来确定元素应该存储的位置

    public int hash(Object key) {
        return key.hashCode() % len;
    }

哈希冲突

不同的键可能会产生相同的哈希值,这种情况称为哈希冲突。

当插入新的键值对时,如果发生哈希冲突(对应的数组位置已有元素)则会遍历链表:检查是否存在相同的键:
如果找到相同的键,新的键值对覆盖旧的键值对。
如果没有找到相同的键,则会将新的键值对插入到链表的末尾。

3.常用方法

put

  1. 判断是否需要扩容:如果当前size>=数组长度*负载因子(0.75),resize()扩容
  2. 根据当前key值计算哈希值,获得存储位置,取出对应链表
  3. 判断是否发生哈希冲突,遍历检查重复键。如果有重复键,更新value值,return
  4. 添加新节点到链表
    如果key值为null,则手动将key值赋为0,以便存储元素。
public void put(Object key, Object value) {
        //判断是否需要扩容
        if (size >= len * LOAD_FACTOR)
            resize();
        //根据当前key计算存储位置
        int index = hash(key);
        //取出对应链表
        LinkedList<MNode> list = linkArr[index];
        //遍历检查重复键
        for(MNode node : list) {
            if(node.key.equals(key)) {
                node.value = value;
                return;
            }
        }
        //添加新节点到链表
        //保存null key
        if (key == null) {
            linkArr[index].add(new MNode(0,value));
        }
        else {
            linkArr[index].add(new MNode(key,value));
        }
        size++;
    }

get

通过哈希值获取位置,遍历相应链表
如果能找到这个key,返回对应的value,找不到则返回null

public Object get(Object key) {
        int index = hash(key);
        for (MNode node : linkArr[index]) {
            //如果能在哈希表找到这个key
            if (node.key.equals(key))
                return node.value;
        }
        return null;
    }

remove

remove方法实现逻辑与get方法相似
先通过哈希值获取位置,遍历相应链表
如果能找到这个key,保存对应的value值,删除当前位置的元素,返回保存的value
如果没找到,返回null

public Object remove(Object key) {
        int index = hash(key);
        for (MNode node : linkArr[index]) {
            if (node.key.equals(key)) {
                Object value = node.value;
                linkArr[index].remove(node);
                size--;
                return value;
            }
        }
        return null;
    }

hash

hash() 方法用于确定对象在哈希表中的存储位置。
哈希表通过将对象的 hashCode 值映射到一个数组的索引上,以此来快速定位和存储对象,从而提高数据的存储和查找效率。

//哈希函数
    public int hash(Object key) {
        return key.hashCode() % len;
    }

4.扩容机制

resize

  1. 数组长度变为原长度的两倍,创建新的链表数组
  2. 遍历初始化新数组
  3. 重新计算哈希值,将所有节点加入新数组
  4. 将新数组赋给原数组
public void resize() {
        len *= 2;
        LinkedList<MNode>[] newLinkArr = new LinkedList[len];
        //初始化新数组
        for (int i = 0; i < newLinkArr.length; i++) {
            newLinkArr[i] = new LinkedList();
        }
        //重新哈希所有节点
        for (LinkedList<MNode> list : linkArr) {
            for (MNode node:list) {
                int newIndex = hash(node.key);
                newLinkArr[newIndex].add(node);
            }
        }
        linkArr = newLinkArr;
    }

5.代码实现

import java.util.LinkedList;

public class MhashMap {
    private LinkedList<MNode>[] linkArr;//保存链表
    private static int len = 16;
    private static final float LOAD_FACTOR = 0.75f;
    public int size;

    public MhashMap(int size) {
        linkArr = new LinkedList[size];
        //初始化数组
        for (int i = 0; i < linkArr.length; i++) {
            linkArr[i] = new LinkedList<MNode>();
        }
    }

    public MhashMap() {
        this(len);
    }

    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public void put(Object key, Object value) {
        //判断是否需要扩容
        if (size >= len * LOAD_FACTOR)
            resize();
        //根据当前key计算存储位置
        int index = hash(key);
        //取出对应链表
        LinkedList<MNode> list = linkArr[index];
        //遍历检查重复键
        for(MNode node : list) {
            if(node.key.equals(key)) {
                node.value = value;
                return;
            }
        }
        //添加新节点到链表
        //保存null key
        if (key == null) {
            linkArr[index].add(new MNode(0,value));
        }
        else {
            linkArr[index].add(new MNode(key,value));
        }
        size++;
    }
    //哈希函数
    public int hash(Object key) {
        return key.hashCode() % len;
    }

    public LinkedList[] getLinkArr() {
        return linkArr;
    }

    public Object get(Object key) {
        int index = hash(key);
        for (MNode node : linkArr[index]) {
            //如果能在哈希表找到这个key
            if (node.key.equals(key))
                return node.value;
        }
        return null;
    }

    public void resize() {
        len *= 2;
        LinkedList<MNode>[] newLinkArr = new LinkedList[len];
        //初始化新数组
        for (int i = 0; i < newLinkArr.length; i++) {
            newLinkArr[i] = new LinkedList();
        }
        //重新哈希所有节点
        for (LinkedList<MNode> list : linkArr) {
            for (MNode node:list) {
                int newIndex = hash(node.key);
                newLinkArr[newIndex].add(node);
            }
        }
        linkArr = newLinkArr;
    }

    public Object remove(Object key) {
        int index = hash(key);
        for (MNode node : linkArr[index]) {
            if (node.key.equals(key)) {
                Object value = node.value;
                linkArr[index].remove(node);
                size--;
                return value;
            }
        }
        return null;
    }
    
class MNode {
    Object key;
    Object value;
    public MNode(Object key, Object value) {
        this.key = key;
        this.value = value;
    }
}

总结

HashMap以数组 + 链表存储键值对,允许 null 键值且非线程安全。哈希表的设计将复杂的数据结构简化为数组索引,极大地提升了数据操作的效率。在处理哈希冲突时,链表的引入让数据存储更加灵活。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值