文章目录

    • JDK1.8 HashMap介绍
    • JDK1.8 HashMap源码分析
      • 成员变量
      • 构造方法
      • `put(K key, V value)`方法
      • `hash(Object key)`方法
      • `putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)`方法
      • `treeifyBin(Node

JDK1.8 HashMap介绍

  • 在JDK1.8 之前 HashMap 由 数组+链表 数据结构组成的。
  • 在JDK1.8 之后 HashMap 由 数组+链表 +红黑树数据结构组成的。

JDK1.8 之前 HashMap 由 数组+链表 组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突 (两个对象调用的hashCode方法计算的哈希码值一致导致计算的数组索引值相同) 而存在的(“拉链法”解决冲突).JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(或者红黑树的边界值,默认为 8)并且当前数组的长度大于64时,此时此索引位置上的所有数据改为使用红黑树存储。

补充:将链表转换成红黑树前会判断,即使阈值大于8,但是数组长度小于64,此时并不会将链表变为红黑树。而是选择进行数组扩容。

这样做的目的是因为数组比较小,尽量避开红黑树结构,这种情况下变为红黑树结构,反而会降低效率,因为红黑树需要进行左旋,右旋,变色这些操作来保持平衡 。同时数组长度小于64时,搜索时间相对要快些。所以综上所述为了提高性能和减少搜索时间,底层在阈值大于8并且数组长度大于64时,链表才转换为红黑树。具体可以参考 treeifyBin方法。

当然虽然增了红黑树作为底层数据结构,结构变得复杂了,但是阈值大于8并且数组长度大于64时,链表转换为红黑树时,效率也变的更高效。

JDK1.8 HashMap源码分析

前面的博文中我们已经分析了JDK1.7中HashMap的源码,这篇博文我们来分析JDK1.8中HashMap的源码,看看JDK1.8中对HashMap做了哪些改变。

JDK 1.8 以前 HashMap 的实现是 数组+链表,即使哈希函数取得再好,也很难达到元素百分百均匀分布。当 HashMap 中有大量的元素都存放到同一个桶中时,这个桶下有一条长长的链表,这个时候 HashMap 就相当于一个单链表,假如单链表有 n 个元素,遍历的时间复杂度就是 O(n),完全失去了它的优势。针对这种情况,JDK 1.8 中引入了 红黑树(查找时间复杂度为 O(logn))来优化这个问题。 当链表长度很小的时候,即使遍历,速度也非常快,但是当链表长度不断变长,肯定会对查询性能有一定的影响,所以才需要转成树。

JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度(阀值)超过 8 时且当前数组的长度 > 64时,将链表转换为红黑树,这样大大减少了查找时间。jdk8在哈希表中引入红黑树的原因只是为了查找效率更高。

在这里插入图片描述

成员变量

public class HashMap<K,V> extends AbstractMap<K,V>implements Map<K,V>, Cloneable, Serializable {private static final long serialVersionUID = 362498820763181265L;/*** The default initial capacity - MUST be a power of two.*///定义默认的初始容量为16,必须是2的幂次方(面试常问,之前的博文已经分析过为啥一定是2的幂次方)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 two <= 1<<30.*///最大的容量static final int MAXIMUM_CAPACITY = 1 << 30;/*** The load factor used when none specified in constructor.*/// 定义默认的负载因子是0.75static final float DEFAULT_LOAD_FACTOR = 0.75f;/*** The bin count threshold for using a tree rather than list for a* bin.  Bins are converted to trees when adding an element to a* bin with at least this many nodes. The value must be greater* than 2 and should be at least 8 to mesh with assumptions in* tree removal about conversion back to plain bins upon* shrinkage.*/// 当链表中元素个数大于8时,HashMap将会把链表转换为红黑树的结构。static final int TREEIFY_THRESHOLD = 8;/*** The bin count threshold for untreeifying a (split) bin during a* resize operation. Should be less than TREEIFY_THRESHOLD, and at* most 6 to mesh with shrinkage detection under removal.*///当树中的元素个数小于6个时,HashMap会将红黑树转换为链表结构。static final int UNTREEIFY_THRESHOLD = 6;/*** The smallest table capacity for which bins may be treeified.* (Otherwise the table is resized if too many nodes in a bin.)* Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts* between resizing and treeification thresholds.*///当链表中的元素等于8个进行创建树的时候,如果当前桶的数量小于64,则进行扩容重新分配 hash 值,而不是将节点变为红黑树。static final int MIN_TREEIFY_CAPACITY = 64;/*** The table, initialized on first use, and resized as* necessary. When allocated, length is always a power of two.* (We also tolerate length zero in some operations to allow* bootstrapping mechanics that are currently not needed.)*///JDK1.8中改为了Node, JDK1.7中则是Entrytransient Node<K,V>[] table;/*** Holds cached entrySet(). Note that AbstractMap fields are used* for keySet() and values().*/transient Set<Map.Entry<K,V>> entrySet;/*** The number of key-value mappings contained in this map.*/transient int size;/*** The number of times this HashMap has been structurally modified* Structural modifications are those that change the number of mappings in* the HashMap or otherwise modify its internal structure (e.g.,* rehash).  This field is used to make iterators on Collection-views of* the HashMap fail-fast.  (See ConcurrentModificationException).*/transient int modCount;/*** The next size value at which to resize (capacity * load factor).** @serial*/// (The javadoc description is true upon serialization.// Additionally, if the table array has not been allocated, this// field holds the initial array capacity, or zero signifying// DEFAULT_INITIAL_CAPACITY.)int threshold;/*** The load factor for the hash table.** @serial*/final float loadFactor;//....
}

构造方法

首先我们来看看构造方法,其实大体上跟JDK1.7是一样的。我这里重点介绍其中两个构造方法,这个构造方法跟JDK1.7是有些不同的,如下:

第一个就是无参构造方法

    /*** Constructs an empty <tt>HashMap</tt> with the default initial capacity* (16) and the default load factor (0.75).*/public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted}

跟JDK1.7不同的是,这里的无参构造只是对成员变量loadFactor进行了赋值,并没有调用其他的构造方法,而在JDK1.7中则是调用了HashMap(int initialCapacity, float loadFactor)的构造方法

第二个就是我们两个参数的构造方法:

    /*** Constructs an empty <tt>HashMap</tt> with the specified initial* capacity and load factor.** @param  initialCapacity the initial capacity* @param  loadFactor      the load factor* @throws IllegalArgumentException if the initial capacity is negative*         or the load factor is nonpositive*/public HashMap(int initialCapacity, float loadFactor) {//判断初始容量是否小于0,初始容量不能小于0if (initialCapacity < 0)//如果小于0,则抛出非法的参数异常IllegalArgumentExceptionthrow new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);//判断初始化容量initialCapacity是否大于集合的最大容量MAXIMUM_CAPACITY->2的30次幂 if (initialCapacity > MAXIMUM_CAPACITY)//如果超过MAXIMUM_CAPACITY,会将MAXIMUM_CAPACITY赋值给initialCapacityinitialCapacity = MAXIMUM_CAPACITY;//判断负载因子loadFactor是否小于等于0或者是否是一个非数值if (loadFactor <= 0 || Float.isNaN(loadFactor))//如果满足上述其中之一,则抛出非法的参数异常IllegalArgumentExceptionthrow new IllegalArgumentException("Illegal load factor: " +loadFactor);//将指定的加载因子赋值给HashMap成员变量的负载因子loadFactorthis.loadFactor = loadFactor;//前面部分跟JDK1.7都是一样的,区别就在下面this.threshold = tableSizeFor(initialCapacity);}

我们来看看这个tableSizeFor(initialCapacity)方法

    /*** Returns a power of two size for the given target capacity.*/static final int tableSizeFor(int cap) {int n = cap - 1;n |= n >>> 1;n |= n >>> 2;n |= n >>> 4;n |= n >>> 8;n |= n >>> 16;return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;}

这个方法我们就不多介绍了,前面的博文也介绍过,这个方法的作用就是得到大于给定initialCapacity的最小2的n次幂的整数。然后注意我们是把这个最小2的n次幂的整数赋值给了threshold。这个threshold的值应该是等于(capacity * load factor),而这里我们却赋值为HashMap的容量,是有问题吗?我们后面继续往后看,后面又对threshold进行了赋值的。

tableSizeFor(initialCapacity) 判断指定的初始化容量是否是2的n次幂,如果不是那么会变为比指 定初始化容量大的最小的2的n次幂。这点上述已经讲解过。
但是注意,在tableSizeFor方法体内部将计算后的数据返回给调用这里了,并且直接赋值给threshold边 界值了。有些人会觉得这里是一个bug,应该这样书写:
this.threshold = tableSizeFor(initialCapacity) * this.loadFactor;
这样才符合threshold的意思(当HashMap的size到达threshold这个阈值时会扩容)。
但是,请注意,在jdk8以后的构造方法中,并没有对table这个成员变量进行初始化,table的初始化被推 迟到了put方法中,在put方法中会对threshold重新计算,put方法的具体实现我们下面会进行讲解

put(K key, V value)方法

接下来我们来介绍一下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 value is to be associated* @param value value to be associated with the specified key* @return the previous value associated with <tt>key</tt>, or*         <tt>null</tt> if there was no mapping for <tt>key</tt>.*         (A <tt>null</tt> return can also indicate that the map*         previously associated <tt>null</tt> with <tt>key</tt>.)*/public V put(K key, V value) {return putVal(hash(key), key, value, false, true);}

我们看到里面调用了putVal方法,然后传入的第一个参数是hash(key),我们来看看这个hash方法做了什么?

hash(Object key)方法

    /*** Computes key.hashCode() and spreads (XORs) higher bits of hash* to lower.  Because the table uses power-of-two masking, sets of* hashes that vary only in bits above the current mask will* always collide. (Among known examples are sets of Float keys* holding consecutive whole numbers in small tables.)  So we* apply a transform that spreads the impact of higher bits* downward. There is a tradeoff between speed, utility, and* quality of bit-spreading. Because many common sets of hashes* are already reasonably distributed (so don't benefit from* spreading), and because we use trees to handle large sets of* collisions in bins, we just XOR some shifted bits in the* cheapest possible way to reduce systematic lossage, as well as* to incorporate impact of the highest bits that would otherwise* never be used in index calculations because of table bounds.*/static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}

这个hash方法其实就是拿到key对象的hashCode,然后进行扰乱函数的处理,减少hash碰撞。这个我们前面的博文也有介绍过。

下面我们继续看这个putVal()方法

putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)方法

主要参数:

  • hash key的hash值
  • key 原始Key
  • value 要存放的值
  • onlyIfAbsent 如果true代表不更改现有的值
  • evict 如果为false表示table为创建状态

我们现在开始分析这个方法,加上我们HashMap<String,String> map = new HashMap();,在HashMap的无参构造方法中并没有做其他事情,只是对成员变量loadFactor进行了赋值。然后我们调用put方法往map对象中添加元素,接下来我们来分析这个put方法中做了些什么事情。

    /*** Implements Map.put and related methods** @param hash hash for key* @param key the key* @param value the value to put* @param onlyIfAbsent if true, don't change existing value* @param evict if false, the table is in creation mode.* @return previous value, or null if none*/final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;/*1)transient Node<K,V>[] table; 表示存储Map集合中元素的数组。2)(tab = table) == null 表示将空的table赋值给tab,然后判断tab是否等于null,第一次肯定是null3)(n = tab.length) == 0 表示将数组的长度0赋值给n,然后判断n是否等于0,n等于0由于if判断使用双或,满足一个即可,则执行代码 n = (tab = resize()).length; 进行数组初始化。并将初始化好的数组长度赋值给n.4)执行完n = (tab = resize()).length,数组tab每个空间都是null*/if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else {Node<K,V> e; K k;if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}if (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;}

当第一次的时候HashMap是没有初始化的,所以条件if ((tab = table) == null || (n = tab.length) == 0)是为true的,所以会调用n = (tab = resize()).length;来进行数组的初始化,并把初始化好的数组长度赋值给n。

那么我们接下来就来看看这个resize()方法中做了什么?

这个方法简单的来说就是初始化或者是扩容的操作。

    /*** Initializes or doubles table size.  If null, allocates in* accord with initial capacity target held in field threshold.* Otherwise, because we are using power-of-two expansion, the* elements from each bin must either stay at same index, or move* with a power of two offset in the new table.** @return the table*/final Node<K,V>[] resize() {//得到当前数组Node<K,V>[] oldTab = table;//如果当前数组等于null长度返回0,否则返回当前数组的长度,所以当前的table是空,所以oldCap为0int oldCap = (oldTab == null) ? 0 : oldTab.length;//因为new HashMap()的时候并没有给threshold赋值,所以oldThr的值也为0int oldThr = threshold;int newCap, newThr = 0;if (oldCap > 0) {if (oldCap >= MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return oldTab;}else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)newThr = oldThr << 1; // double threshold}else if (oldThr > 0) // initial capacity was placed in thresholdnewCap = oldThr;//所以初始化的时候走到else的逻辑else {               // zero initial threshold signifies using defaults//设置一个默认的容量,也就是16newCap = DEFAULT_INITIAL_CAPACITY;//设置阈值为(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY)newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}if (newThr == 0) {float ft = (float)newCap * loadFactor;newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);}//赋值到成员变量的阈值threshold = newThr;@SuppressWarnings({"rawtypes","unchecked"})//初始化table数组Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];//赋值到成员变量的table中table = newTab;//因为oldTab为空,所以不会走下面的逻辑if (oldTab != null) {for (int j = 0; j < oldCap; ++j) {Node<K,V> e;if ((e = oldTab[j]) != null) {oldTab[j] = null;if (e.next == null)newTab[e.hash & (newCap - 1)] = e;else if (e instanceof TreeNode)((TreeNode<K,V>)e).split(this, newTab, j, oldCap);else { // preserve orderNode<K,V> loHead = null, loTail = null;Node<K,V> hiHead = null, hiTail = null;Node<K,V> next;do {next = e.next;if ((e.hash & oldCap) == 0) {if (loTail == null)loHead = e;elseloTail.next = e;loTail = e;}else {if (hiTail == null)hiHead = e;elsehiTail.next = e;hiTail = e;}} while ((e = next) != null);if (loTail != null) {loTail.next = null;newTab[j] = loHead;}if (hiTail != null) {hiTail.next = null;newTab[j + oldCap] = hiHead;}}}}}// 返回新的tablereturn newTab;}

通过上面的代码我们可以看出来,经过resize()方法之后获得一个初始容量为16的table,然后threshold为
(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY)

接下来我们继续看这个putVal的方法。

 /*** Implements Map.put and related methods.** @param hash hash for key* @param key the key* @param value the value to put* @param onlyIfAbsent if true, don't change existing value* @param evict if false, the table is in creation mode.* @return previous value, or null if none*/final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;/*1)transient Node<K,V>[] table; 表示存储Map集合中元素的数组。2)(tab = table) == null 表示将空的table赋值给tab,然后判断tab是否等于null,第一次肯定是null3)(n = tab.length) == 0 表示将数组的长度0赋值给n,然后判断n是否等于0,n等于0由于if判断使用双或,满足一个即可,则执行代码 n = (tab = resize()).length; 进行数组初始化。并将初始化好的数组长度赋值给n.4)执行完n = (tab = resize()).length,数组tab每个空间都是null*/if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;/*1)i = (n - 1) & hash 表示计算数组的索引赋值给i,即确定元素存放在哪个桶中2)p = tab[i = (n - 1) & hash]表示获取计算出的位置的数据赋值给节点p3) (p = tab[i = (n - 1) & hash]) == null 判断节点位置是否等于null,如果为null,则执行代码:tab[i] = newNode(hash, key, value, null);根据键值对创建新的节点放入该位置的桶中小结:如果当前桶没有哈希碰撞冲突,则直接把键值对插入空间位置    */     if ((p = tab[i = (n - 1) & hash]) == null)//创建一个新的节点存入到桶中tab[i] = newNode(hash, key, value, null);else {// 执行else说明tab[i]不等于null,表示这个位置已经有值了。Node<K,V> e; K k;/*比较桶中第一个元素(数组中的结点)的hash值和key是否相等1)p.hash == hash :p.hash表示原来存在数据的hash值  hash表示后添加数据的hash值 比较两个hash值是否相等说明:p表示tab[i],即 newNode(hash, key, value, null)方法返回的Node对象。Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {return new Node<>(hash, key, value, next);}而在Node类中具有成员变量hash用来记录着之前数据的hash值的2)(k = p.key) == key :p.key获取原来数据的key赋值给k key表示后添加数据的key 比较两个key的地址值是否相等3)key != null && key.equals(k):能够执行到这里说明两个key的地址值不相等,那么先判断后添加的key是否等于null,如果不等于null再调用equals方法判断两个key的内容是否相等*/if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))/*说明:两个元素哈希值相等,并且key的值也相等将旧的元素整体对象赋值给e,用e来记录*/ e = p;// hash值不相等或者key不相等;判断p是否为红黑树结点else if (p instanceof TreeNode)// 放入树中e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);// 说明是链表节点 else {/*1)如果是链表的话需要遍历到最后节点然后插入2)采用循环遍历的方式,判断链表中是否有重复的key*/for (int binCount = 0; ; ++binCount) {/*1)e = p.next 获取p的下一个元素赋值给e2)(e = p.next) == null 判断p.next是否等于null,等于null,说明p没有下一个元素,那么此时到达了链表的尾部,还没有找到重复的key,则说明HashMap没有包含该键将该键值对插入链表中*/if ((e = p.next) == null) {/*1)创建一个新的节点插入到尾部(尾插法)p.next = newNode(hash, key, value, null);Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {return new Node<>(hash, key, value, next);}注意第四个参数next是null,因为当前元素插入到链表末尾了,那么下一个节点肯定是null2)这种添加方式也满足链表数据结构的特点,每次向后添加新的元素*/p.next = newNode(hash, key, value, null);/*1)节点添加完成之后判断此时节点个数是否大于TREEIFY_THRESHOLD临界值8,如果大于则将链表转换为红黑树2)int binCount = 0:表示for循环的初始化值。从0开始计数。记录着遍历节点的个数。值是0表示第一个节点,1表示第二个节点。。。。7表示第八个节点,加上数组中的第一个元素,元素个数是9TREEIFY_THRESHOLD - 1 --> 8 - 1 ---> 7如果binCount的值是7(加上数组中的的一个元素,元素个数是9)TREEIFY_THRESHOLD - 1也是7,此时转换红黑树*/if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st//转换为红黑树treeifyBin(tab, hash);// 跳出循环break;}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))// 相等,跳出循环/*要添加的元素和链表中的存在的元素的key相等了,则跳出for循环。不用再继续比较了直接执行下面的if语句去替换去 if (e != null) */break;/*说明新添加的元素和当前节点不相等,继续查找下一个节点。用于遍历桶中的链表,与前面的e = p.next组合,可以遍历链表*/p = e;}}/*表示在桶中找到key值、hash值与插入元素相等的结点也就是说通过上面的操作找到了重复的键,所以这里就是把该键的值变为新的值,并返回旧值这里完成了put方法的修改功能*/if (e != null) { // existing mapping for key// 记录e的valueV oldValue = e.value;// onlyIfAbsent为false或者旧值为nullif (!onlyIfAbsent || oldValue == null)//用新值替换旧值//e.value 表示旧值  value表示新值 e.value = value;// 访问后回调afterNodeAccess(e);// 返回旧值return oldValue;}}//修改记录次数++modCount;// 判断实际大小是否大于threshold阈值,如果超过则扩容if (++size > threshold)resize();// 插入后回调afterNodeInsertion(evict);return null;}

treeifyBin(Node<K,V>[] tab, int hash)方法

当节点添加完成之后判断此时节点个数是否大于TREEIFY_THRESHOLD临界值8,如果大于则将链表转换为红黑树,然后就会调用转换红黑树的方法treeifyBin。接下来我们来看看这个转换红黑树的treeifyBin方法。

   /*** Replaces all linked nodes in bin at index for given hash unless* table is too small, in which case resizes instead.* 替换指定哈希表的索引处桶中的所有链接节点,除非表太小,在这种情况下会调整大小。* Node<K,V>[] tab = tab 数组名* int hash = hash表示哈希值*/final void treeifyBin(Node<K,V>[] tab, int hash) {int n, index; Node<K,V> e;/*如果当前数组为空或者数组的长度小于进行树形化的阈值(MIN_TREEIFY_CAPACITY = 64),就去扩容。而不是将节点变为红黑树。目的:如果数组很小,那么转换红黑树,然后遍历效率要低一些。这时进行扩容,那么重新计算哈希值,链表长度有可能就变短了,数据会放到数组中,这样相对来说效率高一些。*/if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)//扩容方法resize();else if ((e = tab[index = (n - 1) & hash]) != null) {/*1)执行到这里说明哈希表中的数组长度大于阈值64,开始进行树形化2)e = tab[index = (n - 1) & hash]表示将数组中的元素取出赋值给e,e是哈希表中指定位置桶里的链表节点,从第一个开始*///hd:红黑树的头结点   tl :红黑树的尾结点TreeNode<K,V> hd = null, tl = null;do {//新创建一个树的节点,内容和当前链表节点e一致TreeNode<K,V> p = replacementTreeNode(e, null);if (tl == null)//将新创键的p节点赋值给红黑树的头结点hd = p;else {/*p.prev = tl:将上一个节点p赋值给现在的p的前一个节点tl.next = p;将现在节点p作为树的尾结点的下一个节点*/p.prev = tl;tl.next = p;}tl = p;/*e = e.next 将当前节点的下一个节点赋值给e,如果下一个节点不等于null则回到上面继续取出链表中节点转换为红黑树*/} while ((e = e.next) != null);/*让桶中的第一个元素即数组中的元素指向新建的红黑树的节点,以后这个桶里的元素就是红黑树而不是链表数据结构了*/if ((tab[index] = hd) != null)hd.treeify(tab);}}

小结:上述操作一共做了如下几件事:

  1. 根据哈希表中元素个数确定是扩容还是树形化
  2. 如果是树形化遍历桶中的元素,创建相同个数的树形节点,复制内容,建立起联系
  3. 然后让桶中的第一个元素指向新创建的树根节点,替换桶的链表内容为树形化内容

resize()方法

接下来我们来分析一下resize()方法。

想要了解HashMap的扩容机制你要有这两个问题

  1. 什么时候才需要扩容
  2. HashMap的扩容是什么

什么时候才需要扩容

当HashMap中的元素个数超过数组大小(数组长度)*loadFactor(负载因子)时,就会进行数组扩容,loadFactor的默认值(DEFAULT_LOAD_FACTOR)是0.75,这是一个折中的取值。也就是说,默认情况下,数组大小为16,那么当HashMap中的元素个数超过16×0.75=12(这个值就是阈值或者边界值threshold值)的时候,就把数组的大小扩展为2×16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预知元素的个数能够有效的提高HashMap的性能。

补充:
当HashMap中的其中一个链表的对象个数如果达到了8个,此时如果数组长度没有达到64,那么HashMap会先扩容解决,如果已经达到了64,那么这个链表会变成红黑树,节点类型由Node变成TreeNode类型。当然,如果映射关系被移除后,下次执行resize方法时判断树的节点个数低于6,也会再把树转换为链表。

HashMap的扩容是什么

进行扩容,会伴随着一次重新hash分配,并且会遍历hash表中所有的元素,是非常耗时的。在编写程序中,要尽量避免resize。

HashMap在进行扩容时,使用的rehash方式非常巧妙,因为每次扩容都是翻倍,与原来计算的 (n-1)&hash的结果相比,只是多了一个bit位,所以节点要么就在原来的位置,要么就被分配到"原位置+旧容量"这个位置。

怎么理解呢?例如我们从16扩展为32时,具体的变化如下所示:

在这里插入图片描述

因此元素在重新计算hash之后,因为n变为2倍,那么n-1的标记范围在高位多1bit(红色),因此新的index就会发生这样的变化:

在这里插入图片描述

说明:5是假设计算出来的原来的索引。这样就验证了上述所描述的:扩容之后所以节点要么就在原来的位置,要么就被分配到"原位置+旧容量"这个位置。

因此,我们在扩充HashMap的时候,不需要重新计算hash,只需要看看原来的hash值新增的那个bit是1还是0就可以了,是0的话索引没变,是1的话索引变成“原索引+oldCap(原位置+旧容量)”。可以看看下图为16扩充为32的resize示意图:

在这里插入图片描述

正是因为这样巧妙的rehash方式,既省去了重新计算hash值的时间,而且同时,由于新增的1bit是0还是1可以认为是随机的,在resize的过程中保证了rehash之后每个桶上的节点数一定小于等于原来桶上的节点数,保证了rehash之后不会出现更严重的hash冲突,均匀的把之前的冲突的节点分散到新的桶中了。

下面是代码的具体实现:

  /*** Initializes or doubles table size.  If null, allocates in* accord with initial capacity target held in field threshold.* Otherwise, because we are using power-of-two expansion, the* elements from each bin must either stay at same index, or move* with a power of two offset in the new table.** @return the table*/final Node<K,V>[] resize() {//得到当前数组Node<K,V>[] oldTab = table;//如果当前数组等于null长度返回0,否则返回当前数组的长度int oldCap = (oldTab == null) ? 0 : oldTab.length;//当前阀值点 默认是12(16*0.75)int oldThr = threshold;int newCap, newThr = 0;//如果老的数组长度大于0//开始计算扩容后的大小if (oldCap > 0) {// 超过最大值就不再扩充了,就只好随你碰撞去吧if (oldCap >= MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return oldTab;}/*没超过最大值,就扩充为原来的2倍1)(newCap = oldCap << 1) < MAXIMUM_CAPACITY 扩大到2倍之后容量要小于最大容量2)oldCap >= DEFAULT_INITIAL_CAPACITY 原数组长度大于等于数组初始化长度16*/else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)//阈值扩大一倍newThr = oldThr << 1; // double threshold}//老阈值点大于0 直接赋值else if (oldThr > 0) // initial capacity was placed in thresholdnewCap = oldThr; // 老阈值赋值给新的数组长度else {               // zero initial threshold signifies using defaults// 直接使用默认值newCap = DEFAULT_INITIAL_CAPACITY;newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}// 计算新的resize最大上限if (newThr == 0) {float ft = (float)newCap * loadFactor;newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);}//新的阀值 默认原来是12 乘以2之后变为24threshold = newThr;//创建新的哈希表@SuppressWarnings({"rawtypes","unchecked"})//newCap是新的数组长度--> 32Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];table = newTab;//判断旧数组是否等于空if (oldTab != null) {// 把每个bucket都移动到新的buckets中//遍历旧的哈希表的每个桶,重新计算桶里元素的新位置for (int j = 0; j < oldCap; ++j) {Node<K,V> e;if ((e = oldTab[j]) != null) {//原来的数据赋值为null 便于GC回收oldTab[j] = null;//判断数组是否有下一个引用if (e.next == null)//没有下一个引用,说明不是链表,当前桶上只有一个键值对,直接插入newTab[e.hash & (newCap - 1)] = e;//判断是否是红黑树else if (e instanceof TreeNode)//说明是红黑树来处理冲突的,则调用相关方法把树分开((TreeNode<K,V>)e).split(this, newTab, j, oldCap);else { // preserve order // 采用链表处理冲突Node<K,V> loHead = null, loTail = null;Node<K,V> hiHead = null, hiTail = null;Node<K,V> next;//通过上述讲解的原理来计算节点的新位置do {// 原索引next = e.next;//这里来判断如果等于true e这个节点在resize之后不需要移动位置if ((e.hash & oldCap) == 0) {if (loTail == null)loHead = e;elseloTail.next = e;loTail = e;}// 原索引+oldCapelse {if (hiTail == null)hiHead = e;elsehiTail.next = e;hiTail = e;}} while ((e = next) != null);// 原索引放到bucket里if (loTail != null) {loTail.next = null;newTab[j] = loHead;}// 原索引+oldCap放到bucket里if (hiTail != null) {hiTail.next = null;newTab[j + oldCap] = hiHead;}}}}}return newTab;}

remove(Object key)方法

 /*** Removes the mapping for the specified key from this map if present.** @param  key key whose mapping is to be removed from the map* @return the previous value associated with <tt>key</tt>, or*         <tt>null</tt> if there was no mapping for <tt>key</tt>.*         (A <tt>null</tt> return can also indicate that the map*         previously associated <tt>null</tt> with <tt>key</tt>.)*/public V remove(Object key) {Node<K,V> e;return (e = removeNode(hash(key), key, null, false, true)) == null ?null : e.value;}

remove方法的具体实现在removeNode方法中,所以我们重点看下removeNode方法

removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable)方法

 /*** Implements Map.remove and related methods** @param hash hash for key* @param key the key* @param value the value to match if matchValue, else ignored* @param matchValue if true only remove if value is equal* @param movable if false do not move other nodes while removing* @return the node, or null if none*/final Node<K,V> removeNode(int hash, Object key, Object value,boolean matchValue, boolean movable) {Node<K,V>[] tab; Node<K,V> p; int n, index;//根据hash找到位置 //如果当前key映射到的桶不为空if ((tab = table) != null && (n = tab.length) > 0 &&(p = tab[index = (n - 1) & hash]) != null) {Node<K,V> node = null, e; K k; V v;//如果桶上的节点就是要找的key,则将node指向该节点if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))node = p;else if ((e = p.next) != null) {//说明节点存在下一个节点if (p instanceof TreeNode)//说明是以红黑树来处理的冲突,则获取红黑树要删除的节点node = ((TreeNode<K,V>)p).getTreeNode(hash, key);else {//判断是否以链表方式处理hash冲突,是的话则通过遍历链表来寻找要删除的节点do {if (e.hash == hash &&((k = e.key) == key ||(key != null && key.equals(k)))) {node = e;break;}p = e;} while ((e = e.next) != null);}}//比较找到的key的value和要删除的是否匹配if (node != null && (!matchValue || (v = node.value) == value ||(value != null && value.equals(v)))) {if (node instanceof TreeNode)//通过调用红黑树的方法来删除节点((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);else if (node == p)//链表删除tab[index] = node.next;elsep.next = node.next;//记录修改次数++modCount;//变动的数量--size;afterNodeRemoval(node);return node;}}return null;}

参考

Java 8系列之重新认识HashMap

HashMap 工作原理

JDK1.8中的HashMap

HashMap学习笔记

https://gitee.com/cckevincyh/hashmap-learning

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 061 《PPT设计思维:教你又好又快搞定幻灯片》读书笔记

    挺喜欢的一本书,内容也挺实用,回归原始,还是要时常参学大师作品和动手实践结合。 书买回来放了好久了,终于在这个需要的紧急时刻,来抱佛脚看完了,还是扎扎实实的动起来,最重要!思维导图笔记《PPT设计思维:教你又好又快搞定幻灯片》邵云蛟著. ——北京:电子工业出版社…...

    2024/4/15 15:09:55
  2. 了解Go编译处理(三)—— 初识go compile

    前言 了解Go编译处理(二)—— go build一文中对go build的过程进行了追踪,build的源码中并不负责代码的编译,而是交给专门的编译工具进行编译,完整的build过程使用到以下工具: cover-cgo-compile-asm-pack-buildid-link 部分工具因文件类型的不一或编译参数的设置,可能不…...

    2024/4/28 15:29:01
  3. 链表的个人总结

    我犯过的错1.寻找尾节点://m为链表结构体指针 p为指向头节点地址的指针 该链表为单向不循环1. for(m = p->next; m != NULL; m = m->next) // 此时的m为空,不能进行增删2. for(m = p; m->next != NULL; m = m->next)//此时的m为尾节点,可以进行增删第一种情况…...

    2024/4/25 4:53:39
  4. 分支回溯-求解目标和的元素

    题目给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的数字可以无限制重复被选取。算法找到解答树,然后达到目标后回溯,最终通过排序剪枝达到优化算法的目的 代码时递归的模板代码给出边界条…...

    2024/4/21 23:33:58
  5. Django2.2丨管理器

    Manager是一种接口,它赋予了Django模型操作数据库的能力。Django应用中每个模型拥有至少一个Manager。 管理器名称 默认情况下,Django为每个模型类添加一个名为objects的Manager。可以通过自定义重命名Manager。 from django.db import modelsclass Person(models.Model):#..…...

    2024/5/4 14:54:25
  6. Educational Codeforces Round 92 (Rated for Div. 2) C. Good String

    Educational Codeforces Round 92 (Rated for Div. 2) C. Good String 题目链接 Let’s call left cyclic shift of some string t1t2t3…tn−1tn as string t2t3…tn−1tnt1.Analogically, let’s call right cyclic shift of string t as string tnt1t2t3…tn−1.Let’s say …...

    2024/4/15 15:09:51
  7. 20200802.114(待).53

    今天的每日一题: 114.将二叉树展开为链表,就是前序遍历二叉树。 等复习完二叉树再想想更快的方法。 53.又是一个简单的动态规划,思路很简单,我还错了两次。 1. 先分析动态规划的可行性, 优化子结构:对于包含第n个数的最长子序列, 假如第n个数是正的, 1.如果前面是正的,…...

    2024/4/15 17:57:47
  8. 【数据结构】归并排序

    归并排序(Merge) 稳定性: 稳定 最好时间复杂度: O(nlog n) 最坏时间复杂度: O(nlog n) 空间复杂度: O(n)将两个有序数组进行归并: void Merge(int low, int mid, int high) {int *B = (int*)malloc(N * sizeof(int));for (int i = low; i <= high; i++) …...

    2024/4/15 17:57:46
  9. VS2017学习C++基础—(数据类型)

    // 章节2 a01:数据类型小结 // 声明变量 数据类型 变量名; // 定义时初始化变量 数据类型 变量名 = 值; // 定义后初始化变量 数据类型 变量名;变量名 = 值; //命名方式 驼峰法,下划线,尽量用英文#include "pch.h" #include <iostream> #include <…...

    2024/4/15 17:57:45
  10. 汇编学习 day10

    检测点9.1db 0,0,0bx cscs:0006h IP:00beh...

    2024/4/15 17:57:44
  11. IDEA pom文件已经添加了依赖,但运行时仍然找不到类NoClassDefFoundError

    <dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.5</version> </dependency>pom文件显示正常,没有报红。 点击左上角的File,project structure,查看Artifaces是否成功添加依赖。如…...

    2024/4/15 17:57:45
  12. java 中基本数据类型(以及包装类)与String的相互转换

    基本数据类型与包装类 基本数据类型:int,long,char… 包装类:Integer,Long,Character…(除了Integer和Character有变型其余都是首字母大写) 注意:boolean不参与符号运算 包装类的本质就是把基本数据类型写成一个类,里面有各种各样的方法方便对数据的操作。同时在编写…...

    2024/4/27 8:17:12
  13. 114. 二叉树展开为链表(C++)

    题目详情给定一个二叉树,原地将它展开为一个单链表。例如,给定二叉树1/ \2 5/ \ \ 3 4 6 将其展开为:1\2\3\4\5\6——题目难度:中等-前序遍历解法 - 递归实现class Solution { public:void preorderTraversal(TreeNode* root, vector<TreeNode*>& save) {…...

    2024/4/15 17:57:41
  14. 芋道 Spring Cloud Netflix 注册中心 Eureka 入门

    摘要: 原创出处 http://www.iocoder.cn/Spring-Cloud/Netflix-Eureka/ 「芋道源码」欢迎转载,保留摘要,谢谢!1. 概述2. Eureka 简介3. 注册中心原理4. 快速入门5. 高可用 Eureka 集群6. 安全认证666. 彩蛋本文在提供完整代码示例,可见 https://github.com/YunaiV/SpringBoo…...

    2024/4/15 17:57:41
  15. php 把session存入数据库

    session 一般默认是以文件的形式放在服务器上的, 当你有多台服务器上就有麻烦了,用户在使用的时候,可能让你二次登录。我们可以把session 放在memcache 中,也可以把放在数据库中 来保证用户登录状态。PHP保存session默认的情况下是采用的文件方式来保存的,我们在PHP的配制…...

    2024/4/24 4:19:06
  16. 科大讯飞2021 笔试题第一道:固定大小和数量的纸币,至少需要几张纸币来付钱

    文章目录题目 题目题目大致描述: 假设1元、5元、10元、50元、100元 纸币分别有a,b,c,d,e张。现在要用这些钱来支付m元,至少要用多少张纸币?无解时返回 -1;进阶题目:每种纸币各用了多少张?(用tuple组队保存就行)解题思路:贪心算法(贪心算法的思想,每一次选择最大面值的…...

    2024/4/15 17:57:38
  17. Git基础知识整理

    ...

    2024/4/27 1:56:52
  18. 谷粒商城32 - 前端基础 VUE- 指令 v-on v-for v-if v-show

    文章目录v-on 示例事件修饰符按键修饰符组合按键v-for 示例显示user信息遍历对象信息加上:key来区分不同数据v-if & v-showv-if & v-show 代码示例v-else-ifv-if 与v-for的结合使用 v-on 示例 v-on示例点赞 <body...

    2024/4/25 9:33:04
  19. [py,mse] = svmpredict(y,x,model) py为空

    [py,mse] = svmpredict(y,x,model) py为空 [py,mse] = svmpredict(y,x,model)改成[py,mse,decision_values] = svmpredict(y,x,model)即可运行成功啦,多加一个参数。...

    2024/4/27 16:25:12
  20. C++面向对象设计模式-简单工厂模式

    尤为印象深刻的是,去年校招的时候有个几百人的公司技术负责人跟我说,不要以为学了几个设计模式就可以说是学到了,真正项目需要的都是十万行以上的,几个模式套着用,为的就是降低耦合度,和最大程度提高复用性,尽量分离客户端界面和逻辑。 今天刚好重新学习了简单工厂模式和…...

    2024/4/28 20:57:09

最新文章

  1. CMakeLists.txt语法规则:部分常用命令说明四

    一. 简介 前面几篇文章学习了CMakeLists.txt语法中前面几篇文章学习了CMakeLists.txt语法中部分常用命令。文章如下&#xff1a; CMakeLists.txt语法规则&#xff1a;部分常用命令说明一-CSDN博客 CMakeLists.txt语法规则&#xff1a;部分常用命令说明二-CSDN博客 CMakeLi…...

    2024/5/5 9:10:50
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. ROS2高效学习第十章 -- ros2 高级组件之大型项目中的 launch 其二

    ros2 高级组件之大型项目中的 launch 1 前言和资料2 正文2.1 启动 turtlesim&#xff0c;生成一个 turtle &#xff0c;设置背景色2.2 使用 event handler 重写上节的样例2.3 turtle_tf_mimic_rviz_launch 样例 3 总结 1 前言和资料 早在ROS2高效学习第四章 – ros2 topic 编程…...

    2024/5/4 16:36:42
  4. 微信小程序的页面交互2

    一、自定义属性 &#xff08;1&#xff09;定义&#xff1a; 微信小程序中的自定义属性实际上是由data-前缀加上一个自定义属性名组成。 &#xff08;2&#xff09;如何获取自定义属性的值&#xff1f; 用到target或currentTarget对象的dataset属性可以获取数据 &#xff…...

    2024/5/1 13:38:59
  5. 416. 分割等和子集问题(动态规划)

    题目 题解 class Solution:def canPartition(self, nums: List[int]) -> bool:# badcaseif not nums:return True# 不能被2整除if sum(nums) % 2 ! 0:return False# 状态定义&#xff1a;dp[i][j]表示当背包容量为j&#xff0c;用前i个物品是否正好可以将背包填满&#xff…...

    2024/5/4 12:05:22
  6. 【Java】ExcelWriter自适应宽度工具类(支持中文)

    工具类 import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet;/*** Excel工具类** author xiaoming* date 2023/11/17 10:40*/ public class ExcelUti…...

    2024/5/4 11:23:32
  7. Spring cloud负载均衡@LoadBalanced LoadBalancerClient

    LoadBalance vs Ribbon 由于Spring cloud2020之后移除了Ribbon&#xff0c;直接使用Spring Cloud LoadBalancer作为客户端负载均衡组件&#xff0c;我们讨论Spring负载均衡以Spring Cloud2020之后版本为主&#xff0c;学习Spring Cloud LoadBalance&#xff0c;暂不讨论Ribbon…...

    2024/5/4 14:46:16
  8. TSINGSEE青犀AI智能分析+视频监控工业园区周界安全防范方案

    一、背景需求分析 在工业产业园、化工园或生产制造园区中&#xff0c;周界防范意义重大&#xff0c;对园区的安全起到重要的作用。常规的安防方式是采用人员巡查&#xff0c;人力投入成本大而且效率低。周界一旦被破坏或入侵&#xff0c;会影响园区人员和资产安全&#xff0c;…...

    2024/5/4 23:54:44
  9. VB.net WebBrowser网页元素抓取分析方法

    在用WebBrowser编程实现网页操作自动化时&#xff0c;常要分析网页Html&#xff0c;例如网页在加载数据时&#xff0c;常会显示“系统处理中&#xff0c;请稍候..”&#xff0c;我们需要在数据加载完成后才能继续下一步操作&#xff0c;如何抓取这个信息的网页html元素变化&…...

    2024/5/4 12:10:13
  10. 【Objective-C】Objective-C汇总

    方法定义 参考&#xff1a;https://www.yiibai.com/objective_c/objective_c_functions.html Objective-C编程语言中方法定义的一般形式如下 - (return_type) method_name:( argumentType1 )argumentName1 joiningArgument2:( argumentType2 )argumentName2 ... joiningArgu…...

    2024/5/4 23:54:49
  11. 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】

    &#x1f468;‍&#x1f4bb;博客主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 收录于专栏 【洛谷算法题】 文章目录 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】&#x1f30f;题目描述&#x1f30f;输入格…...

    2024/5/4 23:54:44
  12. 【ES6.0】- 扩展运算符(...)

    【ES6.0】- 扩展运算符... 文章目录 【ES6.0】- 扩展运算符...一、概述二、拷贝数组对象三、合并操作四、参数传递五、数组去重六、字符串转字符数组七、NodeList转数组八、解构变量九、打印日志十、总结 一、概述 **扩展运算符(...)**允许一个表达式在期望多个参数&#xff0…...

    2024/5/4 14:46:12
  13. 摩根看好的前智能硬件头部品牌双11交易数据极度异常!——是模式创新还是饮鸩止渴?

    文 | 螳螂观察 作者 | 李燃 双11狂欢已落下帷幕&#xff0c;各大品牌纷纷晒出优异的成绩单&#xff0c;摩根士丹利投资的智能硬件头部品牌凯迪仕也不例外。然而有爆料称&#xff0c;在自媒体平台发布霸榜各大榜单喜讯的凯迪仕智能锁&#xff0c;多个平台数据都表现出极度异常…...

    2024/5/4 14:46:11
  14. Go语言常用命令详解(二)

    文章目录 前言常用命令go bug示例参数说明 go doc示例参数说明 go env示例 go fix示例 go fmt示例 go generate示例 总结写在最后 前言 接着上一篇继续介绍Go语言的常用命令 常用命令 以下是一些常用的Go命令&#xff0c;这些命令可以帮助您在Go开发中进行编译、测试、运行和…...

    2024/5/4 14:46:11
  15. 用欧拉路径判断图同构推出reverse合法性:1116T4

    http://cplusoj.com/d/senior/p/SS231116D 假设我们要把 a a a 变成 b b b&#xff0c;我们在 a i a_i ai​ 和 a i 1 a_{i1} ai1​ 之间连边&#xff0c; b b b 同理&#xff0c;则 a a a 能变成 b b b 的充要条件是两图 A , B A,B A,B 同构。 必要性显然&#xff0…...

    2024/5/5 2:25:33
  16. 【NGINX--1】基础知识

    1、在 Debian/Ubuntu 上安装 NGINX 在 Debian 或 Ubuntu 机器上安装 NGINX 开源版。 更新已配置源的软件包信息&#xff0c;并安装一些有助于配置官方 NGINX 软件包仓库的软件包&#xff1a; apt-get update apt install -y curl gnupg2 ca-certificates lsb-release debian-…...

    2024/5/4 21:24:42
  17. Hive默认分割符、存储格式与数据压缩

    目录 1、Hive默认分割符2、Hive存储格式3、Hive数据压缩 1、Hive默认分割符 Hive创建表时指定的行受限&#xff08;ROW FORMAT&#xff09;配置标准HQL为&#xff1a; ... ROW FORMAT DELIMITED FIELDS TERMINATED BY \u0001 COLLECTION ITEMS TERMINATED BY , MAP KEYS TERMI…...

    2024/5/4 12:39:12
  18. 【论文阅读】MAG:一种用于航天器遥测数据中有效异常检测的新方法

    文章目录 摘要1 引言2 问题描述3 拟议框架4 所提出方法的细节A.数据预处理B.变量相关分析C.MAG模型D.异常分数 5 实验A.数据集和性能指标B.实验设置与平台C.结果和比较 6 结论 摘要 异常检测是保证航天器稳定性的关键。在航天器运行过程中&#xff0c;传感器和控制器产生大量周…...

    2024/5/4 13:16:06
  19. --max-old-space-size=8192报错

    vue项目运行时&#xff0c;如果经常运行慢&#xff0c;崩溃停止服务&#xff0c;报如下错误 FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory 因为在 Node 中&#xff0c;通过JavaScript使用内存时只能使用部分内存&#xff08;64位系统&…...

    2024/5/4 16:48:41
  20. 基于深度学习的恶意软件检测

    恶意软件是指恶意软件犯罪者用来感染个人计算机或整个组织的网络的软件。 它利用目标系统漏洞&#xff0c;例如可以被劫持的合法软件&#xff08;例如浏览器或 Web 应用程序插件&#xff09;中的错误。 恶意软件渗透可能会造成灾难性的后果&#xff0c;包括数据被盗、勒索或网…...

    2024/5/4 14:46:05
  21. JS原型对象prototype

    让我简单的为大家介绍一下原型对象prototype吧&#xff01; 使用原型实现方法共享 1.构造函数通过原型分配的函数是所有对象所 共享的。 2.JavaScript 规定&#xff0c;每一个构造函数都有一个 prototype 属性&#xff0c;指向另一个对象&#xff0c;所以我们也称为原型对象…...

    2024/5/5 3:37:58
  22. C++中只能有一个实例的单例类

    C中只能有一个实例的单例类 前面讨论的 President 类很不错&#xff0c;但存在一个缺陷&#xff1a;无法禁止通过实例化多个对象来创建多名总统&#xff1a; President One, Two, Three; 由于复制构造函数是私有的&#xff0c;其中每个对象都是不可复制的&#xff0c;但您的目…...

    2024/5/4 23:54:30
  23. python django 小程序图书借阅源码

    开发工具&#xff1a; PyCharm&#xff0c;mysql5.7&#xff0c;微信开发者工具 技术说明&#xff1a; python django html 小程序 功能介绍&#xff1a; 用户端&#xff1a; 登录注册&#xff08;含授权登录&#xff09; 首页显示搜索图书&#xff0c;轮播图&#xff0…...

    2024/5/4 9:07:39
  24. 电子学会C/C++编程等级考试2022年03月(一级)真题解析

    C/C++等级考试(1~8级)全部真题・点这里 第1题:双精度浮点数的输入输出 输入一个双精度浮点数,保留8位小数,输出这个浮点数。 时间限制:1000 内存限制:65536输入 只有一行,一个双精度浮点数。输出 一行,保留8位小数的浮点数。样例输入 3.1415926535798932样例输出 3.1…...

    2024/5/4 14:46:02
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57