HashMap

一、HashMap的属性和方法

HashMap和HashTable相似,但HashMap是线程不安全的并且允许null

将HashMap转为线程安全的Map:

Map m = Collections.synchronizedMap(new HashMap(...));

类中的属性:

  1. DEFAULT_INITIAL_CAPACITY:默认容量16
  2. DEFAULT_LOAD_FACTOR:默认装填因子0.75
  3. table:Node<K, V>数组
  4. TREEIFY_THRESHOLD:8,在桶中用树替换链表的阈值
  5. UNTREEIFY_THRESHOLD:6,在桶中将树转换回链表的阈值

类中的方法:

  1. Hashmap:可以传入初始容量值和装填因子
  2. clear
  3. clone:浅复制
  4. entrySet
  5. get
  6. isEmpty
  7. put
  8. remove
  9. replace
  10. 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的区别

  1. HashMap是线程不安全的,而HashTable是线程安全的,因此HashMap的效率高,而需要在多线程中使用哈希表结构时,应使用ConcurrentHashMap而不是HashTable
  2. HashMap支持null值的key和value,而HashTable不支持,直接抛出NullPointerException
  3. HashMap底层是用数组+链表+红黑树,HashTable底层是数组+链表
  4. ①创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。②创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小

四、其他值得注意的点

  1. JDK1.8 之前 HashMap 由 数组+链表 组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)
  2. 计算key的hash值:
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  3. 计算该hash值在数组中的位置:(n为数组长度)
    (n - 1) & hash
  4. table数组的长度在扩容(resize)后长度总是2的倍数,该操作很耗时间,尽量避免扩容
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值