HashMap:数组+链表,结合二者优势
上一篇粗略理解了Map的各个类,这一篇详写对HashMap的认识。
HashMap特点
HashMap是基于哈希表的Map接口实现。
HashMap底层采用的是Entry数组和链表实现。
HashMap是采用key-value形式存储,其中key是可以允许为null但是只能是一个,并且key不允许重复(如果重复则新值覆盖旧值)。
HashMap是线程不安全的。
HashMap存入的顺序和遍历的顺序有可能是不一致的。
HashMap保存数据的时候通过计算key的hash值来去决定存储的位置。
HashMap的方法:
void clear()
Object clone()
boolean containsKey(Object key)
boolean containsValue(Object value)
Set<Entry<K, V>> entrySet()
V get(Object key)
boolean isEmpty()
Set<K> keySet()
V put(K key, V value)
void putAll(Map<? extends K, ? extends V> map)
V remove(Object key)
int size()
Collection<V> values()
HashMap put(key,value)代码:
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
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))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
HashMap put(key,value)的算法思路:
1)对key计算hashcode(),得到index
2)如果没有发生冲突,直接放进table里
3)如果发生冲突,遍历链表,如果节点已经存在(key重复),则替换old value(从这里可以看出,hashCode方法的存在是为了减少equals方法的调用次数,从而提高程序效率)
4)将节点尾插到链表
5)如果冲突导致链表过长(n>=8),链表转换为红黑树
6)如果table满,则需要resize
课后习题:
1.请讲解HashMap数据结构+put()的算法步骤
答:
HashMap数据结构:
数组的特点是:寻址容易,插入和删除困难;而链表的特点是:寻址困难,插入和删除容易。而HashMap是结合了两者优点的数据结构。
put()算法步骤:
根据传入键值的hashCode计算出index,看table[index],如果为空就直接new一个链表放入新传入的键值实值。如果不为空,遍历链表。如果有键值equals()方法返回true, 则用新的实值替换旧实值,如果不存在则在链表之后添加新的键值实值构造出的Entry对象。之后判断是否树化。
2.加载因子LoadFactor的作用
加载因子是表示Hsah表中元素的填满的程度,加载因子越大,填满的元素越多,此时空间利用率高了,但冲突的机会加大了.。HashMap默认的加载因子是0.75,最大容量是16,因此可以得出HashMap的默认容量是:0.75*16=12。
3.Threshold的作用
threshold是HashMap所能容纳的最大数据量的键值对个数,用于判断是否需要调整HashMap的容量,以达到空间利用率和Hash冲突发生机率相对平衡。threshold的值="容量*加载因子",当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍(resize)。
4.NavigableMap的作用
可以看看NavigableMap的源码:
由于NavigableMap继承了SortedMap,因此它是有序的,以下是它的方法
//返回第一个key小于参数的Entry
Map.Entry<K,V> lowerEntry(K key);
//返回第一个key小于参数的key
K lowerKey(K key);
//返回第一个key小于等于参数的Entry
Map.Entry<K,V> floorEntry(K key);
//返回第一个key小于等于参数的key
K floorKey(K key);
//返回第一个key大于等于参数的Entry
Map.Entry<K,V> ceilingEntry(K key);
//返回第一个key大于等于参数的key
K ceilingKey(K key);
//返回第一个key大于参数的Entry
Map.Entry<K,V> higherEntry(K key);
//返回第一个key大于参数的key
K higherKey(K key);
//返回key最小的Entry
Map.Entry<K,V> firstEntry();
//返回key最大的Entry
Map.Entry<K,V> lastEntry();
//删除并返回key最小的Entry
Map.Entry<K,V> pollFirstEntry();
//删除并返回key最大的Entry
Map.Entry<K,V> pollLastEntry();
//返回key降序排列的NavigableMap
NavigableMap<K,V> descendingMap();
//返回key升序排列的NavigableSet
NavigableSet<K> navigableKeySet();
//返回key降序排列的NavigableSet
NavigableSet<K> descendingKeySet();
//返回key升序排列的子映射,设置包含标志
NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive);
//按key升序排列,返回子映射,开头到toKey,设置包含标志
NavigableMap<K,V> headMap(K toKey, boolean inclusive);
//按key升序排列,返回子映射,fromKey到末尾,设置包含标志
NavigableMap<K,V> tailMap(K fromKey, boolean inclusive);
//同时也继承了SortedMap的不带包含标志的子映射方法
SortedMap<K,V> subMap(K fromKey, K toKey);
SortedMap<K,V> headMap(K toKey);
SortedMap<K,V> tailMap(K fromKey);
方法 lowerEntry、floorEntry、ceilingEntry 和 higherEntry 分别返回与小于、小于等于、大于等于、大于给定键的键关联的 Map.Entry 对象,如果不存在这样的键,则返回 null。类似地,方法 lowerKey、floorKey、ceilingKey 和 higherKey 只返回关联的键。所有这些方法是为查找条目而不是遍历条目而设计的。
此接口还定义了 firstEntry、pollFirstEntry、lastEntry 和 pollLastEntry 方法,它们返回和/或移除最小和最大的映射关系(如果存在),否则返回 null。
subMap(K, K)、headMap(K) 和 tailMap(K) 方法被指定为返回 SortedMap,以允许现有 SortedMap 实现能相容地改进为实现 NavigableMap,但鼓励此接口的扩展和实现重写这些方法以返回 NavigableMap。类似地,可以重写 SortedMap.keySet() 以返回 NavigableSet。
因此,粗略总结一下它的作用:
使用NavigableMap,除了获取最大值与最小值,我们还可以对任何一个元素,找到比它小的值和比它大的值,还可以按照按照原有的顺序倒序排序等。
5.为什么Hash类容器放入的元素必须覆写hashCode()方法和eaquls()方法?
覆写equals方法之后,可以实现对象的自定义比较,如果不覆写此方法,默认调用Object的equals方法,等同于比较地址值,是否指向同一个内存地址。如果不覆写hashCode方法,而直接调用该方法,得到的哈希值是由系统计算出的,返回的是对象对应的内存地址,返回值和对象属性之间没有直接联系。覆写hashCode方法,可以根据对象属性的不同,返回相应的哈希值。
在java的集合中,判断两个对象是否相等的规则是:
1.首先,判断两个对象的hashCode是否相等
2.如果不相等,认为两个对象也不相等
3.如果相等,则判断两个对象用equals运算是否相等
4.如果不相等,认为两个对象也不相等
5.如果相等,认为两个对象相等
因此,equals相等的两个对象,hashcode一定相等;equals不相等的两个对象,却并不能证明他们的hashcode不相等。如果不覆写,放入两个新的对象,可能会是不相等的.可能是不同的key,产生了多条记录。