前言:算法
平时工做的时候,用的最多的就是ArrayList和HashMap了,今天看了遍HashMap的源码,决定本身手写一遍HashMap。数组
1、建立MyHashMap接口ide
咱们首先建立一个MyHashMap的入口,暴露一个外部调用的接口,里面简单的定义一下put和get。函数
public interface MyHashMap<K,V> { public V put(K k,V v); public V get(K k); interface Entry<K,V>{ public K getKey(); public V getValue(); } }
2、建一个实现类MyHashMapImpl性能
接口定义完成以后,那就要开始实现了,咱们首先建立一个类MyHashMapImpl来实现MyHashMap。而后咱们定义一些变量。以及构造函数,好比咱们定义的数组初始长度为16,加载因子为0.75。这两个参数会涉及到自动扩容,咱们后面再说。this
public class MyHashMapImpl<K, V> implements MyHashMap<K, V> {
//数组的初始长度
private static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
//阀值比例(加载因子)
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
private int defaultInitSize;
private final float defaultLoadFactor;
//Map当中entry的数量
private int entryUseSize;
//数组
private Entry<K, V>[] table;
//构造函数
public MyHashMapImpl() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
public MyHashMapImpl(int defaultInitialCapacity, float defaultLoadFactor) {
if (defaultInitialCapacity < 0)
//容量不合规
throw new IllegalArgumentException("Illegal initial capacity" + defaultInitialCapacity);
if (defaultLoadFactor <= 0 || Float.isNaN(defaultLoadFactor))
//不合规的加载因子
throw new IllegalArgumentException("Illegal load factor" + defaultLoadFactor);
this.defaultInitSize = defaultInitialCapacity;
this.defaultLoadFactor = defaultLoadFactor;
table = new Entry[this.defaultInitSize];
}
}
3、重写put方法spa
咱们首先重写下put方法,能够看到,当Map中存储的数据大于加载因子*初始化数据长度的时候,会第一时间触发扩容机制,扩容的过程也就是从新设置一个更大的数组,并把本来的数组地址指过去,而且把本来的值从新put进去。这个过程若是频繁发生仍是很消耗机器性能的,因此咱们在写代码的时候最好是预估好初始大小,尽可能不触发扩容机制。code
@Override public V put(K k, V v) { V oldValue; //是否须要扩容 //扩容完毕,确定须要从新散列 if (entryUseSize >= defaultInitSize * defaultLoadFactor) { resize(2 * defaultInitSize); } int index = hash(k) & (defaultInitSize - 1); if (table[index] == null) { table[index] = new Entry<K, V>(k, v, null); ++entryUseSize; } else { Entry<K, V> entry = table[index]; Entry<K, V> e = entry; while (e != null) { if (k == e.getKey() || k.equals(e.getKey())) { oldValue = e.value; e.value = v; return oldValue; } e = e.next; } table[index] = new Entry<K, V>(k, v, entry); ++entryUseSize; } return null; } private void resize(int i) { Entry[] newTable = new Entry[i]; defaultInitSize = i; entryUseSize = 0; rehash(newTable); } private void rehash(Entry<K, V>[] newTable) { //获得原来老得entry集合,注意遍历单链表 List<Entry<K, V>> entryList = new ArrayList<Entry<K, V>>(); for (Entry<K, V> entry : table) { if (entry != null) { do { entryList.add(entry); entry = entry.next; } while (entry != null); } } //覆盖旧的引用 if (newTable.length > 0) { table = newTable; } //从新hash也就是从新put entry到hashmap for (Entry<K, V> entry : entryList) { put(entry.getKey(), entry.getValue()); } } class Entry<K, V> implements MyHashMap.Entry<K, V> { private K key; private V value; private Entry<K, V> next; public Entry() { } public Entry(K key, V value, Entry<K, V> next) { this.key = key; this.value = value; this.next = next; } @Override public K getKey() { return key; } @Override public V getValue() { return value; } }
4、重写get方法blog
若是要拿到数组中的值,咱们首先要获取对应的位置。其中有一个基本概念要说一下,每个数据经过hash函数都会获得一个值,而且这个值是固定的,因此咱们能够经过k.hashCode()接口
来获取对应的hash值,而后按照散列算法均匀分散hash值,而后经过hashcode获取对应的值,获得基本数组的下标。这时候就能拿到咱们存在map中的值了,可是hash值并非必定是惟一的,也就是说能够能a.hash和b.hash值是同样的,可是a不等于b,因此若是两个数据hash值相同,会触发hash冲突。严重下降hashmap的性能,本次hash方法的做用也就是尽可能减小hash冲突。使数据排列的更加均匀一些。当咱们遇到hash冲突的时候能够再次hash解决冲突。
@Override public V get(K k) { int index = hash(k) & (defaultInitSize - 1); if (table[index] == null) { return null; } else { Entry<K, V> entry = table[index]; do { if (k == entry.getKey() || k.equals(entry.getKey())) { return entry.value; } entry = entry.next; } while (entry != null); } return null; }