集合框架 — HashMap

关于Map、Set和List的关系,有个说法很形象

  • 把 Map里所有的 key 放一起就组成了一个 Set 集合(无序、不重复),keySet()
  • 把 Map 里所有的 value 放一起就组成了一个 List (可以重复,通过索引查找),values()
Map常用方法:

clear():删除Map对象所有的 key-value 对

containsKey(Object key):查询 Map 中是否有指定的 key

get(Object key):返回指定 key 对应的 value(如果没有该 key,返回 null,注意也有可能值为 null)

put(Object key, Object value):添加一个 key-value 对,存在则覆盖

pubAll(Map m):将指定 Map 中的 key-value 对复制到本 Map中

remove(Object key):删除指定 key 对应的 key-value 对

entrySet():返回由 Map 中所有Map.Entry组成的 Set集合

keySet():返回该 Map 中所有 key 组成的 Set 集合

values():返回该 Map 中所有 value 组成的 Conlection

size():返回 Map 里 key-value 的个数

一、HashMap的使用

底层由数组+链表组成,数组里存放key-Value这样的实例(也就是Node,jdk1.7叫Entry),初始所有的位置都为null,赋值后每个节点中如上图所示,都会保存自身的hashkeyvalue和下一个节点next,源码如下

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
}

HashMap类的几个重要字段:

transient int size; // 数组key-value的个数
transient int modCount; // 记录内部结构变化次数,用于迭代的快速失败
int threshold; // 容纳最大键值对数量
final float loadFactor; // 负载因子

HashMap初始值:

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //初始容量
static final int MAXIMUM_CAPACITY = 1 << 30; //最大容量(1<<30)
static final float DEFAULT_LOAD_FACTOR = 0.75f; //负载因子
static final int TREEIFY_THRESHOLD = 8; //树化阈值
static final int UNTREEIFY_THRESHOLD = 6; //反树化阈值
static final int MIN_TREEIFY_CAPACITY = 64; //最小树容量(小于该值则扩容,而不是树化)

添加数据的时候,根据key的hash和数组长度length通过高位运算和取模运算得出 index 值,也就是均匀的放在数组的哪个位置,在该数组位置存储数据,jdk1.7采用头插法,现在采用尾插法放在链表最后(jdk1.8时,链表长度超过8,链表转红黑树存储)

如果数组键值对数量超出threshold,数组进行2倍扩容,最大容量为1<<30,达到了就不再扩容

二、HashMap注意点
1、jdk1.8采用尾插法的原因

防止多线程情况下,扩容时因为改变节点引用关系造成环形链表(死循环)

  • 因为头插法中,next指向下一个节点,当扩容重新计算索引位置时,可能会出现next相互指向

  • 使用尾插法时,扩容会保持链表原本的顺序,不会出现链表成环问题

2、jdk1.8转红黑树

红黑树具有比链表更高的效率,查找时间复杂度为O(logn),链表为O(n)

  • key-value个数大于64(小于则进行扩容),且链表长度大于8时,由链表转为红黑树,红黑树key-vale小于6时,重新转为链表
3、解决哈希冲突,初始值是16,扩容也为2倍

使数组length为2的幂,可以直接用位运算,效率更高,分布均匀

  • 解决哈希冲突,可以把hash值对数组长度取模运算,但是模运算的消耗还是比较大

  • 在HashMap中,当数组length总是2的n次方时,h&(length-1)运算等价于对length取模(length-1)的值二进制都是1,&运算就等于HashCode后几位的值,实现了均匀分布),也就是在数组的位置,但是&%具有更高的效率

  • 高16位异或低16位可以在数组的length比较小的时候,也能保证高位与低位的特征,减少冲突的概率,同时不会有太大的开销。

static final int hash(Object key) {
	// h = key.hashCode() 为第一步 取hashCode值
	// h ^ (h >>> 16)  为第二步 高位低位异或运算
	int h;
	return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

//jdk1.7的源码,jdk1.8没有这个方法,但是实现原理一样的
static int indexFor(int h, int length) {  
    //第三步 取模运算
     return h & (length-1);  
}
4、new HashMap时,为什么可以赋值不是2的幂

不管赋值是多少,该算法会使用比参数大,且最小的2的幂

static final int tableSizeFor(int cap) {
    // cap-1防止已经是2的幂
	int n = cap - 1;
	n |= n >>> 1;
	n |= n >>> 2;
	n |= n >>> 4;
	n |= n >>> 8;
	n |= n >>> 16;
	return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

网上示意图:

5、扩容时,不是直接复制过去,而是重新Hash

Hash的算法:

static int indexFor(int h, int length) {  
	return hashCode(key) & (length-1);  
}

原先位运算出来的值,当扩容后,再次用位运算,位置就不一样了

6、安全问题(modCount字段)

多线程中不安全,方法都没有加同步锁,多线程使用ConcurrentHashMap

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值