采用数组+节点方式,手写HashMap

模仿JDK1.7版本,手写一个迷你版的HashMap,实现HashMap的部分功能

 

难点分析:

一、Hash碰撞问题

当两次计算的hash取模值相等的情况下,会出现两种情况。

1、两次key完全相等,此时需要做的是修改节点中的value值。

2、两次key不等,此时就在向该链表头节点添加新的Node对象

 二、数组扩容问题

当Hash碰撞出现次数过多时,会造成链表过长,从而影响运行效率,此时需要进行数组扩容。

解决方法是,在每次添加数据时进行一次扩容判断,当实际容量超过阈值时,就进行扩容。

扩容基本步骤为:

1.新建扩容两倍后的table数组
2.重新计算hash索引
3.将table进行替换

 

 

package com.hashmap;

/**
 * 模仿JDK1.7自定义HashMap
 * 
 * @author 李福涛
 *
 */
public class MyHashMap<K, V> {

	// Map的实际容量
	private int size;

	// Map的初始容量
	private int capacity = 1 << 2; // aka 16

	// 默认负载因子的大小
	static final float DEFAULT_LOAD_FACTOR = 0.75f;

	// 存放map的节点链表
	private Node<K, V>[] table;

	// 构造函数
	public MyHashMap() {

	}

	// 向map插入数据
	public void put(K key, V value) {
		// 1.判断table是否为空
		if (table == null) {
			table = new Node[capacity];
		}

		// 2.判断是否需要扩容
		// 实际存储大小>负载因子*初始容量时 -> 0.75*16=12时,需要扩容
		if (size > (DEFAULT_LOAD_FACTOR * capacity)) {
			// 需要对table数组进行扩容
			resize();
		}

		// 3.插入数据
		// 获得key的hash取模后的值,该值就是链表数组对应的下标
		int hash = key.hashCode() % table.length;
		// 获得数组中该下标的链表
		Node<K, V> node = table[hash];
		// 如果该链表为空,则需要新建一个节点
		if (node == null) {
			// 没有发生hash碰撞
			table[hash] = new Node<K, V>(key, value, null);
			size++;
		} else {
			// 发生hash冲突
			// 否则遍历该链表,查看是否有相同的key
			while (node != null) {
				// 判断该节点的key值是否等于新插入的key
				if (node.key.equals(key) || node.key == key) {
					// 如果发生了hash碰撞,直接修改该值
					node.value = value;
					return;
				} else {
					// 否则获取下一节点
					node = node.next;
				}
			}
			// 如果遍历完仍然没有相同的key,则在该链表头位置添加节点
			table[hash] = new Node<K, V>(key, value, table[hash].next);
			size++;
		}
	}

	// 通过key获取值
	public V get(K key) {
		// 获得key的hash取模后的值,该值就是链表数组对应的下标
		int hash = key.hashCode() % table.length;
		// 获得该下标的链表
		Node<K, V> node = table[hash];
		while (node != null) {
			// 判断该节点的key值是否等于要查找的key
			if (node.key.equals(key)) {
				return node.value;
			} else {
				// 否则获取下一节点
				node = node.next;
			}
		}
		return null;
	}

	// 对table数组进行扩容
	private void resize() {
		// 1.新建扩容两倍后的table数组
		capacity = capacity << 1;
		Node<K, V>[] newTable = new Node[capacity];
		// 2.重新计算hash索引
		// 遍历需要扩容的table
		for (int i = 0; i < table.length; i++) {
			// 获取table上的节点的信息
			Node<K, V> oldNode = table[i];
			// 将oldNode上的值添加至newTable中
			putToTable(oldNode, newTable);
		}
		// 3.将table进行替换
		table = newTable;
	}

	// 将oldNode的值放入newTable中
	private void putToTable(Node<K, V> oldNode, Node<K, V>[] newTable) {
		while (oldNode != null) {
			// 计算新的hash值
			int newHash = oldNode.key.hashCode() % newTable.length;
			// 获得该hash值节点
			Node<K, V> node = newTable[newHash];
			// 判断节点是否为空
			if (node == null) {
				// 没有发生hash碰撞
				newTable[newHash] = new Node<K, V>(oldNode.key, oldNode.value, null);
			} else {
				// 又发生hash碰撞
				// 此时发生hash碰撞,其原因不会时key值相同而产生,故直接添加节点
				newTable[newHash] = new Node<K, V>(oldNode.key, oldNode.value, newTable[newHash].next);
			}
			oldNode = oldNode.next;
		}
	}

	// 打印所有元素
	public void print() {
		for (int i = 0; i < table.length; i++) {
			Node<K, V> node = table[i];
			while (node != null) {
				System.out.println("key:" + node.key + "||value:" + node.value + "   ");
				node = node.next;
			}
		}
	}

	class Node<K, V> {
		// 存放key值
		K key;
		// 存放Value值
		V value;
		// 存放下一个节点
		Node<K, V> next;

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

	public static void main(String[] args) {
		MyHashMap<String, String> myHashMap = new MyHashMap<String, String>();
		myHashMap.put("a", "aa");
		myHashMap.put("b", "bb");
		myHashMap.put("c", "bb");
		myHashMap.put("d", "bb");
		myHashMap.put("e", "bb");
		myHashMap.put("f", "bb");
		myHashMap.put("g", "bb");
		myHashMap.put("a", "bb");
		myHashMap.print();
		System.out.println("a:" + myHashMap.get("a"));
		System.out.println("size:" + myHashMap.size);
	}
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值