简单实现HashMap

前言:

HashMap 是我们经常使用的一种数据结构,而且最近面试也经常被问到HashMap的底层原理,然后呢给面试官说的简单点他会觉得你学习不够深,然后说的复杂点,自己又觉得不切实际,因为自己本来就没有经历过那些场景,感觉自己很虚。因此今天,我先简单实现一下HashMap的最基本的功能。(目前未实现红黑树部分)

代码:

1、首先建立Map接口和存储元素的Entry接口
package MyHashMap;

public interface Map {
	/**
	 * 向map中插入值
	 * @param k 键
	 * @param v 值
	 * @return V 若非更新操作,则返回当前值,若是更新操作,则返回旧值
	 */
	public V put(K k, V v);
	
	/**
	 * 根据K获得V
	 * @param k
	 * @return
	 */
	public V get(K k);
	
	/**
	 * 获取map中元素的个数
	 * @return
	 */
	public int size();
	
        /**
         * 打印当前map中的元素,测试使用
         */
	public void printMap();
	
	
	/**
	 * 集合map中存储键值对(元素)的对象
	 * @author 康茜
	 *
	 * @param 
	 * @param 
	 */
	interface Entry {
		K getKey();
		
		V getValue();
		
		V setValue(V v);
	}
}

2、HashMap实现类

package MyHashMap;


public class HashMap implements Map {
	//HashMap中数组的长度,默认为16
	private int defaultLength = 16;
	//数组table
	Node[] table;
	//table中元素(Entry)的个数,不是table的长度
	int size;
	//加载因子,其实就是设置一个参数,通过这个参数与size的乘积和defaultLength相比看是否需要扩容
	private double loadFactor = 0.75d;
 	//这个model是一个测试参数,可以是%、&,其含义是定位数组下标的两种方式,在position()函数中具体体现
	private static String model = "&";
	
	public HashMap() {
	}
	
	public HashMap(int capacity) {
		this.defaultLength = capacity;
	}
	

	@Override
	public V put(K k, V v) {
		//懒加载
		if(table == null) {
			table = new Node[defaultLength];
		}
		//判断是否扩容
		if(size * loadFactor > defaultLength) {
			System.out.println("进行扩容:" + size + ":" +  defaultLength);
			reSize();
		}
		
		int index = position(k, defaultLength);
		Node nodeHead = table[index];
		//如果当前链表为空,则直接插入并且返回插入值
		if(nodeHead == null) {
			Node node = new Node(k, v, null);
			table[index] = node;
			size++;
			return v;
		} else {
			Node temp = nodeHead;
			//若是更新操作
			if(temp.k.equals(k)) {
				V oldValue = temp.getValue();
				temp.setValue(v);
				return oldValue;
			}
			while(temp.next != null) {
				if(temp.k.equals(k)) {
					V oldValue = temp.getValue();
					temp.setValue(v);
					return oldValue;
				}
				temp = temp.next;
			}
			
			//无更新操作则将新节点头插到链表上
			Node newNode = new Node(k, v, nodeHead);
			table[index] = newNode;
			//插入成功size++
			size++;
			
			//或者使用尾插法,减少一个断链操作即:table[index] = newNode;
//			Node newNode = new Node(k, v, null);
//			temp.next = newNode
		}
		return v;
	}

	@Override
	public V get(K k) {
		int index = position(k, defaultLength);
		Node nodeHead = table[index];//直接定位到数组的链表上
		if(nodeHead == null) {
			return null;
		}
		
		Node temp = nodeHead;
		while(temp != null) {
			if(temp.k.equals(k)) {
				return temp.getValue();
			}
		}
		//如果找不到,则返回null
		return null;
	}

	@Override
	public int size() {
		return size;
	}
	
	@Override
	public void printMap() {
		System.out.println("===================打印map======================");
		for(int i = 0; i < table.length; i++) {
			Node node = table[i];
			System.out.println("下标为【" + i
					+ "】的链表");
			if(node == null) {
				System.out.println("NULL");
				continue;
			}
			while(node != null) {
				System.out.print(node.toString());
				node = node.next;
			}
			System.out.println();
		}
	}
	
	/**
	 * 定位元素在数组中的下标
	 * @param k
	 * @return
	 */
	private int position(K k, int tableLength) {
		int index = 0;
		if(k == null) {
			System.out.println("这里键为空,需要处理键为空的情况");
			//TODO
		}
		
		if(model.equals("&")) {
			index = k.hashCode() & (tableLength - 1);
		} else if(model.equals("%")) {
			index = k.hashCode() % tableLength;
		}
		
//		System.out.println("本次put操作的index:" + index);
		return index;
	}
	

	/**
	 * 扩容:扩容并且重新排列元素
	 * 如果size*loadFactor>defaultLength,则扩容
	 */
	private void reSize() {
		//翻倍扩容
		//1、创建临时数组
		Node[] temp = new Node[defaultLength << 2];
		
		//2、重新计算散列值,将元素插入到temp中
		for(int i = 0; i < table.length; i++) {
			Node node = table[i];
			while(node != null) {
				int index = position((K)node.getKey(), temp.length);
				//头插法插入节点
				Node next = node.next;
				node.next = temp[index];
				temp[index] = node;
				
				node = next;
			}
		}
		/**
		 * Node tempNode = temp[index];
				if(tempNode == null) {
					//这里我本来想选择更改引用的方式,而不是选择重新new一个新的node,但是写了之后发现存在一个问题:因为这个nodeHead指向原链表的
					//,所以如果传递引用的话后续操作就不能轻易对newNode进行修改,所以我采用直接new的方式
					//newNode = nodeHead;
					tempNode = new Node(node.k, node.v, null);
				} else {//如果复制的话,这里table中不会有键值重复的节点,这里直接将新节点加链表后面(头插)
					Node  newNode = new Node(node.k, node.v, tempNode);
					temp[index] = newNode;
				}
		 */
		
		//3、替换
		this.table = temp; 
		this.defaultLength = temp.length;
		temp = null;
		System.out.println("=============扩容成功");
	}
	
	static class Node implements Entry {
		K k;
		V v;
		Node next;
		
		public Node(K k, V v, Node next) {
			this.k = k;
			this.v = v;
			this.next = next;
		}

		@Override
		public K getKey() {
			return this.k;
		}

		@Override
		public V getValue() {
			return this.v;
		}

		@Override
		public V setValue(V v) {
			V oldValue = this.v;
			this.v = v;
			return oldValue;
		}
		
		@Override
		public String toString() {
			return "[key:" + k
					+ ", value:" + v + "]";
		}
		
	}

}

3、测试

package MyHashMap;

public class Test {

	public static void main(String[] args) {
		Map map = new HashMap();
		map.put("000号:", "000");
		map.put("001号:", "001");
		map.put("002号:", "002");
		map.put("003号:", "003");
		map.put("004号:", "004");
		map.put("005号:", "005");
		map.put("006号:", "006");
		map.put("007号:", "007");
		map.put("008号:", "008");
		map.put("009号:", "009");
		
		map.put("010号:", "010");
		map.put("011号:", "011");
		map.put("012号:", "012");
		map.put("013号:", "013");
		map.put("014号:", "014");
		map.put("015号:", "015");
		map.put("016号:", "016");
		map.put("017号:", "017");
		map.put("018号:", "018");
		map.put("019号:", "019");
		
		map.put("020号:", "010");
		map.put("021号:", "011");
		map.put("022号:", "012");
		map.put("023号:", "013");
		map.put("024号:", "014");
		map.put("025号:", "015");
		map.put("026号:", "016");
		map.put("027号:", "017");
		map.put("028号:", "018");
		map.put("029号:", "019");
		
		map.put("030号:", "010");
		map.put("031号:", "011");
		map.put("032号:", "012");
		map.put("033号:", "013");
		map.put("034号:", "014");
		map.put("035号:", "015");
		map.put("036号:", "016");
		map.put("037号:", "017");
		map.put("038号:", "018");
		map.put("039号:", "019");
		
		map.printMap();
		System.out.println(map.size());
	}
}

总结:

目前只完成了数组+链表形式的hashMap实现,因为我现在还不太了解红黑树的算法实现,后续应该会完成数组+链表+红黑树的实现,或者是选择一种合适的算法加入到自己的hashMap中,使其性能更优。


你可能感兴趣的:(java,自己动手简单实现HashMap)