HashMap 原理分析

HashMap 原理分析

hashmap的底层结构如上图所示,主要是数组链表组成。

当我们创建一个hashmap对象之后,调用put方法的时候,首先通过key获取到一个hash值也就是hashcode,然后还会获取到一个所在数组的下标index,算法是:

hashcode = key.hashcode();

index = hashcode%数组长度-1;

然后将得到的hashcode、index、key和value存入一个entry对象当中。然后在将这个entry存入对应的数组位置上。但是通过上边的hashcode底层原理图会出现一个问题, 就是如果一个位置上已经存在一个entry了,但是现在又有一个entry也要存到这个位置上边,应该怎么实现呢?这里我们就要用到链表结构,在entry对象中在放入一个链表(node)的next属性。这样我们就可以在同一个位置上存储多个entry了。

但是还有一个问题,就是数组的扩容问题,当一个数组的长度到一定值的时候,就需要扩大数组的容量,这个值为:

int 数组扩容阈值=数组原长度*0.75;

如果到了这个阈值就需要扩充数组的长度。但是这个时候就会出现问题。比如之前在数组下标为4位置上的entry在扩容之后,根据 index获取公式,它可能会到别的位置上,比如19上边去。这是就会造成,在调用get方法的时候,返回为null。这就是为什么说hashmap是线程非安全的。

下面上代码:

/**
 * 自定义map接口
 */
public interface Map<K,V> {

	public V put(K k,V v);
	
	public V get(K k);
	
	public int size();
	
	interface Entry<K,V>{
		public K getKey();
		public V getValue();
	}
}

 

/**
 * 
* @ClassName: HashMap
* @Description: 自定义hashmap类  实现自定义的map接口
* @author hsd
* @date 2019年3月8日
*
 */
public class HashMap<K,V> implements Map<K,V>{
	
	// map的底层是数组加链表,所以这里先定义一个数组
	private Entry[] table = null;
	// 数组初始化长度
	private static int defaultLength = 16;
	// 需要扩容的加载因子,  当数组的容量(数据长度size)为16*0.75=12的时候,数组就需要扩容
	private static double defaultLoader=0.75d;
	
	private int size = 0;
	
	public HashMap(){
		this(defaultLength,defaultLoader);
	}
	
	public HashMap(int length,double loader) {
		defaultLength = length;
		defaultLoader = loader;
		table = new Entry[defaultLength];

	}

	@Override
	public V put(K k, V v) {
		// 获取元素所在数组的位置,也就是数组的下标。
		int index = hash(k);
		Entry<K,V> entry = table[index];
		if(entry == null){
			table[index] = addEntry(index,k,v,null);
			size ++;
		} else{
			table[index] = addEntry(index,k,v,entry);
		}
		
		return (V) table[index].getValue();
	}
	
	
	
	
	private Entry<K,V> addEntry(int hash,K k, V v, Entry next) {
		
		return new Entry<K,V>(k,v,next,hash);
		
	}

	public int hash(K k){
		int index = k.hashCode()%defaultLength-1;
		return Math.abs(index);
	}

	@Override
	public V get(K k) {
		System.out.println("这是自己手写的hashmap");
		Entry<K,V> entry = getEntry(k);
		
		return entry == null?null:entry.getValue();
	}

	private Entry<K, V> getEntry(K k) {
		if(size == 0){
			return null;
		}
		
		int hash = hash(k);
		for(Entry e = table[hash];e!=null;e=e.next){
			if(e.hash == hash && (k==e.getKey() || k.equals(e.getKey()))){
				return e;
			}
		}
		return null;
	}

	@Override
	public int size() {
		// TODO Auto-generated method stub
		return size;
	}
	
	class Entry<K,V> implements Map.Entry<K, V>{
		
		K k;
		
		V v;
		
		Entry<K,V> next;
		// 每个数组位置放的数据的hashcode值
		int hash;
		
		

		public Entry(K k, V v,Entry<K, V> next, int hash) {
			this.k = k;
			this.v = v;
			this.next = next;
			this.hash = hash;
		}

		@Override
		public K getKey() {
			// TODO Auto-generated method stub
			return k;
		}

		@Override
		public V getValue() {
			// TODO Auto-generated method stub
			return v;
		}
		
	}

}
// 自写的map测试类
public class MapTest {

	public static void main(String[] args) {
		Map<String,String> map = new HashMap<String,String>();
		map.put("kobe","永远的24号");
		map.put("iverson","永远的3号");
		System.out.println(map.get("kobe"));
	}
	
	
}

代码中有一点需要注意的是,在entry中存储的写成了index,而不是hashcode,自己可以修改一下。

还有一点问题是,hashmap在jdk1.8之后,加入了红黑树来做链表那块的处理。优化了hashmap。