1、HashMap基本特点2、HashMap实现方法3.Hash的底层实现
3.1 底层数据结构3.2 jdk1.8之前HashMap存在的问题3.3 Hash包含的重要变量 4.put方法分析5.常见问题
5.1加载因子为什么默认值为0.75f?5.2HashMap如何实现序列化和反序列化(io)5.3 HashMap和Hashtable的区别5.4 HashMap和ConcurrentHashMap区别5.5 HashMap 的长度为什么是 2 的幂次方5.6 如何决定使用HashMap还是TreeMap? 1、HashMap基本特点 HashMap实现了Map接口。HashMap允许有null键和null值。HashMap键不能重复,null键会按照普通键对待。如果键重复,会按照覆盖重复的键。HashMap是无序的。Map接口提供三种collection视图,允许以键集,值集或者键-值映射关系集的形式查看某个映射的内容。映射的顺序定于迭代器在映射的collection视图上返回其元素的顺序。某些映射实现可明确保证其顺序,如TreeMap类。另一些映射实现则不保证顺序,如HashMap。 2、HashMap实现方法 方法 说明 Object put(Object key,Object value)将互相关联的一组键/值对放入该映像Object remove(Object key)从映射中删除与key相关的映射void clear()从映像中删除所有映射Object get(Object key)获得与关键字key相关的值boolean containsKey(Object Key)判断映像中是否存在关键字keyint size()返回当前映像中映射的数量boolean isEmpty()判断映像是否为空Set keySet()返回映像中所有关键字的视图集Collection values()返回映像中所有值的视图集Set entrySet()返回Map.Entry对象的视图集,即印象中的关键字/值对3.Hash的底层实现
基于哈希表的Map接口实现。此实现提供所有可选的映射操作,并允许使用null值和null键。(除了非同步和允许使用的null之外,HashMap类与HashTable大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
3.1 底层数据结构 HashMap底层采用哈希表结构(数组+链表+红黑树)实现,结合了数组和链表的优点:
1、数组优点:通过数组下标可以快速实现对数组元素的访问,效率极高。
2、链表优点:插入或删除数据不需要移动元素,只需要修改节点引用,效率极高。
HashMap内部使用数组存储数据,数组中的每个元素类型为Node
static class Node
Node包含了四个字段:hash,key,value,next,其中next表示链表的下一个节点。
3.2 jdk1.8之前HashMap存在的问题HashMap通过Hash方法计算key的哈希码,然后通过(n-1)&hash公式(n为数组长度)得到key在数组中存放的下标。当两个key在数组中存放的下标一致时,数组将以链表的方式存储(哈希碰撞)。在链表中查找数据必须从第一个元素开始一层一层往下找,知道找到为止,所以当链表长度越来越长时,HashMap的效率越来越低。
解决思想:
加入红黑树的结构来实现HashMap。当链表中的元素超过8个,并且数组长度大于64时,会将链表转换为红黑树。
红黑树的节点使用TreeNode表示:
static final class TreeNode
//默认数组初始化长度为16static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16//数组最大容量,2的30次幂,即1073741824 static final int MAXIMUM_CAPACITY = 1 << 30; //默认加载因子 static final float DEFAULT_LOAD_FACTOR = 0.75f; //链表转换为红黑树的长度阈值 static final int TREEIFY_THRESHOLD = 8; //红黑树转换为链表的长度阈值 static final int UNTREEIFY_THRESHOLD = 6; //链表转换为红黑树时,数组容量必须大于等于64 static final int MIN_TREEIFY_CAPACITY = 64; //HashMap里键值对的个数 transient int size; //扩容阈值:数组容量*加载因子 int threshold; //HashMap使用数组存放数据,数组元素类型为Node
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node
5.常见问题 5.1加载因子为什么默认值为0.75f?put操作过程总结:
1.判断HashMap数组是否为空,是的话初始化数组(由此可见,在创建HashMap对象时,不会直接初始化数组)
2.通过(n-1)&hash计算key在数组中的存放索引
3.如果目标索引为空的话,则创建node对象存储
4.如果目标索引不为空,则分为下面3中情况
(1)key值相同,则覆盖原索引
(2)该节点时红黑树的话,执行红黑树插入操作
(3) 该节点类型是链表的话,遍历到最后一个元素尾部插入,如果中间遇到key相同的,则直接覆盖。如果链表长度大于等于8,数组长度大于等于64,则将链表转换为红黑树结构。
5.判断HashMap元素个数是否大于threshold(数组容量*加载因子),是的话,进行扩容操作(扩大2倍)
扩容这个过程非常耗时,会影响程序性能。所以加载因子是基于容量和性能之间平衡的结果。
当加载因子过大时,扩容的占用就回贬低,但是哈希碰撞的几率增加,效率下降。
当加载因子过小时,容量占用变大。
用于存储数据的table字段都使用transient修饰,通过transient修饰的字段在序列化时将被排除在外,那么HashMap在序列化后进行反序列化时,通过自定义方法来序列化和反序列化操作。
1.table一般不会存满,序列化table未使用的部分浪费时间和空间
2.同一个记住你支队在不同jvm下,在table存储的位置可能不同,那么反序列化时可能出错
同:HashMap 和 HashTable 都是基于哈希表实现的,其内部每个元素都是 key-value 键值对,HashMap 和 HashTable 都实现了 Map、Cloneable、Serializable 接口。
不同:
1.父类不同:HashMap 继承了 AbstractMap 类,而 HashTable 继承了 Dictionary 类。
2.空值不同:HashMap 允许空的 key 和 value 值,HashTable 不允许空的 key 和 value 值。HashMap 会把 Null key 当做普通的 key 对待。不允许 null key 重复。
3.安全性:HashMap线程不安全,HashTable线程安全。
4.性能:HashTable由于加了 synchronized 锁,操作较为耗时。
5.初始容量不同:HashTable 的初始长度是11,之后每次扩充容量变为之前的 2n+1(n为上一次的长度),而 HashMap 的初始长度为16,之后每次扩充变为原来的两倍。创建时,如果给定了容量初始值,那么HashTable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小。
HashMap线程不安全,ConcurrentHashMap是线程安全的。
5.5 HashMap 的长度为什么是 2 的幂次方当数组长度不为2 的幂次方时,hash&(n-1)时,会产生大量重复数据,这样会增大 HashMap 碰撞的几率。
5.6 如何决定使用HashMap还是TreeMap?如果你需要得到一个有序的结果时就应该使用TreeMap(因为HashMap中元素的排列顺序是不固定的)。除此之外,由于HashMap有更好的性能,所以大多不需要排序的时候我们会使用HashMap。