HashMap是我们在日常的开发中最长见的类之一,对于HashMap的使用在这里就不用过多的解释了。我们来看看HashMap的简单的实现原理。
首先在jdk1.7之前的版本中,HashMap的实现的都是依靠的,(数组 + 链表)的数据结构。在jdk1.8中采用的是(数组 + 红黑树)的数据结构。
对于HashMap的理解,首先我们可以简单的自己写一个HashMap的实现来加深理解,那么就先不考虑jdk1.8,用(数组 + 链表)的形式来简单的实现。
1.定义一个ICustomMap接口方法有put(), get()方法,并且定义一个Entry内部接口
2.一定一个ICustomHashMap的类实现ICustomMap,实现put()和get()方法
uml图如下:
具体的代码如下:
public interface ICustomMap<K, V> {
K put(K key, V value);
V get(K key);
interface Entry<K, V> {
K getKey();
V getValue();
}
}
public class ICustomHashMap<K, V> implements ICustomMap {
@Override
public Object put(Object key, Object value) {
return null;
}
@Override
public Object get(Object key) {
return null;
}
}
那么就开始实现put()方法。
实现的大致思路是:
对(数组 + 链表)的结构可以这样理解。一堆数据我们按照某个规律分为几个桶,就是分为几个数组,那么分到一个通的,我们可以称之为hash冲突,如何解决hash冲突呢,hashmap的操作是把产生hash冲突的数据按照链表的形式存储起来(有点像ArrayList + linkList的结合)。
那么就开始实现put的方法:
@Override
public Object put(Object key, Object value) {
int hash = key.hashCode();
int i = indexFor(hash);
/**
*
* 遍历这个数组上的没一个节点,观察是否有相同的,如果有相同的节点那么就替换掉
* 判断两个对象是否相等(hashcode是否相等 + equals是否相同)
*/
for (Entry<K, V> e = table[i]; e != null ; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
e.value = (V) value;
return key;
}
}
table[i] = new Entry(key, value, table[i], hash);
return null;
}
static class Entry<K,V> implements ICustomMap.Entry<K,V> {
K key;
V value;
Entry<K, V> next;
int hash;
Entry(K k, V v, Entry<K, V> e, int h) {
key = k;
value = v;
next = e;
hash = h;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
}
在Entry的实现类中,放了4个值,key,value,上一个节点next,和确定唯一性的hash值。
每put的一个新的元素进来的时候,
(1)先求在哪个一个数组下标下,indexFor(),
(2)然后判断是否重复
(3)把值放入下标中,并且把next指向前一个值形成链表
其实这种的方式是有一定的随机性的,如果hash冲突特别的多,hasmap就会退化成一个线性结构。
在jdk1.7的源码中,对此没有给出什么好的解决方法,在jdk1.8中个出的解决方案是,不采用链表,而是采用红黑树来存储hash冲突的数据,在查找的时候,时间复杂度从原先的O(n)将为O(logn)。我们会在下节介绍在jdk1.8中的hashmap的实现。
下面附上hashmap1.7 api的方法,已经自己对这个方法的理解:
hashMap有4个构造方法:
HashMap()
HashMap(int initialCapacity)
HashMap(int initialCapacity, float loadFactor)
HashMap(Map<? extends K,? extends V> m)
最常用的是第一个什么都不传入,默认的数组容量大小为16,负载因子为0.75,负载因子的作用是在数组扩容时体现的,当一个hashmap的hash冲突过多的时候,如果数组不扩容,那么可能的情况就是数据线性存放,那么在查找的时候时间复杂度为O(n),所以当haspmap中的容量 > 数组容量(默认为16) * 负载因子(0.75),数组扩容2倍
void clear() 清除map中所有的元素
Object clone() 返回hashmap浅拷贝的实例,该方法并非map中的方法。
boolean containsKey(Object key) 是否包括某个key
boolean containsValue(Object value) 是否包括某个value
Set<Map.entry<K,V>> entrySet() 返回Map映射中的映射集合。此方法用与遍历map
V get(Object key) 用key查找值,返回值的范性数据
boolean isEmpty() 是否为空 空为true 非空为 false
set<K> keySet() key的set集合
K put(K key, V value) 存放键值对,返回键
void putAll(Map<? extends K,? extends V> m) 存放一个map映射
V remove(Object K) 移除一个键值对
int size() 返回hashmap的键值对个数
collection<V> values() 返回map的值的集合。