Java集合:HashMap
一、集合概览
1. 集合的数据结构有Map/List/Set
2. 集合继承关系
二、HashMap定义:
1. Hash表(key-value),根据key(hash code)找到对应的value;会有hash冲突
2. 是基于Hash表的Map实现,Map即key-value的接口
public class HashMap<K,V> extends AbstractMap<K,V> // 继承AbstractMap抽象类,这个抽象类提供了Map接口的最主要功能的实现 implements Map<K,V>, Cloneable, Serializable // 实现Map接口,这个接口定义了键映射到值的规则
三、 装载因子:
1. 装载因子表示hash表中元素的填满程度,装载因子越大则空间利用率高,冲突机会也大,查找耗时也越大
2. HashMap容量:
a. HashMap默认最大容量16,默认装载因子0.75,即阈值12
b. 容量即数据结构中数组的大小
c. 一旦大于等于阈值,2倍扩大空间,最大容量2^30
d. 扩大空间是指创建一个新的2倍大的数组,并把原来的对象放入新的数组中,这个过程叫rehashing
3. 扩容会损耗性能,如果能预估HashMap中元素个数,预设个数能提高性能
HashMap():默认初始容量(16)和加载因子(0.75)
HashMap(int initialCapacity):可以自定义初始容量,预估个数能提高性能,因为动态扩容会损耗性能
HashMap(int initialCapacity, float loadFacotor):可以自定义初始容量和加载因子
4. 扩容会存在条件竞争,多线程下扩容,会造成死锁,因此HashMap不是线程安全的
四、数据结构
1. HashMap是由数组和链表组合实现的,一个数组的值加上其链表,叫做一个散列桶
2. 数组初始容量为initalCapacity
3. 链表的每一项为Entry,Entry为HashMap的内部类,包含4个字段:key(键),value(值),next(下一节点),hash(hash值)
static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; final int hash; /** * Creates new entry. */ Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } ....... }
五、存储实现过程:put(key,value)
1. key可以为null,当key为null时,调用putForNullKey方法
2. 通过key的hashCode()方法计算key的hash值,根据hash值确认在table数组中的索引位置
3. 如果table中没有元素,直接插入
4. 否则遍历Entry,通过equals()方法比较key,如果不同则插入链表头,调用addEntry方法;否则替换掉旧值,所以没有相同的key
5. 如果链表长度超过了阈值8,就把链表转成红黑树,链表长度低于6,就把红黑树转回链表;
a. 使用红黑树,因为红黑树是平衡二叉树
b. 不使用二叉树,因为二叉树在极端的情况下,会变成一条线性结构
6. 如果数组容量超过了装载因子,就需要扩容
public V put(K key, V value) { //当key为null,调用putForNullKey方法,保存null与table第一个位置中,这是HashMap允许为null的原因 if (key == null) return putForNullKey(value); //计算key的hash值 int hash = hash(key.hashCode()); ------(1) //计算key hash 值在 table 数组中的位置 int i = indexFor(hash, table.length); ------(2) //从i出开始迭代 e,找到 key 保存的位置 for (Entry<K, V> e = table[i]; e != null; e = e.next) { Object k; //判断该条链上是否有hash值相同的(key相同) //若存在相同,则直接覆盖value,返回旧value if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; //旧值 = 新值 e.value = value; e.recordAccess(this); return oldValue; //返回旧值 } } //修改次数增加1 modCount++; //将key、value添加至i位置处 addEntry(hash, key, value, i); return null; }
六、读取实现过程:get(key)
1. 根据key的hash值找到Entry
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
2. 通过keys.equals()方法找到链表中正确的位置
七、如何减少碰撞
1. 使用扰动函数,使不相等的对象返回不同的hashcode
2. 使用包装类(例如Integer, String)作为key,String是不可变的,作为key,键值就是不变的,可以缓存其hashcode
八、 HashMap和HashTable的区别(几乎可以等价,HashTable已经过时,不推荐使用):都实现了Map接口
1. HashMap不是同步(synchronized)的,所以不是线程安全的,即多线程环境下可能有问题
a. 可以用ConcurrentHashMap解决线程安全问题
b. 用Collections.synchronizeMap(hashMap)方法解决线程安全问题
2. HashTable是同步的,是线程安全的,但在单线程比较慢,同步会加同步锁,意味着一次仅有一个线程能够更新对象
3. HashMap可以接受key为null,而HashTable不行
4. HashMap的迭代器(Iterator)是fail-fast迭代器,可能在多线程环境下更改结构时出ConcurrentModificationException异常;而HashTable的迭代器(Enumerator)不是
5. HashMap不能保证元素的排列次序,所以次序可能会变!!!可以使用LinkedHashMap代替
九、使用线程安全的集合类:ConcurrentHashMap和CopyOnWriteArrayList
1. 同步的集合类(HashTable和Vector),同步的封装类(使用Collections.synchronizeMap()和Collections.synchronizeList()方法返回的对象)是线程安全的
2. 但是不适合高并发的系统,它们仅有单个同步锁,并且对整个集合加锁,以及为了防止ConcurrentModificationException异常经常在迭代的时候将集合锁定一段时间
十、ConcurrentHashMap和HashTable的区别
a. HashTable的大小增加到一定的时候,性能会急剧下降,因为迭代的时候会对整个对象锁定很长时间
b. ConcurrentHashMap引入了分割(Segmentation),在迭代的过程中,仅仅锁定map的某个部分,而HashTable是锁住整个Map,因此比HashTable性能好
十一、HashMap与HashSet的区别,存储不同数据类型
1. HashMap实现了Map接口,存储键值对,不允许有重复的Key;HashSet实现了Set接口,存储对象,不允许有重复的值
2. 添加元素:HashMap: put(Object key, Object value); HashSet: add()
原文地址:https://www.cnblogs.com/june0816/p/7295718.html
- UITabBarController实现Tab切换
- React Native库版本升级与降级
- Java并发学习之Volatile及内存模型探究
- Java并发学习之CountDownLatch实现原理及使用姿势
- Linux基础(day58)
- 携程Android App插件化和动态加载实践
- 15.5 使用pure-ftpd搭建ftp服务
- JDK容器学习之Queue: ArrayBlockingQueue
- 手机APP安装包缩减方案
- react-native添加redux支持
- Java并发学习之ReentrantLock的工作原理及使用姿势
- Linux基础(day63)
- IOS WebView控件详解
- Java并发学习之synchronized使用小结
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- [漫谈] 状态
- 变量覆盖
- 【Rust日报】2020-08-27 在Arduino Uno上面跑 Rust 程序
- 浅谈类加载
- Rust FFI 编程 - Rust导出共享库06
- go语言文件操作汇总
- Redis 主从复制 哨兵模式实战
- 【Rust日报】2020-08-28 Rust 1.46稳定版发布
- go语言反射
- 和同事谈谈Flood Fill 算法
- 【每周一库】 img_hash,rust下的pHash算法库
- 【Rust日报】2020-08-29 生产环境 Rust 序列化库的选择
- 【投稿】刀哥:Rust学习笔记 5
- Python测试开发django3.视图和URL配置
- 【Rust日报】2020-08-31 easy_rust 正式完成了