HashMap是怎么实现的?手写一个HashMap(超简单)

哈希表,HashMap

HashMap = 数组 + 链表;

哈希表也可称为哈希映射。

设计哈希表的目的:快速查找

其实我这个类HashMap的实现方法并不复杂,其内部是一个容量很大的数组,通过在数组每个元素内挂一个链表来存储和查找数据的。

 

我们看一下官方HashMap的用法(Student类自备):

HashMap<String, Student> map = new HashMap<>();
		
map.put("1001", new Student(20191001, "张三"));
		
Student s = map.get("1001");
		
System.out.println(s);

控制台输出:

哈希表的设计

先实现一个最简单的哈希表:<Integer, Student>的映射

设计思想:建立一个超大的数组

Student[] a = new Student[100000000];
a[20191001] = ...
a[20191002] = ...

如图一,我们发现数组是不是太夸张了,且很多空间都比较浪费。所以优化一下取后四位数即可,如图二

Student[] a = new Student[10000];
a[1001]=...
a[1002]=...

这样,我们把数组设置10000的容量,通过数组下标就可以快速的查找到对应的数据。

具体代码实现:

public class MyHashMap
{
    //创建一个容量为10000的数组
    private Object[] buffer = new Object[10000];

    //添加数据
    public void put(Object key, Object value)
        {
	    int index = key2index(key);
		
		buffer[index] = value;
	}

    public Object get(Object key)
	{
		int index = key2index(key);
		
		return buffer[index];
	}

	//映射key值(数组下标),将Key映射为数组下标Index
	private int key2index(Object key)
	{
        //除10000取余
		return (Integer)key % 10000;
	}
}

关键步骤:Key2Index方法,将Key映射为数组下标Index

测试一下:

MyHashMap map = new MyHashMap();
		
map.put(20191001, new Student(20191001, "张三"));
map.put(20191002, new Student(20191002, "李四"));
map.put(20191003, new Student(20191003, "麻五"));
		
Student s = (Student)map.get(20191001);
		
System.out.println("结果:" + s);

控制台输出:

接下来我们发现一个新问题,如果新来一届新生20201001怎么办?

map.put(20201001, new Student(20201001, "小新"));

Student s = (Student)map.get(20191001);
		
System.out.println("结果:" + s);

不对啊,这不是我想要的结果啊。怎么回事呢?不难发现,我们存储数据时都是取后面四位数,2020级和2019级都是1001,后面添加的数据把前面的覆盖了,所以数组中只存在“小新”而没有“张三”。

这里就出现了位置冲突的问题

问题解决:

数组里不直接存储Student对象,而是挂一个链表!

位置冲突的处理:

  1. put():首先根据学号找到对应的位置,每个位置存放一个链表,把Student对象挂在链表上。
  2. get():首先根据学号找到对应的位置,每个位置存放着一个链表,遍历这个链表,找到相应的对象。

代码实现:

public class MyHashMap
{
	//每个链表的节点
	public static class Node
	{
		Object key;
		Object value;
		Node next;
	}
    //创建一个容量为10000的数组
    private Node[] buffer = new Node[10000];
    
    //这里使用有头链表实现
    public MyHashMap()
    {
    	//给所有数组元素添加一个头节点(空)
    	for(int i = 0; i < buffer.length; i ++)
    		buffer[i] = new Node();
    }

    //添加数据
    public void put(Object key, Object value)
    {
	    int index = key2index(key);
		
	    //创建一个链表,将键值对赋值
	    Node node = new Node();
		node.key = key;
		node.value = value;
		
		//遍历节点找到尾节点,并将新节点附加
		Node tail = buffer[index];
		while(tail.next != null)
			tail = tail.next;
		
		tail.next = node;
	}

    public Object get(Object key)
	{
		int index = key2index(key);
		
		Node tail = buffer[index];
		//遍历节点找到key值所对应的节点,并返回
		while(tail.next != null)
		{
			if(tail.next.key.equals(key))
				return tail.next.value;
			tail = tail.next;
		}
		
		return null;
	}

	//映射key值(数组下标),将Key映射为数组下标Index
	private int key2index(Object key)
	{
        //除10000取余
		return (Integer)key % 10000;
	}
}

实现按名字检索(字符串查找)

此时,Key应该是字符串类型

我们需要把字符串映射为0-10000的 Index

解决方案:

  1. 取得字符串首字母,将其转化为整数,例如:“张三”取“首字母z”映射为109(参照ASCII码)
  2. 所有字母”z“开头的都挂在buffer[109]位置的链表下

代码实现:改造key2index方法

    //映射key值(数组下标),将Key映射为数组下标Index
	private int key2index(Object key)
	{
		//Key若是数字类型
		if(key instanceof Integer || key instanceof Double)
		{
			return (Integer)key % 10000;
		}
		//Key若为字符串
		else if(key instanceof String)
		{
			//取首字母转成字符
			char n = ((String) key).charAt(0);
			int p = n;
			return p % 10000;
		}
		//直接取对象的HashCode(哈希码)
		return key.hashCode() % 10000;
	}

HashCode,Object下的方法,默认返回对象的内存地址(整型)

测试

		map.put("zhangsan", new Student(20191001, "张三"));
		map.put("zhangsi", new Student(20191002, "张四"));
		
		Student s = (Student)map.get("zhangsi");
		
		System.out.println("结果:" + s);

至此,HashMap的设计基本完成。

最后,再加上泛型的处理就OK啦!

上最终代码!

package my;

/**
 * 作者:夏铭杰
 * 修改于:2019-09-26
 * */
public class MyHashMap<K, V>
{
	//节点
	public static class Node
	{
		Object key;
		Object value;
		Node next;
	}
	
	//数组缓冲区
	private Node[] buffer = new Node[10000];
	
	//初始化每个头节点
	public MyHashMap()
	{
		for(int i = 0; i < buffer.length; i ++)
			buffer[i] = new Node();
	}
	
	/*添加数据
	 * */
	public void put(K key, V value)
	{
		int index = key2index(key);
		
		Node node = new Node();
		node.key = key;
		node.value = value;
		
		Node tail = buffer[index];
		while(tail.next != null)
			tail = tail.next;
		
		tail.next = node;
	}
	
	/*取得数据
	 * */
	public V get(K key)
	{
		int index = key2index(key);
		
		Node tail = buffer[index];
		while(tail.next != null)
		{
			if(tail.next.key.equals(key))
				return (V)tail.next.value;
			tail = tail.next;
		}
		
		return null;
	}
	
	/*删除
	 * */
	public void remove(K key)
	{
		int index = key2index(key);
		
		Node tail = buffer[index];
		while(tail.next != null)
		{
			if(tail.next.key.equals(key))
				tail.next = tail.next.next;
			else
				tail = tail.next;
		}
	}
	
	//哈希函数
	public Integer key2index(K key)
	{
		//Key若是数字类型
		if(key instanceof Integer || key instanceof Double)
		{
			return (Integer)key % 10000;
		}
		//Key若为字符串
		else if(key instanceof String)
		{
			//取首字母转成字符
			char n = ((String) key).charAt(0);
			int p = n;
			return p % 10000;
		}
		//直接取对象的HashCode(哈希码)
		return key.hashCode() % 10000;
	}
}

测试

		MyHashMap<String, Student> map = new MyHashMap<>();
		
		map.put("1001", new Student(20191001, "张三"));
		
		Student s = map.get("1001");
		
		System.out.println(s);

 结果

Key和Value可以是任意类型。这就实现了JDK中HashMap的基本功能了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值