【源码】HashMap源码阅读

相机基础知识讲解:CMOS和CCD 阅读详情

2020-02-02

下面是JDK11中HashMap的源码分析,对代码的分析将主要以注释的方式来体现。

1 概述

1.1 HashMap的主要概念

  1. HashMap是基于Map接口实现的哈希表,实现了Map接口中的所有操作,而且HashMap允许键为空值,也允许值为空值,与之对应的是Hashtable,Hashtable不能将键和值设置为空。HashMap不能保证元素的顺序,特别是,它不能保证随着时间的推移保持顺序不变。

  2. HashMap为基本操作(get和put)提供了恒定的时间性能,假设散列函数在木桶(buckets)中适当地分散了元素。集合(Collection)的迭代需要的时间与HashMap实例的“容量”和它的大小成比例。因此,如果迭代的性能很重要,要求很高,那么不将初始容量设置得太高(或负载因素过低)是非常重要的。

  3. HashMap是线程不安全的集合,即当多线程访问时,同一时刻如果无法保证只有一个线程修改HashMap,则会毁坏HashMap,抛出ConcurrentModificationException。

1.2 HashMap的基本实现

HashMap底层使用哈希表(数组 + 单链表),当链表过长会将链表转成红黑树以实现O(logn)时间复杂度内查找。
HashMap的定义为class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable, Serializable

1.3 HashMap内部类

1.Node
2.KeySet
3.Values
4.EntrySet
5.HashIterator
6.KeyIterator
7.ValueIterator
8.EntryIterator
9.HashMapSpliterator
10.KeySpliterator
11.ValueSpliterator
12.EntrySpliterator
13.TreeNode 代表红黑树节点,HashMap中对红黑树的操作的方法都在此类中

1.4 扩容原理

HashMap采用的扩容策略是,每次加倍的方式。这样,原来位置的Entry在新扩展的数组中要么依然在原来的位置,要么在原来的位置+原来的容量的位置。

1.5 hash计算

HashMap通过hash()函数(也叫“扰动函数”)来计算hash值,方法为key == null ? 0 : (h = key.hashCode()) ^ h >>> 16;,计算出来的hash值存放在Node.hash中。

hash的值计算相当于将高16位与底16位进行异或,结果是高16位不变,底16位变成异或的新结果。为什么这样做呢,原因是HashMap扩容之前的数组大小才为16,散列值是不能直接拿来用的。在进行长度取模运算时采用的只是取二进制中的最右端的几位,并没有用到高位二进制的信息,做带来的结果就是hash结果分布不太均匀。而将高16位和底16位异或后就可以让低位附带高位的信息,加大低位的随机性。

在对散列值做完高低位的异或操作后,在对异或结果进行对长度的取模得到最终的结果。具体参考JDK源码中HashMap的hash方法原理是什么?-胖君的回答-知乎

1.6 插入null原理

在hash计算中,null的hash值为0,然后按照正常的putVal()插入。

1.7 new HashMap()

从源码中(下文构造函数)我们可以看到,new HashMap()开销非常少,仅仅确认装载因子。真正的创建table的操作尽可能的往后延迟,这使得HashMap有不少操作都需要检查table是否初始化。这种设计有一种好处,就是能够不必担心HashMap的开销,可以一次性大量的创建HashMap。

2 HashMap的成员变量

public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable, Serializable {
	//用于序列化
	private static final long serialVersionUID = 362498820763181265L;
	//HashMap的默认容量是16
	static final int DEFAULT_INITIAL_CAPACITY = 16;
	//最大容量为1073741824(2的30次方,即1<<30)
	static final int MAXIMUM_CAPACITY = 1073741824;
	//默认装载因子为0.75f
	static final float DEFAULT_LOAD_FACTOR = 0.75F;
	/*
	将链表转化为红黑树的阈值为8,即当链表长度 >= 8时,链表转化为红黑树,也就是树形化。
	为什么要树形化呢?想一下我们为什么要用HashMap,是因为通过Hash算法在理想情况下时间复杂度O(1)就能找到元素,特别快,但仅限于理想情况下,如果遇到了hash碰撞,且碰撞比较频繁的话,那么当我们get一个元素的时候,定位到了这个数组,还需要在这个数组中遍历一次链表最终才能找到要get的元素,是不是已经失去了hashmap的初心了?(因为需要遍历链表,所以时间复杂度就高上去了)。
	所以使用红黑树这种数据结构来解决链表过长的问题,可以理解为红黑树遍历比链表遍历快,时间复杂度低。
	*/
	static final int TREEIFY_THRESHOLD = 8;
	//将红黑树转化成链表的阈值为6(<6时),这个是在resize()的过程中调用TreeNode.split()实现
	static final int UNTREEIFY_THRESHOLD = 6;
	/*
	最小树形化阈值。要树化并不仅仅是要超过TREEIFY_THRESHOLD,同时容量要超过MIN_TREEIFY_CAPACITY,如果只是超过TREEIFY_THRESHOLD,则会进行扩容(调用resize())。为什么这个时候是扩容而不是树形化呢?
	原因就在于,造成链表过长也可能是数组(桶)太短了也就是容量太小了。举个例子,如果数组长度为1,那么所有的元素都挤在了数组的第0个位置上,这个时候就算树形化只是治标不治本,因为引起链表过长的根本原因是数组过短。
	所以在执行树形化之前(链表长度>=8),会检查数组长度,如果长度小于64,则对数组进行扩容,而不是树形化。
	*/
	static final int MIN_TREEIFY_CAPACITY = 64;
	/*
	哈希表的数组主体定义,初始化时,在构造函数中并不会初始化,所以在各种操作中总是要先检查table是否为null。
	*/
	transient HashMap.Node<K, V>[] table;
	/*
	作为一个entrySet缓存,使用entrySet首先检查其是否为null,不为null则使用这个缓存,否则生成一个entrySet并缓存至此。
	*/
	transient Set<Entry<K, V>> entrySet;
	//HashMap中的Entry的数量
	transient int size;
	/*
	记录修改内部结构化修改次数,用于实现fail-fast,ConcurrentModificationException就是通过检测这个抛出。
	*/
	transient int modCount;
	//其值=capacity*loadFactor,当size超过threshold的时候便进行一次扩容
	int threshold;
	//装载因子
	final float loadFactor;

	……
}

3 HashMap的方法

3.1 构造函数

  1. 该构造函数进行对容量和装载因子的合法性验证,然而没有对容量进行存储,只是用来确定扩容阈值threshold。
public HashMap(int initialCapacity, float loadFactor) {
	if (initialCapacity < 0) {
		throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
	} else {
		if (initialCapacity > 1073741824) {
			initialCapacity = 1073741824;
		}

		if (loadFactor > 0.0F && !Float.isNaN(loadFactor)) {
			this.loadFactor = loadFactor;
			this.threshold = tableSizeFor(initialCapacity);
		} else {
			throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
		}
	}
}
  1. 只传入初始容量
public HashMap(int initialCapacity) {
	this(initialCapacity, 0.75F);
}
  1. 无参构造函数仅仅确认装载因子
public HashMap() {
	this.loadFactor = 0.75F;
}
  1. 通过Map构造HashMap时,使用默认装载因子,并调用putMapEntries将Map装入HashMap
	public HashMap(Map<? extends K, ? extends V> m) {
		this.loadFactor = 0.75F;
		this.putMapEntries(m, false);
	}

	final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
		//获取该map实际长度
	    int s = m.size();
	    if (s > 0) {
	    	//判断table是否初始化,如果没有初始化
	        if (this.table == null) {
	        	/**求出需要的容量,因为实际使用的长度=容量*0.75得来的,+1是因为小数相除,基本都不会是整数,容量大小
				不能为小数的,后面转化为int,多余的小数就要被丢掉,所以+1,例如,map实际长度为29.3,则所需要的容量为30.
	        	*/
	            float ft = (float)s / this.loadFactor + 1.0F;
	            //判断该容量大小是否超出上限
	            int t = ft < 1.07374182E9F ? (int)ft : 1073741824;
	            //对临界值进行初始化,tableSizeFor(t)这个方法会返回大于t值的,且离其最近的2次幂,例如t为29,则返回的值是32
	            if (t > this.threshold) {
	                this.threshold = tableSizeFor(t);
	            }
	        } else if (s > this.threshold) {  //如果table已经初始化,则进行扩容操作,resize就是扩容
	            this.resize();
	        }

	        Iterator var8 = m.entrySet().iterator();

	        //遍历,把map中的数据转移到hashmap中
	        while(var8.hasNext()) {
	            Entry<? extends K, ? extends V> e = (Entry)var8.next();
	            K key = e.getKey();
	            V value = e.getValue();
	            this.putVal(hash(key), key, value, false, evict);
	        }
	    }

	}

该构造函数,传入一个Map,然后把该Map转为hashMap,resize方法在下面添加元素的时候会详细讲解,在上面entrySet方法会返回一个Set<Map.Entry<K,V>>,泛型为Map的内部类Entry,它是一个存放key-value的实例,也就是Map中的每一个key-value就是一个Entry实例,为什么使用这个方式进行遍历,因为效率高,putVal方法把取出来的每个key-value存入到hashMap中。

3.2 hash(Object key)

hash函数负责产生hashcode,计算方法为若为空则返回0,否则返回对key的高16位和底16位的异或的结果。

static final int hash(Object key) {
	int h;
	return key == null ? 0 : (h = key.hashCode()) ^ h >>> 16;
}

3.3 comparableClassFor(Object x)

这个方法是判断传入的Object对象x是否实现了Comparable接口,如果传入的是String对象,自然实现了Comparable接口,直接返回就行。但是对于其他的类,比方说我们自己写了一个类对象,然后存在HashMap中,但是就HashMap来说它并不知道我们有没有实现Comparable接口,甚至都不知道我们Comparable接口中有没有用泛型,泛型具体用的是哪个类。

static Class<?> comparableClassFor(Object x) {
	if (x instanceof Comparable) {
		Class c;
		if ((c = x.getClass()) == String.class) {
			return c;
		}

		ype[] ts;
		if ((ts = c.getGenericInterfaces()) != null) {
			Type[] var5 = ts;
			int var6 = ts.length;

			for(int var7 = 0; var7 < var6; ++var7) {
				Type t = var5[var7];
				Type[] as;
				ParameterizedType p;
				if (t instanceof ParameterizedType && (p = (ParameterizedType)t).getRawType() == Comparable.class && (as = p.getActualTypeArguments()) != null && as.length == 1 && as[0] == c) {
					return c;
				}
			}
		}
	}
	return null;
}

3.4 compareComparables(Class<?> kc, Object k, Object x)

如果x为空,返回0;如果x的类型为kc,则返回compareTo(x)。

static int compareComparables(Class<?> kc, Object k, Object x) {
	return x != null && x.getClass() == kc ? ((Comparable)k).compareTo(x) : 0;
}

3.5 tableSizeFor(int cap)

该函数用于计算大于等于cap的的最小的2的整数幂,用于做table的长度。numberOfLeadingZeros()方法的作用是返回无符号整形i的最高非零位前面的0的个数,包括符号位在内。

static final int tableSizeFor(int cap) {
	int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);
	return n < 0 ? 1 : (n >= 1073741824 ? 1073741824 : n + 1);
}

3.6 put(K key, V value)

	public V put(K key, V value) {
		//四个参数,第一个hash值,第四个参数表示如果该key存在值,如果为null的话,则插入新的value,最后一个参数,在hashMap中没有用,可以不用管。
        return this.putVal(hash(key), key, value, false, true);
    }

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    	//tab哈希数组,p为该哈希桶的首节点,n为hashMap的长度,i为计算出的数组下标
        HashMap.Node[] tab;
        int n;
        //获取长度并扩容,使用的是懒加载,table一开始是没有加载的,等puthou才开始加载
        if ((tab = this.table) == null || (n = tab.length) == 0) {
            n = (tab = this.resize()).length;
        }

        Object p;
        int i;
        //如果计算出的该哈希桶的位置没有值,则把新插入的key-value放到此处,此处就算没有插入成功,也就是发生哈希冲突时也会把哈希桶的首节点赋予p
        if ((p = tab[i = n - 1 & hash]) == null) {
            tab[i] = this.newNode(hash, key, value, (HashMap.Node)null);
        } else {  //发生哈希冲突的几种情况
        	//e临时节点的作用,k存放当前节点的key值
            Object e;
            Object k;
            //第一种,插入的key-value的hash值,key都与当前节点相等,e=p,则表示为首节点
            if (((HashMap.Node)p).hash == hash && ((k = ((HashMap.Node)p).key) == key || key != null && key.equals(k))) {
                e = p;
            }
            //第二种,hash值不等于首节点,判断该p是否属于红黑树的节点
            else if (p instanceof HashMap.TreeNode) {
            	/*
            	为红黑树的节点,则在红黑树中进行添加,如果该节点已经存在,则返回该节点(不为null),
            	该值很重要,用来判断put操作是否成功,如果添加成功返回null
            	*/
                e = ((HashMap.TreeNode)p).putTreeVal(this, tab, hash, key, value);
            }
            //第三种,hash值不等于首节点,不为红黑树节点,则为链表的节点
            else {
            	//遍历该链表
                int binCount = 0;
                while(true) {
                	//如果找到尾部,则表明添加的key-value没有重复,在尾部进行添加。
                    if ((e = ((HashMap.Node)p).next) == null) {
                        ((HashMap.Node)p).next = this.newNode(hash, key, value, (HashMap.Node)null);
                        //判断是否要转化为红黑树结构
                        if (binCount >= 7) {
                            this.treeifyBin(tab, hash);
                        }
                        break;
                    }

                    //如果链表有重复的key,e为当前重复的节点,结束循环。
                    if (((HashMap.Node)e).hash == hash && ((k = ((HashMap.Node)e).key) == key || key != null && key.equals(k))) {
                        break;
                    }

                    p = e;
                    ++binCount;
                }
            }
            //e不为null,则说明有重复的key,则用待插入值进行覆盖,返回旧值。
            if (e != null) {
                V oldValue = ((HashMap.Node)e).value;
                if (!onlyIfAbsent || oldValue == null) {
                    ((HashMap.Node)e).value = value;
                }

                this.afterNodeAccess((HashMap.Node)e);
                return oldValue;
            }
        }

        /*
        到了这步,说明待插入的key-value是没有key的重复,因为插入成功的e节点的值为null。
        修改次数+1
        */
        ++this.modCount;
        //实际长度+1,并判断是否大于临界值,大于则扩容
        if (++this.size > this.threshold) {
            this.resize();
        }

        this.afterNodeInsertion(evict);
        //添加成功
        return null;
    }

3.7 resize()

扩容方法resize()

    final HashMap.Node<K, V>[] resize() {
    	//把没有插入之前的哈希数组叫做oldTab
        HashMap.Node<K, V>[] oldTab = this.table;
        //oldTab的长度
        int oldCap = oldTab == null ? 0 : oldTab.length;
        //oldTab的临界值
        int oldThr = this.threshold;
        //初始化new的长度和临界值
        int newThr = 0;
        int newCap;
        //oldCap>0也就说明不是首次加载,因为hashMap用的是懒加载
        if (oldCap > 0) {
        	//如果大于最大值
            if (oldCap >= 1073741824) {
            	//将临界值设置为整数的最大值
                this.threshold = 2147483647;
                return oldTab;
            }
            //位置*。其他情况,扩容两倍,并且扩容后的长度要小于最大值,old的长度也要大于16
            if ((newCap = oldCap << 1) < 1073741824 && oldCap >= 16) {
            	//临界值也要扩容为old的2倍
                newThr = oldThr << 1;
            }
        }
        /*
        如果oldCap<0,但是已经初始化了,像把元素删除完之后的情况,那么它的临界值肯定还存在,
        如果是首次初始化,它的临界值则为0.
        */
        else if (oldThr > 0) {
            newCap = oldThr;
        }
        //首次初始化,给默认值
        else {
            newCap = 16;
            newThr = 12;  //临界值等于容量*0.75
        }
        //位置*的补充,也就是初始化时容量小于默认值16的,此时newThr没有赋值
        if (newThr == 0) {
        	//new的临界值
            float ft = (float)newCap * this.loadFactor;
            //判断new容量是否大于最大值,临界值是否大于最大值
            newThr = newCap < 1073741824 && ft < 1.07374182E9F ? (int)ft : 2147483647;
        }

        //把上面各种情况分析出的临界值,在此处进行真正的改变,也就是容量和临界值都改变了
        this.threshold = newThr;
        //初始化
        HashMap.Node<K, V>[] newTab = new HashMap.Node[newCap];
        //赋予当前的table
        this.table = newTab;
        //此处自然是把old中的元素,遍历到new中
        if (oldTab != null) {
            for(int j = 0; j < oldCap; ++j) {
            	//临时变量
                HashMap.Node e;
                //当前哈希桶的位置值不为null,也就是数组下标处有值,因为有值表示可能会发生冲突
                if ((e = oldTab[j]) != null) {
                	//把已经赋值之后的变量置位null,当然是为了好回收,释放内存
                    oldTab[j] = null;
                    //如果下标处的节点没有下一个元素
                    if (e.next == null) {
                    	//把该变量的值存入newTab中,e.hash & new Cap-1并不等于j
                        newTab[e.hash & newCap - 1] = e;
                    }
                    //如果该节点为红黑树结构,也就是存在hash冲突,该hash桶中有多个元素
                    else if (e instanceof HashMap.TreeNode) {
                    	//把此树转移到newTab中
                        ((HashMap.TreeNode)e).split(this, newTab, j, oldCap);
                    }
                    /*
                    此处表示为链表结构,同样把链表转移到newTab中,就是把链表遍历后,把值转过去,再置位null
                    */
                    else {
                        HashMap.Node<K, V> loHead = null;
                        HashMap.Node<K, V> loTail = null;
                        HashMap.Node<K, V> hiHead = null;
                        HashMap.Node hiTail = null;

                        HashMap.Node next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null) {
                                    loHead = e;
                                } else {
                                    loTail.next = e;
                                }

                                loTail = e;
                            } else {
                                if (hiTail == null) {
                                    hiHead = e;
                                } else {
                                    hiTail.next = e;
                                }

                                hiTail = e;
                            }

                            e = next;
                        } while(next != null);

                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }

                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        //返回扩容后的hashmap
        return newTab;
    }

3.8 remove(Object key)

删除元素

    public V remove(Object key) {
    	//临时变量
        HashMap.Node e;
        /*
        调用removeNode,第三个value表示,把key的节点直接都删除了,不需要用到值,
        如果设为值,则还需要去进行查找操作。
        */
        return (e = this.removeNode(hash(key), key, (Object)null, false, true)) == null ? null : e.value;
    }

    /*
    第一参数为哈希值,第二个为key,第三个为value,第四个为true的话,则表示删除它
    key对应的value,不删除key,第四个如果为false,则表示删除后,不移动节点。
    */
    final HashMap.Node<K, V> removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable) {
    	//tab哈希数组,p数组下标节点,n长度,index当前数组下标
        HashMap.Node[] tab;
        HashMap.Node p;
        int n;
        int index;
        //哈希数组不为null,且长度大于0,然后获得要删除key的节点的数组下标位置
        if ((tab = this.table) != null && (n = tab.length) > 0 && (p = tab[index = n - 1 & hash]) != null) {
        	//node存储要删除的节点,e临时变量,k当前节点的key,v当前节点的value
            HashMap.Node<K, V> node = null;
            Object k;
            //如果数组下标的节点正好是要删除的节点,把值赋给临时变量
            if (p.hash == hash && ((k = p.key) == key || key != null && key.equals(k))) {
                node = p;
            }
            //也就是要删除的节点,在链表或者红黑树上,先判断是否为红黑树的节点
            else {
                HashMap.Node e;
                if ((e = p.next) != null) {
                    if (p instanceof HashMap.TreeNode) {
                    	//遍历红黑树,找到该节点并返回
                        node = ((HashMap.TreeNode)p).getTreeNode(hash, key);
                    }
                    //如果是链表节点,遍历找到该节点
                    else {
                        label88: {
                            while(e.hash != hash || (k = e.key) != key && (key == null || !key.equals(k))) {
                            	//p为要删除节点的上一个节点
                                p = e;
                                if ((e = e.next) == null) {
                                    break label88;
                                }
                            }

                            //node为要删除的节点
                            node = e;
                        }
                    }
                }
            }

            Object v;
            /*
            找到要删除的节点后,判断!matchValue,我们正常的remove删除,!matchValue都为true
            */
            if (node != null && (!matchValue || (v = ((HashMap.Node)node).value) == value || value != null && value.equals(v))) {
                //如果删除的节点是红黑树节点,则从红黑树中删除
                if (node instanceof HashMap.TreeNode) {
                    ((HashMap.TreeNode)node).removeTreeNode(this, tab, movable);
                }
                //如果是链表节点,且删除的节点为数组下标节点,也就是头节点,直接让下一个作为头。
                else if (node == p) {
                    tab[index] = ((HashMap.Node)node).next;
                }
                //为链表结构,删除的节点在链表中,要把删除的下一个节点设为上一个节点的下一个节点。
                else {
                    p.next = ((HashMap.Node)node).next;
                }

                //修改计数器
                ++this.modCount;
                //长度减1
                --this.size;
                this.afterNodeRemoval((HashMap.Node)node);
                //返回删除的节点
                return (HashMap.Node)node;
            }
        }

        return null;
    }

删除还有clear方法,把所有的数组下标元素都置位null。

3.9 get()

下面在看较为简单的获取元素。

    public V get(Object key) {
        HashMap.Node e;
        //也是调用getNode方法来完成
        return (e = this.getNode(hash(key), key)) == null ? null : e.value;
    }

    final HashMap.Node<K, V> getNode(int hash, Object key) {
    	//tab哈希数组,first头节点,n长度,k为key
        HashMap.Node[] tab;
        HashMap.Node first;
        int n;
        //如果哈希数组不为null,且长度大于0,获取key值所在的链表头赋值给first
        if ((tab = this.table) != null && (n = tab.length) > 0 && (first = tab[n - 1 & hash]) != null) {
            Object k;
            //如果是头节点,则直接返回头节点。
            if (first.hash == hash && ((k = first.key) == key || key != null && key.equals(k))) {
                return first;
            }

            HashMap.Node e;
            //结果不是头节点
            if ((e = first.next) != null) {
            	//判断是否是红黑树结构
                if (first instanceof HashMap.TreeNode) {
                	//去红黑树中找,然后返回
                    return ((HashMap.TreeNode)first).getTreeNode(hash, key);
                }

                //是属于链表节点,遍历链表,找到该节点并返回
                do {
                    if (e.hash == hash && ((k = e.key) == key || key != null && key.equals(k))) {
                        return e;
                    }
                } while((e = e.next) != null);
            }
        }

        return null;
    }

hashMap源码暂时分析到这里,能力有限,如果内容出现错误,欢迎指出。

Pluto SDR新手避坑指南:从环境配置到第一个MATLAB收发程序(附完整代码) 本文为Pluto SDR新手提供全面的避坑指南,从硬件连接到MATLAB环境配置,再到第一个信号收发程序的实现。详细介绍了驱动安装、常见问题排查以及关键参数设置,帮助用户快速上手软件定义无线电(SDR)技术,并附有完整代码示例。 阅读详情

相关推荐

永磁同步电机自抗扰控制ADRC(位置环/速度环控制器)Matlab/Simulink仿真(S-Function)

ESO是ADRC的核心,其作用是跟踪被控对象的输出,同时给出受控对象的各阶状态变量以及系统总扰动的实时估计,将其补偿到控制器中,实现动态补偿线性化。NLSEF采用非线性函数把TD产生的跟踪及微分信号与ESO给出的状态变量估计值之间的误差进行非线性组合得到初级控制量,然后同总扰动的补偿量一起生成最终的控制信号。自抗扰控制(ADRC)技术的核心思想是将简单的积分串联型视为标准模型,把系统模型中不同于标准型的部分和外部干扰之和视为总扰动,并将其扩张为一个新的状态。看作系统内外部扰动之和。令一个高阶状态变量为。

m0_66341377的博客 7615

HASH_AREA内存不足导致job Hung

昨天晚上 我做了一个关于Hash Join的调整 老外说 某个JOB Hung住了,用户名是IDWSQ6 于是我查询等待事件:direct patch write temp SQL&gt; SELECT INST_ID,SID,EVENT,P1,P2,P3,MACHINE FROM GV$SESSION WHERE USERNAME='IDWSQ6'; INST_ID SID...

ruilinruirui 385

利用树莓派制作智能音箱

利用树莓派制作智能音箱

qq_61803022的博客 1777

关于jdk11新特性流操作的研究

此外,组合器函数必须是关联的,并且必须与累加器函数兼容:对于所有 u 和 t,combiner.apply(u, accumulator.apply(identity, t)) 必须等于 accumulator.apply(u, t)。收集操作需要三个函数:一个供应商函数,用于构造结果容器的新实例,一个累加器函数,用于将输入元素合并到结果容器中,以及一个组合函数,用于将一个结果容器的内容合并到另一个结果容器中。包含独占无状态中间操作的管道可以在单次传递中处理,无论是顺序的还是并行的,只需最少的数据缓冲。

qq_43476358的博客 1166

Android 11系统定制:如何彻底隐藏状态栏电池图标(RK3568实测)

本文详细解析了在Android 11系统上如何通过多维度修改实现状态栏电池图标的彻底隐藏,特别针对RK3568平台提供了实战指南。从系统UI架构解析到核心配置文件修改,再到进阶技巧与疑难排查,帮助开发者深度定制Android系统,满足特定场景需求。

weixin_29250573的博客 377

电力抄表协议DL/T645和698.45到底有啥区别?一张图帮你选对协议

本文深入对比了电力抄表协议DL/T645和DL/T698.45的技术差异,从协议架构、报文结构、功能覆盖到实施成本进行全面分析。通过实际案例和数据,揭示698.45协议在通信效率、业务扩展性和长期成本上的优势,并提供清晰的选型决策流程图,帮助工程师根据具体需求选择最适合的电力抄表协议。

weixin_38168696的博客 401

【Java】详解HashMap原理

HashMap介绍 Map是一种存放键值对的数据类型。在Java中,最为常用的三个基于hashing原理实现的类是:HashMap、HashTable、HashSet。(实线段表示泛化关系,即继承,实线是实现关系) HashMap的特点 HashMap中Key值不可以相同,但是value值可以相同。 HashMap可以使用null作为key值,但需要规避这样的做法。 HashMap无法确保线程...

weixin_43358075的博客 321

清洁机器人之屏幕(中,大LCD)方案--ITE 联阳986x系列 soc显控芯片介绍

清洁机器人之屏幕(中,大LCD)方案--ITE 联阳986x系列 soc显控芯片介绍

AIoT深度探索者 | 嵌入式系统架构师 深耕物联网(IoT)与人工智能(AI)融合技术,聚焦​​边缘计算、图像开发、低功耗通信协议​​,致力于智能硬件,关注我获取最新技术文章信息与海量资源~ 2666

HashMap源码和实现原理

HashMap简介 HashMap是一种利用哈希表原理存储元素的集合,当遇到哈希冲突时,HashMap会使用链地址法来解决冲突。在JDK1.7中,HashMap是由数组+链表构成的,在JDK1.8中则采用了数组+链表+红黑树的结构,新增了红黑树作为底层数据结构使得查询效率变得更高。 HashMap理论基础 一、HashMap数据结构 HashMap使用数组、链表和红黑树三者结合起来存储数据,构成一个哈希表。哈希表中每一个节点为HashMap集合存储的一个键值对,节点里面包含当前节点的哈希值、key、valu

weixin_43274704的博客 1564

一层一责,一聚合一包:DDD分层架构下的微服务代码模型、三重边界与服务数据协作

领域模型画得再清楚,落不进代码也是空谈。本文以一个可运行的客户中心微服务为线索,先对比整洁架构、六边形架构与DDD分层架构,指出三者共同守护的那条隔离核心业务的红线;再把DDD分层落成四个一级目录、以聚合为单位分包、一个聚合一个仓储的代码模型,拆解实体、聚合根、值对象、领域事件、领域服务与仓储的设计要点;随后厘清逻辑、物理、代码三重边界,并用检查器实测:干净代码零越界,拆出积分聚合时领域代码无需改动;注入九处越界后,必须先重构才能拆分。最后梳理服务调用三场景与PO、DO、DTO、VO的协作。

曾经“等你生日那天”都遥远得像未来,如今却可欢愉的挥手说“下个十年见” 177万+

基于FFT的轴承故障诊断 - MATLAB代码和详细解释

最后,我们可以根据异常特征的位置和特征分析来确定轴承故障的类型。最后,我们可以根据异常特征的位置和特征分析来确定轴承故障的类型。因此,通过分析频谱图,我们可以识别轴承故障的特征。因此,通过分析频谱图,我们可以识别轴承故障的特征。我们可以选择适当的阈值,并在频谱图上标记异常特征。通过这种基于FFT的轴承故障诊断方法,我们可以快速、准确地检测和诊断轴承故障。接下来,我们将应用FFT来获取振动信号的频谱信息,频谱显示了信号在不同频率上的能量分布。通过将异常特征标记在频谱图上,我们可以直观地识别轴承故障的存在。

COWizard的博客 613

java集合

1. List集合 1.1 ArrayList ArrayListr中维护了一个Object类型的数组elementData. transient Object[]elementData; 当创建ArrayListy对象时,如果使用的是无参构造器,则初始elementData容量为0,第1次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍。 如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容eleme

weixin_44744094的博客 995

智慧加油站小程序数据库设计文档

【代码】智慧加油站小程序数据库设计文档。

低代码布道师的博客 1403

1.8 HashMap源码阅读带详细注释(put())

记录自己阅读源码的过程,有什么问题都可以指出哈 /** * Associates the specified value with the specified key in this map. * If the map previously contained a mapping for the key, the old * value is replaced. * * @param key key with which the specified val

偶尔记录些东西 330

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

HashMap源码阅读

这里看下jdk1.8源码 先看下相关的属性 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 /** * The maximum capacity, used if a higher value is implicitly specified * by either of the constructors with arguments. * MUST be a power of tw

weixin_45870082的博客 209

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

HashMap源码阅读笔记

本文基于jdk1.8 HashMap采用 key/value 存储结构,每个key对应唯一的value。 在jdk1.7之前,HashMap 的内部存储结构是数组+链表。 在jdk1.8中 HashMap的存储结构是 数组+链表+红黑树,提高了效率。 红黑树 在阅读HashMap源码之前,有必要对红黑树进行一些了解。 红黑树的性质 红黑树是一种自平衡二叉查找树。 红黑树具有如下特性: 1、 任何一个节点都有颜色,黑色或者红色 2、根节点是黑色的 3、 父子节点之间不能出现两个连续的红节点 4、任何一.

三分恶的博客 1172

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

HashMap & HashSet源码阅读

HashMap是Map接口的一个实现,采用哈希表实现的键值对集合,键和值都允许为null。HashMap 的特殊存储结构使得在获取指定元素前需要经过哈希运算,得到目标元素在哈希表中的位置,然后再进行少量比较即可得到元素,这使得 HashMap 的查找效率贼高。由于是基于哈希的Map,元素之间没有特定的顺序。

qq_36769368的博客 990

【Java】HashMap 源码阅读

HashMap 主要用来存放键值对,实现了基于哈希表的 Map 接口,非线程安全。HashMap 可以存放 null 的 key 和 null 值,但 null 作为 key 只能有一个,null 作为 value 可以有多个。HashMap 的默认大小为16,之后每次扩充,容量都变为原来的2倍,并且 HashMap 总是以2的幂作为哈希表的大小。

栗子的博客 946

HashMap 源码阅读

HashMap源码阅读

weixin_45568648的博客 441

HashMap的源码阅读

HashMap的继承结构 首先了解HashMap之前,先看看HashMap的具体继承结构: 可以看到一个类叫做AbstractMap,这显然是个抽象类,通过继承这个抽象类,可以写出不同特性的map. 我们看看最顶层的Map接口: Map 接口主要是用于保存具有映射关系的数据:key 和 value Map中的 key用 Set来存储, set是无序不可重复。因此同一个 Map 对象所对应的类,必须重写 hashCode 和 equals 方法。 Map接口中的key与value都可以是任何引用类型的数据,

qq_46004291的博客 298

JDK源码阅读之HashMap

JDK源码阅读之HashMapHashMap简介HashMap类图HashMap重要方法方法变量构造方法精华方法putValresize重要方法getNodeputMapEntriescloneHashMap变动HashMap阅读感想说明 HashMap简介 基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了非同步和允许使用 null...

BlingZeng的博客 527
上一篇: 【踩错】解决ajax异步更新后控件的click事件失效的方法
下一篇: 【算法】LeetCode91.解码方法(类似爬楼梯)
皮皮蟹01
博客等级 码龄10年 29粉丝 76原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值