hashMap--put(k,v)源码分析
来源:互联网 发布:mac srt 字幕乱码 编辑:程序博客网 时间:2024/06/05 14:32
1. hashMap中的注释:
Hash table是基于Map接口的实现。这个版本的实现提供了所有map操作的实现并且允许null值和null键除了允许空值(null)和不支持同步,HashMap和hashtable没有什么区别HashMap不保证有序,尤其是不保证顺序随时间不变该实现提供常数时间的get、put操作,假设hash函数使元素均匀分布在bucket中。一个HashMap实例有两个参数会影响性能:初始容量(initial capacity)和负载因子(load factor)capacity是hashtable的桶数目,初始容量仅仅只是创建时的容量负载因子是衡量哈希表自动增长前装满的程度当哈希表中键值对的数目超出容量与负载因子之积,哈希表就会重新哈希(rehashed)(即,内部数据结构会重建)为之前容量的两倍通常,默认的负载因子0.75可以在时间和空间之间有很好的权衡,想避免重新hash的时间开销,可以设置比较大的初始容量太多关键字的hashCode()相同也会导致性能下降,HashMap实现使用关键字之间的比较顺序来平衡要特别注意HashMap实现不支持同步。如果有多个线程同步访问一个HashMap,并且一个以上的线程会修改HashMap结构,则需要外部同步;(结构修改是指添加或移除键值对,仅仅修改value值不是结构修改操作)如果没有这样的对象,map需要由Collections.synchronizedMap()方法包装 ,而且最好在Map创建时完成,以防意外的非同步访问非同步访问map:<pre>Map m = Collections.synchronizedMap(new HashMap(...));</preHashMap类所有集合视图返回的迭代器都是“fail-fast”:迭代器创建之后修改map结构,除了使用迭代器自己的remove方法,其他都会抛出ConcurrentModificationException异常但这种行为也不能保证同步不出错2.来看一个put()方法的完整实现
public static void main(String[] args) {//Map map=new HashMap();map.put("1","1");}new HashMap();HashMap的构造函数: 默认了0.75 的负载因子
public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted}put()方法:key和value一起包装成一个Node
public V put(K key, V value) {return putVal(hash(key), key, value, false, true);}hash函数:是一个int类型的返回
static final int hash(Object key) {int h;//null 键的hash值为0return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}//(h = key.hashCode()) ^ (h >>> 16):再次异或运算,避免hash冲突的设计hashCode()函数:
public int hashCode() {int h = hash;//默认是0if (h == 0 && value.length > 0) {char val[] = value;//按字符拆分,累计hash值for (int i = 0; i < value.length; i++) {h = 31h + val[i];}hash = h;}return h;}31h + val[i]公式可以理解为:
s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]如字符串:"yuan".hashCode();的计算步骤:// 第一步 = (int)'y'// 第二步 = (31(int)'y') + (int)'u'// 第三步 = 31((31(int)'y') + (int)'u') + (int)'a'// 第四步 = 31(31((31(int)'y') + (int)'u') + (int)'a') + (int)'n'使用31的原因如下:
1.31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终的出来的结果只能被素数本身和被乘数还有1来整除!(减少冲突)2.31可以 由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化.(提高算法效率)3.选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高。(减少冲突)4.并且31只占用5bits,相乘造成数据溢出的概率较小。putVal方法的源码分析:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {//tab是各链表的头节点(或者是红黑树根节点)组成的数组;Node<K,V>[] tab; Node<K,V> p; int n, i;//如果当前tab对象为null或者它内部没有任何元素,那么resize()分配内存空间,resize()完成后将元素数量赋值给n,并且将对象的Node数组赋值给tab了if ((tab = table) == null || (n = tab.length) == 0)//这里分配重新分配空间后的大小n = (tab = resize()).length;//大小n-1 表示下标地址和hash值求与,判断数组中是否已经存在, 如果没有就new一个Node,放在p节点上,同时也是链表的头节点if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else {//链表表头节点存在话//判断是否需要覆盖当前节点,或者创建新的节点Node<K,V> e; K k;//判断该位置上的Node的hash值和key,是否和传入的参数一致if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))//hash值和key值一致,那么直接将这个元素赋值给e;也就是覆盖原有节点的数据e = p;//不一致,判断这个Node是不是属于TreeNode(红黑树)上的节点,是的话,插入到红黑树中;所以是整个链表都转化为红黑树else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);//如果不是,那么就是说这个位置已经有别的元素了,进行循环对比内部的单向链表;后面的元素继续插入会变成链表的形式加在后面,else {for (int binCount = 0; ; ++binCount) {//binCount表示链表的长度//查询到链表的最后一个节点没有指向的引用,新建一个Node,并将引用存入p.nextif ((e = p.next) == null) {p.next = newNode(hash, key, value, null);// 如果链表的长度大于8,那么就需要转化成红黑树//static final int TREEIFY_THRESHOLD = 8 //下标是7if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st//量大于8转化整个链表为红黑树treeifyBin(tab, hash);break;}//后面的链表中存在一个和传入参数一致的Node,那么获取并赋值if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}//找到这个key对应的Node了,那么对这个Node的value进行覆盖,然后返回它原来的valueif (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}//修改次数增加++modCount;//容量大于容量临界值,则重新分配空间if (++size > threshold)resize();afterNodeInsertion(evict);return null;}newNode(hash, key, value, null);:创建新的节点,传入null,表示该节点后没有下一个节点
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {return new Node<>(hash, key, value, next);}Node<K,V> 是hashMap的一个内部类: 单向链表的设计
static class Node<K,V> implements Map.Entry<K,V> {final int hash;final K key;V value;//下一个节点的引用,也可以看出来是单向链表的概念Node<K,V> next;//构造函数Node(int hash, K key, V value, Node<K,V> next) {this.hash = hash;this.key = key;this.value = value;this.next = next;}public final K getKey() { return key; }public final V getValue() { return value; }public final String toString() { return key + "=" + value; }public final int hashCode() {//key和value同时做hashCode()计算,取异或集合return Objects.hashCode(key) ^ Objects.hashCode(value);}public final V setValue(V newValue) {V oldValue = value;value = newValue;return oldValue;}public final boolean equals(Object o) {if (o == this)return true;if (o instanceof Map.Entry) {Map.Entry<?,?> e = (Map.Entry<?,?>)o;if (Objects.equals(key, e.getKey()) &&Objects.equals(value, e.getValue()))return true;}return false;}}//整个单链表转化为红黑树,并确定根节点是头结点
final void treeifyBin(Node<K,V>[] tab, int hash) {int n, index; Node<K,V> e;//如果当前哈希表为空,或者哈希表中元素的个数小于 进行树形化的阈值(默认为 64),就去新建/扩容if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)resize();else if ((e = tab[index = (n - 1) & hash]) != null) { //大于了64的数组长度//红黑树的头(head)、尾节点(tail)TreeNode<K,V> hd = null, tl = null;//转化节点类型和构建前后链接do {//将当前的节点转换为红黑树中树节点TreeNode<K,V> p = replacementTreeNode(e, null);//尾部节点为空,放在该节点上if (tl == null)hd = p;else {//不为空,该节点前指针,之前前面的节点p.prev = tl;//前面的节点next指向,指向本节点tl.next = p;}//tl = p;} while ((e = e.next) != null);//一个元素是红黑树的头结点,将从该结点链接到的结点组成红黑树if ((tab[index] = hd) != null)hd.treeify(tab);}}//将从该结点链接到的结点组成红黑树
final void treeify(Node<K,V>[] tab) {//默认根节点为nullTreeNode<K,V> root = null;for (TreeNode<K,V> x = this, next; x != null; x = next) {next = (TreeNode<K,V>)x.next;//左右子树默认是nullx.left = x.right = null;if (root == null) {x.parent = null;//默认该节点没有父亲节点x.red = false;//节点设置为黑色root = x;//该节点为根节点}else {//keyK k = x.key;//hash值int h = x.hash;Class<?> kc = null;for (TreeNode<K,V> p = root;;) {int dir, ph;K pk = p.key;if ((ph = p.hash) > h)dir = -1;else if (ph < h)dir = 1;else if ((kc == null &&(kc = comparableClassFor(k)) == null) ||(dir = compareComparables(kc, k, pk)) == 0)dir = tieBreakOrder(k, pk);TreeNode<K,V> xp = p;//判断放在哪个子树上,如果子树为空,再处理if ((p = (dir <= 0) ? p.left : p.right) == null) {x.parent = xp;if (dir <= 0)xp.left = x;elsexp.right = x;//数据插入后平衡一下红黑树的结构root = balanceInsertion(root, x);break;}}}}//确保给定根节点是当前容器中的第一个结点moveRootToFront(tab, root);}//确定根节点为首地址节点
static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) {int n;if (root != null && tab != null && (n = tab.length) > 0) {//索引位置int index = (n - 1) & root.hash;//通过索引位置获取首节点TreeNode<K,V> first = (TreeNode<K,V>)tab[index];//判断root不是首节点,则转化root节点为首节点if (root != first) {Node<K,V> rn;//赋给首节点tab[index] = root;//处理引用指向TreeNode<K,V> rp = root.prev;if ((rn = root.next) != null)((TreeNode<K,V>)rn).prev = rp;if (rp != null)rp.next = rn;if (first != null)first.prev = root;root.next = first;//根节点的前部分设为空root.prev = null;}assert checkInvariants(root);}}
阅读全文
0 0
- hashMap--put(k,v)源码分析
- 【源码分析】HashMap的put(K k,V v)方法
- HashMap.put(K key, V value)源码分析
- TreeMap put(K key,V value)源码分析
- HashMap put(K key, V value)解析
- JDK1.8 HashMap中put源码分析
- HashMap源码分析(四)put-jdk8-红黑树的引入
- jdk1.8 HashMap源码分析(put函数)
- HashMap<K, V>泛型类
- HashMap<K, V>泛型类
- HashMap<K, V>泛型类
- HashMap<K, V>泛型类
- HashMap<K,V>详解
- HashMap的put源码解析
- java.util.HashMap<K,V>
- java hashmap的put函数实现源码
- HashMap源码解析——put方法
- HashMap之put方法源码解析
- redis在spring中的配置及java代码实现
- [算法分析与设计] leetcode 每周一题: Copy List with Random Pointer
- URL与URI
- 关于c语言头文件的编写
- 堆和堆排序
- hashMap--put(k,v)源码分析
- 心急的c小加
- iptables配置
- 【OGRE】Ogre for vs2012安装配置
- git使用总结
- c# 发邮件时发送会议邀请icalendar包括更新以及取消
- 微服务要素-十二要素(The Twelve Factors)
- Hibernate进阶一级缓存
- Yii2框架使用redis: yii2本地添加redis扩展及其使用