总结:HashMap和Hashtable的底层实现: 数组+链表结构;Put碰撞时覆盖与否;
原因:做出一种寻址容易,插入删除也容易的数据结构
使用hahmap对数组中的元素计数:--->map.keyset.contains
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for (int i = 0; i < arr.length; i++) {
if (map.keySet().contains(arr[i])) {
map.put(arr[i], map.get(arr[i]) + 1);
} else {
map.put(arr[i], 1);
}
}
HashMap
分析源码:
HashMap 存储 key value
底层的数据结构:数组链表
HashMap源码
1.HashMap底层数据结构如何?
2.HashMap如何保存数据,增删改咋实现?
3.HashMap如何扩容?
4.为什么扩容的大小一定要是2的整数次幂,也就是2的N次方.
截取部分源码:
1.
继承自AbstractMap 并实现了接口
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
2.
0001 左移4位 0001 0000 = 16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
static final int MAXIMUM_CAPACITY = 1 << 30;
3.
来说说HashMap的实现
HashMap基于==哈希表==,底层结构由数组来实现,添加到集合中的元素以“key—value”形式保存到数组中,在数组中key—value被包装成一个实体来处理—-也就是上面Map接口中的Entry
哈希冲突:通过链表的形式来解决
HashMap继承于AbstractMap,实现了Map, Cloneable, Serializable接口。
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
特点:
- 允许存入null键,null值(null值只有一个,并存于数组第一个位置)
- ==无序集合==,而且顺序会随着元素的添加而随时改变(添加顺序,迭代顺序不一致)
- 随着元素的增加而动态扩容(与ArrayList原理一致)
- 不存在重复元素(得益于hashCode算法和equals方法)
- 线程不安全
HashMap的最终实现:
在HashMap中,存储元素的是==Entry[]数组==,而其中的元素也就是Entry对象:
HashMap的hash方法原理
参考文章:
https://www.zhihu.com/question/20733617
Put
- 第一个键值对A进来,通过计算其key的hash得到的index=0,记做:Entry[0] = A。
- 一会后又进来一个键值对B,通过计算其index也等于0,现在怎么办?HashMap会这样做:B.next = A,Entry[0] = B,如果又进来C,index也等于0,那么C.next = B,Entry[0] = C;
- 这样我们发现index=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起。
- 也就是说数组中存储的是最后插入的元素。
Put过程的覆盖问题
良心贴:https://www.cnblogs.com/yangqiangyu/p/5276629.html
Put
graph LR
key生成的hashcode产生的index-->之前的index位置已经有Entry则碰撞
之前的index位置已经有Entry则碰撞-->所以遍历Entry如果key的value和hashcode都相同就直接替换
之前的index位置已经有Entry则碰撞-->key的value和hashcode不相同没有重复的话就addEntry
key生成的hashcode产生的index-->之前的index位置没有Entry则addEntry
碰撞时覆盖!
碰撞时不覆盖!
碰撞时的替换源码
主要是这行代码:if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
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;
}
容量(Capacity)和负载因子(Load factor)
Capacity就是buckets的数目
当bucket填充的数目(即hashmap中元素的个数)大于capacity*load factor时就需要调整buckets的数目为当前的2倍。
Load factor就是buckets填满程度的最大比例
加载因子越大,填满的元素越多,好处是,空间利用率高了,但:冲突的机会加大了.链表长度会越来越长,查找效率降低。
加载因子越小,填满的元素越少,好处是:冲突的机会减小了,但:空间浪费多了.表中的数据将过于稀疏(很多空间还没用,就开始扩容了)冲突的机会越大,则查找的成本越高。
- 默认值0.75
哈希冲突处理
graph LR
哈希冲突处理-->开放定址法
开放定址法-->线性探测再散列
开放定址法-->平方探测再散列
哈希冲突处理-->链地址法
哈希冲突处理-->再哈希
哈希冲突处理-->建立公共溢出区
什么是hash
- 任意长度的二进制通过映射转成一个固定长度的二进制值
- key - value 唯一映射关系
hash表
事件复杂度O(1).