一、HashMap的属性和方法
HashMap和HashTable相似,但HashMap是线程不安全的并且允许null
将HashMap转为线程安全的Map:
Map m = Collections.synchronizedMap(new HashMap(...));
类中的属性:
- DEFAULT_INITIAL_CAPACITY:默认容量16
- DEFAULT_LOAD_FACTOR:默认装填因子0.75
- table:Node<K, V>数组
- TREEIFY_THRESHOLD:8,在桶中用树替换链表的阈值
- UNTREEIFY_THRESHOLD:6,在桶中将树转换回链表的阈值
类中的方法:
- Hashmap:可以传入初始容量值和装填因子
- clear
- clone:浅复制
- entrySet
- get
- isEmpty
- put
- remove
- replace
- values:返回value的视图,可以删除,不能添加
二、使用HashMap时发现的一个小疑问
一开始我认为是在put操作中会创建entrySet以及往entrySet中添加元素,为了弄清楚entrySet是怎么创建及如何遍历的,于是我跟踪Map中entrySet属性值,我打了断点进行debug,然而在put方法中并没有对entry进行赋值以及添加元素的操作,于是我用反射机制来观察entrySet的值
HashMap<Integer, Integer> map = new HashMap<>();
Class mapClass = HashMap.class;
Field entrySet = mapClass.getDeclaredField("entrySet");
entrySet.setAccessible(true);
System.out.println(entrySet.get(map)); // null
map.put(1, 1);
System.out.println(entrySet.get(map)); // null
map.entrySet();
System.out.println(entrySet.get(map)); // [1=1]
显然在调用entrySet方法前,该属性为空值
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}
调用方法后才新建一个entrySet,那么为什么它能遍历HashMap中的元素呢?
final class EntrySet extends AbstractSet<Map.Entry<K,V>>
首先它继承了AbstractSet抽象类
public final Iterator<Map.Entry<K,V>> iterator() {
return new EntryIterator();
}
然后实现了iterator方法,在遍历时返回的这个EntryIterator类,遍历方式为获取该HashMap中table数组的引用,并用current和next变量记录数组中元素的位置来完成遍历。
同理,在keySet中也是创建一个新的KeySet,实现了一个返回KeyIterator的iterator方法,该Iterator只是将EntrySet的key返回,其他方法一样。
三、HashMap和HashTable的区别
- HashMap是线程不安全的,而HashTable是线程安全的,因此HashMap的效率高,而需要在多线程中使用哈希表结构时,应使用ConcurrentHashMap而不是HashTable
- HashMap支持null值的key和value,而HashTable不支持,直接抛出NullPointerException
- HashMap底层是用数组+链表+红黑树,HashTable底层是数组+链表
- ①创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。②创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小
四、其他值得注意的点
- JDK1.8 之前 HashMap 由 数组+链表 组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)
- 计算key的hash值:
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
- 计算该hash值在数组中的位置:(n为数组长度)
(n - 1) & hash
- table数组的长度在扩容(resize)后长度总是2的倍数,该操作很耗时间,尽量避免扩容