面试中经常会被问到HashMap的原理,所以自己尝试实现了一个极简版的,应该可以应付一般的考官了,但是功能、性能方面肯定远不及官方jdk的了。
先来看看定义的IMap接口:
public interface IMap<K, V> {
V get(K key);
V put(K key, V value);
int size();
interface Entry<K, V>{
K getKey();
V getValue();
}
}
下面是HashMap的实现:
import java.util.ArrayList;
import java.util.List;
public class HashMap<K, V> implements IMap<K, V> {
private static int default_array_len = 16; //hashmap里数组默认长度
private static double default_load = 0.75; //负载因子
private Entry<K, V>[] array = null; //hashmap的数组
private int size = 0; //(链头)元素的个数
public HashMap() {
this(default_array_len, default_load);
}
public HashMap(int array_len, double load) {
this.default_array_len = array_len;
this.default_load = load;
array = new Entry[default_array_len];
}
@Override
public V get(K key) {
int index = getIndex(key);
Entry<K, V> entry = this.array[index];
return getEntryValue(entry, key);
}
//递归得到与参数key一致的entry的value
private V getEntryValue(Entry<K, V> entry, K key){
if (null == entry)
return null;
if (key == entry.getKey() || key.equals(entry.key))
return entry.value;
return getEntryValue(entry.next, key);
}
@Override
public V put(K key, V value) {
//如果array元素个数大于array长度 * load(负载因子),则需要(成倍的)扩容:
if (size > default_array_len * default_load)
doubleSize();
//1.根据key用定义的算法算出key对应的数组下标
int index = getIndex(key);
//2.判断数组对应index位置是否已有元素
Entry<K, V> entry = array[index];
if (null != entry){
//一开始我想到的是逐层寻找链尾节点,再往尾节点后补上新节点,但这样效率会比较低
//把新元素的指针指向旧元素,然后再把旧元素所在的数组区域给占了,这样做就旧节点既不会被GC,复杂度也低了很多
array[index] = new Entry<>(key, value, entry, index);
} else {
array[index] = new Entry<>(key, value, null, index);
size++;
}
return array[index].getValue();
}
/**
* 哈希经典算法之一:取模得到数组下标(会造成大量元素的冲突,有空看看jdk怎么实现的,
* 它几乎不会产生冲突,也就是说数组中每个链表的长度为1)
* hashCode的值可能为负数,为避免下标越界,要取绝对值
* @param key
* @return
*/
private int getIndex(K key){
int index = key.hashCode() % default_array_len;
return index > 0 ? index : -index;
}
//返回数组的(链头)元素的个数
@Override
public int size() {
return this.size;
}
/**
* 数组扩容算法
* 需要把原来的数组里面的元素再散列
* 因为我们使用的哈希算法是取模,扩容后用相同的key得出的数组下标就未必相同了
*/
private void doubleSize(){
Entry<K, V> newArray[] = new Entry[default_array_len * 2];
List<Entry<K, V>> entryList = new ArrayList<>();
//遍历全部元素,并将它们加入到entryList
for (int i=0; i<default_array_len; i++){
if (null == array[i])
continue;
findEntry(array[i], entryList);
}
//再散列
hashAgain(newArray, entryList);
}
//递归查找出entry并加入到list
private void findEntry(Entry<K, V> entry, List<Entry<K, V>> entryList){
if (null == entry.next)
entryList.add(entry);
else {
entryList.add(entry);
findEntry(entry.next, entryList);
}
}
//再散列
private void hashAgain(Entry<K, V>[] newArray, List<Entry<K, V>> entryList){
this.size = 0;
default_array_len *= 2;
this.array = newArray;
for (Entry<K, V> entry : entryList){
put(entry.key, entry.value);
}
}
class Entry<K, V> implements IMap.Entry<K, V>{
K key;
V value;
Entry<K, V> next; //指向下一个Entry的指针
int index; //该Entry在数组中的下标
public Entry(K key, V value, Entry<K, V> next, int index) {
this.key = key;
this.value = value;
this.next = next;
this.index = index;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
}
}
测试代码:
public class Main {
public static void main(String[] args) {
IMap<String, Object> map = new HashMap<>();
for (int i=0; i<10; i++){
map.put("key"+i, "lon"+i);
}
for (int i=0; i<10; i++){
System.out.println(map.get("key"+i));
}
}
}
运行结果:
lon0
lon1
lon2
lon3
lon4
lon5
lon6
lon7
lon8
lon9
顺便说一下,判断HashMap性能的指标有2个:
1.散列越均匀,性能越好
2.冲突越少越好,也就是通过哈希算法算出的索引被占用的概率越少越好