ConcurrentHashMap的JDK1.7源码解析(含详细注释)
为什么HashTable慢
- HashTable 虽然它是一个线程安全的类,但是它是使用 synchronized 关键字来修饰一些操作。
- synchronized 锁住了一整个哈希表。
- 导致同一时期只有一个线程可以进行对哈希表的操作。
- 所以 HashTable 虽然线程安全但是它慢,一个人操作所有人等待,并发值就是1。
ConcurrentHashMap
- 在 JDK1.5 ~JDK1.7 中,ConcurrentHashMap 这个类它使用的是分段锁的机制来实现并发控制的。
数据结构
- 分段锁可以这么理解,我们原来的哈希表又在最外层套了一个数组,这个数组就被称作为 段(Segment)。
- 段数组中每个桶都包含了一个 HashMap 的哈希表,该哈希表在 JDK1.8 之前统一使用数组+链表的形式。
- 段数组中的每个段都可以独立加锁,段的默认大小为 2^4 ,也就是默认可以有16个线程进行并发编程。
- Segment 通过继承 ReentrantLock 来进行加锁。
- 只要保证段中桶的线程安全,那么整个段也就是整个 ConcurrentHashMap 对象是线程安全的了。
- 值得注意的是:Segment数组的长度是不可以被改变的,初始化如果不规定,那么就采用默认的 2^4
初始化
- 两个参数:
- initialCapacity :
- 初始容量,这个值指的是整个 ConcurrentHashMap 的初始容量,实际操作的时候需要平均分给每个 Segment。
- 比如你初始容量是 64,Segment 的容量为16,那么每个段中哈希表的初始容量就为 64/16=4。
- loadFactor:
- 这个负载因子是给 段中哈希表 扩容时候使用的。
- initialCapacity :
- 其中某个构造函数的源码详解:
public ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel) {// 非法参数判断。if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)throw new IllegalArgumentException();// 并行级别越界重设。if (concurrencyLevel > MAX_SEGMENTS)concurrencyLevel = MAX_SEGMENTS;// Find power-of-two sizes best matching arguments 找到2次幂大小的最佳匹配参数.int sshift = 0; // 偏移量吧因该是。int ssize = 1; // segment的数组长度。// 计算并行级别 ssize,因为要保持并行级别是 2 的 n 次方// 如果这里我们的并行级别是16的话。while (ssize < concurrencyLevel) {++sshift;ssize <<= 1;}// 到这一步,sshift = 4, ssize = 16。// 那么计算出 segmentShift 为 28,segmentMask 为 15,后面会用到这两个值this.segmentShift = 32 - sshift; // 段偏移量this.segmentMask = ssize - 1; // 段掩码if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;// initialCapacity 是设置整个 map 初始的大小,// 这里根据 initialCapacity 计算 Segment 数组中每个位置可以分到的大小。// 如 initialCapacity 为 64,那么每个 Segment 或称之为"槽"可以分到 4 个。// 那么此时段中哈希表的容量就为 4。int c = initialCapacity / ssize; // c 用于// 如果段中容量与segment的数组长度乘积小于了整个 ConcurrentHashMap 的初始容量,// 那我们就把目标容量(段中哈希表容量) + 1;if (c * ssize < initialCapacity)++c;// 默认 MIN_SEGMENT_TABLE_CAPACITY 是 2,这个值也是有讲究的,因为这样的话,对于具体的槽上,// 插入一个元素不至于扩容,插入第二个的时候才会扩容int cap = MIN_SEGMENT_TABLE_CAPACITY; //先给此时的段中容量默认为 2 。// 如果段中默认容量小于目标容量的话就一直扩大到段中默认容量等于目标容量。while (cap < c)cap <<= 1;// 创建段数组的第一个元素 segment[0]Segment<K,V> s0 =new Segment<K,V>(loadFactor, (int)(cap * loadFactor),(HashEntry<K,V>[])new HashEntry[cap]);// 创建段数组,数组长度就为 ssize 。Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];// 往段数组中有序的写入 segment[0]。UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]this.segments = ss;
}
- 如果是无参构造的话,我们可以从中看到段中哈希表的 默认大小为2,负载因子是0.75,那么 扩容阈值就是1.5,插入第一个元素不会扩容。
- 那么到这整个 ConcurrentHashMap 对象就创建完毕了,但是有一个疑问:
- 在末尾初始化了段表中的第一个位置,其他位置没有初始化,这是为什么呢?
- 是因为每次创建一个 **Segment **对象要计算好几个值,初始化 **ConcurrentHashMap **的时候初始化了一个s0,只有再要初始化的 Segment 对象的时候,就拿s0当模板直接照搬参数就行,这样就会快一点。
put方法及其过程分析
- 先来看看put方法的源码。
public V put(K key, V value) {// 先建一个段的临时桶。Segment<K,V> s;// 如果要put的值为空的话就抛出异常。if (value == null)throw new NullPointerException();// 计算 key 的 hash 值int hash = hash(key);// 根据 hash 值找到段表中的位置 j。// hash 是 32 位,无符号右移 segmentShift(28) 位,剩下高 4 位,// 然后和 segmentMask(15) 做一次与操作,也就是说 j 是 hash 值的高 4 位,也就是槽的数组下标/*0000 0000 0000 0000 0000 0000 0000 11010000 0000 0000 0000 0000 0000 0000 1111&----------------------------------------0000 0000 0000 0000 0000 0000 0000 1101*/int j = (hash >>> segmentShift) & segmentMask;// 刚刚说了,初始化的时候初始化了 segment[0],但是其他位置还是 null。if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegments = ensureSegment(j); // 初始化 j 处的桶。// 插入新值到 s 中。return s.put(key, hash, value, false);
}
- 上面这个 put方法 是给用户调用的接口。
- 读完之后不难发现,他就做了一件事,根据待插入键值对的 key 的 hash 值找到相应的段表中应该插入的桶位置,并对该位置初始化。
- 再之后详细操作就是段的put操作了。
final V put(K key, int hash, V value, boolean onlyIfAbsent) {// 再 put 到指定段中之前,我们得获取到当前段表中这个桶的独占的锁。// 以此来保证整个过程,只有我们一个线程在对这个桶做操作。HashEntry<K,V> node = tryLock() ? null :scanAndLockForPut(key, hash, value);V oldValue; // 用来存储被覆盖的值。try {// 这个是段表某个桶内部的哈希表。HashEntry<K,V>[] tab = table;// 再次利用待插入键值对 key 的 hash 值,求应该放置的数组下标。int index = (tab.length - 1) & hash;// first 是桶中哈希表的待插入桶处的链表的表头。HashEntry<K,V> first = entryAt(tab, index);// 遍历链表。for (HashEntry<K,V> e = first;;) {// 这个遍历主要是为了找出,key重复的情况。if (e != null) {K k;// 如果当前链表节点的key重复了的话。if ((k = e.key) == key ||(e.hash == hash && key.equals(k)))oldValue = e.value;if (!onlyIfAbsent) {// 覆盖旧的值。e.value = value;++modCount;}break;}e = e.next;}else {// node 到底是不是 null,这个要看获取锁的过程,不过和这里都没有关系。// 如果不为 null。if (node != null)// 那就把它设置为链表表头,JDK1.7使用头插法。node.setNext(first);else// 否则如果是null,就先初始化它再设置为链表表头。node = new HashEntry<K,V>(hash, key, value, first);int c = count + 1;// 如果超过了段表中该桶中哈希表的阈值,这个哈希表就需要扩容。if (c > threshold && tab.length < MAXIMUM_CAPACITY)rehash(node); // 扩容。else// 没有达到阈值,将 node 放到哈希表的 index 位置,// 其实就是将新的节点设置成原链表的表头,使用头插法。setEntryAt(tab, index, node);++modCount;count = c;oldValue = null;break;}}} finally {// 解锁unlock();}return oldValue;
}
- put方法中有几个重要方法,我们下面来看看。
ensureSegment方法(初始化段表中某个桶)
- ConcurrentHashMap 初始化的时候会初始化 第一个桶segment[0] 。
- 对于其他桶来说,在插入第一个值的时候进行初始化。
- 这里需要考虑并发,因为很可能会有多个线程同时进来初始化同一个槽 segment[k],不过只要有一个成功了就可以。
- 该方法中使用了 getObjectVolatile 这个方法的意思就是:
- 线程之间互相可看到一些透明的资源,一个线程要是对某的资源进行了操作,那么别的线程都可以看到操作后的结果。
- 可以防止一个线程对资源进行 6 - 1 = 5,而别的线程还是看这个资源为 6 的情况。
- 这个方法中比较有意思的还有最后一个 if判断。
- CAS操作,这里的比较并交换在CPU里面就是一条指令,保证原子性的。
- 也就是说,不会出现,我比较完了还没赋值,CPU就切换到下一个线程了。
private Segment<K,V> ensureSegment(int k) {// 临时段表。final Segment<K,V>[] ss = this.segments;long u = (k << SSHIFT) + SBASE; // raw offsetSegment<K,V> seg;// 注意,这里是getObjectVolatile这个方法,这个方法的意思就是,别的线程要是修改了segment[k],这个线程是可见的。if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {// 这里看到为什么之前要初始化 segment[0] 了,// segment[0] 就相当于一个初始化模板,// 使用当前 segment[0] 处的数组长度和负载因子来初始化 segment[k],// 为什么要用“当前”,因为 segment[0] 可能早就扩容过了。Segment<K,V> proto = ss[0]; // 获取当前 segment[0] 作为初始化模板。int cap = proto.table.length;float lf = proto.loadFactor;int threshold = (int)(cap * lf);// 初始化 segment[k] 内部的哈希表。HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];// 再次检查一遍该槽是否被其他线程初始化了。// 也就是在做上面那些操作时,看看是否有别的线程操作过 segment[k]。if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))== null) { Segment<K,V> s = new Segment<K,V>(lf, threshold, tab); // 构建新的 segment 对象。// 再次检查 segment [k] 是否为null。// 注意,这里是while,之前的if,也是起到如果下面操作失败,再次检查的作用。while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))== null) {if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))// CAS操作,这里的比较并交换在CPU里面就是一条指令,保证原子性的。// 不存在那种比较完毕之后的间隙,突然切换到别的线程来修改这个值的情况。break;}}}// 返回 segment 对象。// 这里返回的seg可能是自己new的,也可能是别的线程new的,反正只要其中一个就好了。return seg;
}
scanAndLockForPut方法(获取写入锁)
- 我们可以在上面的方法中看到这个方法的身影,这个方法也就是并发环境下控制的重要方法之一。
private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {// 简单来说就是拿到段表中某个桶中的哈希表数组中通过hash计算的那个下标下的第一个节点。HashEntry<K,V> first = entryForHash(this, hash);// 备份一下当前节点的内容。HashEntry<K,V> e = first;// 辅助变量。HashEntry<K,V> node = null;int retries = -1; // 用于记录获取锁的次数。// 循环获取锁。// 如果获取失败,就会去进行一些准备工作。while (!tryLock()) {// 辅助变量用于重复检查,// 用来检查对应段表中那个桶上的哈希表数组中对应索引桶处,之前取出来的第一个节点是否还是我们之前取得那个。HashEntry<K,V> f; // to recheck first below// 准备工作。// 因为准备工作也不需要每次循环都去做对吧,最好的预期,做一次准备工作就够了。if (retries < 0) {// 判断段表中对应桶中的哈希表的对应桶上的节点 HashEntry 是不是还没被初始化。if (e == null) {if (node == null) // speculatively create node// 进到这里说明数组该位置的链表是空的,没有任何元素。// 当然,进到这里的另一个原因是 tryLock() 失败,所以该槽存在并发,不一定是该位置。// 将我们即将插进去的元素,构建成一个HashEntry节点对象。node = new HashEntry<K,V>(hash, key, value, null);// 将 retries 赋值为0,不让准备工作重复执行。retries = 0;}// 否则的话,判断 key 是否有重复的。else if (key.equals(e.key))// 将 retries 赋值为0,不让准备工作重复执行。retries = 0;else// 否则顺着链表往下走。e = e.next;}// 重试次数如果超过 MAX_SCAN_RETRIES(单核1多核64),那么不抢了,进入到阻塞队列等待锁,避免cpu空转。// lock() 是阻塞方法,直到获取锁后返回。else if (++retries > MAX_SCAN_RETRIES) {lock();break;}else if ((retries & 1) == 0 && // 偶数次数才进行后面的判断。// 这个时候出现问题了,那就是有新的元素进到了链表,成为了新的表头。// 也可以说是链表的表头被其他线程改变了。// 所以这边的策略是,相当于重新走一遍这个 scanAndLockForPut 方法。(f = entryForHash(this, hash)) != first) {// 此时怎么做呢,// 别的线程修改了该segment的节点,重新赋值e和first为最初值,和第一二行代码一样的效果。e = first = f; // re-traverse if entry changedretries = -1;}}// 将准备工作制作好的节点返回。return node;
}
- 源码是如何判断segment中某个地方被动过的呢?
- JDK1.7中链表使用的是头插法,所以判断第一个节点是否不一样就对了。
entryForHash(获取对应链表的节点)
- 该方法是用来获取段表中指定桶中哈希表对应索引处的桶中的链表的第一个节点。
static final <K,V> HashEntry<K,V> entryForHash(Segment<K,V> seg, int h) {HashEntry<K,V>[] tab;//赋值变量,用于记录当前 segment 中的哈希表。return (seg == null || (tab = seg.table) == null) ? null :// 判断当前 HashEntry 数组是否为 null。(HashEntry<K,V>) UNSAFE.getObjectVolatile // 如果是 null,则直接返回 null。(tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);// 通过 hash值计算下标,然后取出哈希表中对应下标的第一个HashEntry节点。
}
rehash(扩容)
- 在 ConcurrentHashMap 中,扩容这个概念只针对于段表中的每个桶中的哈希表。
- Segments段表 是无法扩容的。
- 在 **put方法 **中被调用。
- 该方法不需要考虑并发,因为到这里的时候,是持有该桶的独占锁的。
// 传入的参数 node 是这次扩容后,需要添加到新的数组中的数据。
private void rehash(HashEntry<K,V> node) {// 用来存储待扩容槽中的哈希表旧表。HashEntry<K,V>[] oldTable = table;// 用来存储待扩容槽中的哈希表旧表的容量。int oldCapacity = oldTable.length;// 新的容量为旧容量的 2 倍。int newCapacity = oldCapacity << 1;// 设置新的扩容阈值。threshold = (int)(newCapacity * loadFactor);// 用上面的参数创建新的哈希表。HashEntry<K,V>[] newTable =(HashEntry<K,V>[]) new HashEntry[newCapacity];// 新的掩码,如从 16 扩容到 32,那么 sizeMask 为 31,对应二进制 000...00011111。int sizeMask = newCapacity - 1;// 遍历原数组,老套路,将原哈希表索引 i 处的链表拆分到新哈希表索引 i 和 i+oldCap 两个位置,高位和低位。for (int i = 0; i < oldCapacity ; i++) {// e 是链表的第一个元素。HashEntry<K,V> e = oldTable[i];// 如果这条链不是空链的话。if (e != null) {HashEntry<K,V> next = e.next;// 计算当前节点应该放置在新数组中的位置,// 假设原哈希表长度为 16,e 在 oldTable[3] 处,那么 e 在新哈希表中的索引 idx 只可能是 3 或者是 3 + 16 = 19。int idx = e.hash & sizeMask;if (next == null) // 该位置处只有一个元素。// 直接将该节点设置到这里。newTable[idx] = e;else { // Reuse consecutive sequence at same slot// e 是链表表头。HashEntry<K,V> lastRun = e;// idx 是当前链表的头结点 e 的新位置。int lastIdx = idx;// 下面这个 for 循环会找到一个 lastRun 节点,这个节点之后的所有元素是将要放到一起的。for (HashEntry<K,V> last = next;last != null;last = last.next) {int k = last.hash & sizeMask;if (k != lastIdx) {lastIdx = k;lastRun = last;}}// 将 lastRun 及其之后的所有节点组成的这个链表放到 lastIdx 这个位置。newTable[lastIdx] = lastRun;// 下面的操作是处理 lastRun 之前的节点,// 这些节点可能分配在另一个链表中,也可能分配到上面的那个链表中。for (HashEntry<K,V> p = e; p != lastRun; p = p.next) {V v = p.value;int h = p.hash;int k = h & sizeMask;HashEntry<K,V> n = newTable[k];newTable[k] = new HashEntry<K,V>(h, p.key, v, n);}}}}// 将新来的 node 放到新数组中刚刚的 两个链表之一 的 头部int nodeIndex = node.hash & sizeMask; // add the new nodenode.setNext(newTable[nodeIndex]);newTable[nodeIndex] = node;table = newTable;
}
remove(删除)
- 整个操作是先定位到段,然后委托给段的remove操作。
- 当多个删除操作并发进行时,只要它们所在的段不相同,它们就可以同时进行。
public V remove(Object key) { // 得到待删除的key的哈希值。hash = hash(key.hashCode()); // 根据哈希值找到对应的段表中的桶,并对该桶进行删除操作。return segmentFor(hash).remove(key, hash, null);
}
V remove(Object key, int hash, Object value) { // 上锁lock(); try { int c = count - 1; HashEntry<K,V>[] tab = table; // 段表中对应桶中的哈希表。int index = hash & (tab.length - 1); // 定位key在哈希表中的位置。HashEntry<K,V> first = tab[index]; // 记录链表头节点。HashEntry<K,V> e = first; // 再备份链表头节点。// 链表不为空并且当前节点不为要删除节点的话遍历。// 遍历到要删除节点终止。while (e != null && (e.hash != hash || !key.equals(e.key))) e = e.next; V oldValue = null; // 找到了这个要删除的节点。if (e != null) { // 存待删节点的数据域。V v = e.value; if (value == null || value.equals(v)) { // 开始删除。oldValue = v; // All entries following removed node can stay // in list, but all preceding ones need to be // cloned. ++modCount; // 结构更改。HashEntry<K,V> newFirst = e.next; // 存储要删除节点的下一个节点为新的头节点。// 从链表表头开始遍历。for (HashEntry<K,V> p = first; p != e; p = p.next) // 遍历到待删除结点。// HashEntry 中的 next 是 final。// 一经赋值以后就不可修改。// 一局将待删除节点之前的节点都弄到连到待删除节点的下一个节点。newFirst = new HashEntry<K,V>(p.key, p.hash, newFirst, p.value); // 最后再将哈希表对应桶中的原来的链丢弃,将以待删除节点下一个节点为头节点的链表填入。tab[index] = newFirst; count = c; // write-volatile } } return oldValue; } finally { unlock(); } }
- 至于节点为什么要设置为 final 不变性,这跟不变性的访问不需要同步从而节省时间有关。
- 这样节点在插入之后就不可能改变。
ConcurrentHashMap - JDK 1.7 并发问题分析
-
看了这几个核心常用方法之后,发现唯一对结构进行了改变的也就只有 put 和 remove 方法,我们来讨论一下这俩方法的并发问题。
-
put 方法的线程安全性:
- 在插入的过程中,如果桶没被初始化的话,将用 segment[0] 来作为模板以 CAS 来初始化,保证在初始化的过程中不被别的线程打扰。
- JDK1.7 中添加节点到链表的操作是插入到链表表头的,所以这个时候 get 方法已经遍历到链表中间,他是不会被影响的。
- 但是如果,get 方法在 put 方法之后,需要保证刚刚插入表头的节点被读取,他们说这个依赖于 setEntryAt 方法中使用的 UNSAFE.putOrderedObject 来解决。
- 扩容和 HashMap 一样是新创建了新的哈希表,然后进行迁移数据,最后面将 newTable 设置给属性 table。
- 所以此时如果一个线程执行 get 方法的途中另一个线程执行 put 方法,那么也是没问题的,因为你要查找的肯定是旧的链表值当中的值。
- 如果是 put 方法先做,那么也没有问题,因为 table 是被 volatile 关键字修饰,线程之间透明。
-
remove 方法的线程安全性:
-
如果 get 方法的途中另一个线程执行 remove 方法,如果此节点是头结点,那么需要将头结点的 next 设置为数组该位置的元素,table 虽然使用了 volatile 修饰,但是 volatile 并不能提供数组内部操作的可见性保证,所以源码中使用了 UNSAFE 来操作数组,请看方法 setEntryAt。
著作权归https://pdai.tech所有。 链接:https://www.pdai.tech/md/java/thread/java-thread-x-juc-collection-ConcurrentHashMap.html
-
如果要删除的节点不是头结点,它会将要删除节点的后继节点接到前驱节点中,这里的并发保证就是 next 属性是 volatile 的。
著作权归https://pdai.tech所有。 链接:https://www.pdai.tech/md/java/thread/java-thread-x-juc-collection-ConcurrentHashMap.html
-
但是如果先 remove 再 get 的话没有问题。
-
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
相关文章
- 微软云强劲增长的背后,是全新的人工智能黑科技
众所周知,近年来微软Azure云计算平台一直保持着强劲的增长势头。在上一个财季的财报中,微软CEO Satya Nadella表示,数字技术是通胀经济中的去通胀之力,不论是大企业还是小企业,都能通过构建自己的技术强度而提高生产力…...
2024/4/14 5:54:56 - MySQL中的单行函数
目录不同DBMS函数的差异MySQL的内置函数及分类数值函数基本函数角度与弧度互换函数三角函数指数与对数进制间的转换字符串函数流程控制函数不同DBMS函数的差异 我们在使用 SQL 语言的时候,不是直接和这门语言打交道,而是通过它使用不同的数据库软件&…...
2024/4/14 5:54:56 - 全网最完整的性能测试流程
目录 一、准备工作 二、测试计划 三、测试脚本设计与开发 四、测试执行与管理 五、测试分析 六、性能测试思维导图 一、准备工作 1、系统基础功能验证 性能测试在什么阶段适合实施?切入点很重要!一般而言,只有在系统基础功能测试验证…...
2024/4/14 5:55:31 - 016-学习Redis-主从服用、哨兵、主备切换、集群概念
016-学习Redis-主从服用回顾内容:一、主从讲解:1、redis的优缺点2、主从概念-例子:3、主从概念-提出读写分离二、主从服用-读写分离1.将配置文件改为公用,将重复的配置设为公用。2.创建三个私有的配置文件3.启动三个服务器&#x…...
2024/4/26 11:17:39 - 反射面试题
1.什么是反射 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射…...
2024/4/30 17:06:13 - react-redux的基本使用
目录结构 index.js import React from react import ReactDOM from react-dom import App from ./App import store from ./redux/store ReactDOM.render(<App/>,document.getElementById("root"))//监测redux中状态的改变,如redux的状态发生了改变…...
2024/4/17 19:11:46 - Vue2.0+ElementUI+CI 动态路由设置(踩坑记录)
一、后端数据传输 1.数据库格式 2.后端数据交互格式(json) 二、前端处理 1.静态路由 (1)在router/index.js界面设置静态路由,即必定会显示的页面 export const constantRouterMap [{path: /login, component: () > import(/views/login/index), hidden: tru…...
2024/4/19 8:39:35 - java使用swing窗体,基于rtsp视频监控
上图,后续更新代码...
2024/4/27 0:26:33 - 登录注册代码php
connect.php <?php$server "localhost";$db_username "root";$db_passwd "123456";$dbname "test";//连接数据库$db_link mysqli_connect($server,$db_username,$db_passwd,$dbname);//连接失败if(!$db_link){die("Ca…...
2024/4/14 5:55:16 - 翻译作业8
Prerequisites? | JXNUOJ 翻译 描述 新生佛兰迪已经选择了 k 门课程。为了符合学位要求,他必须从几个类别中选修课程。根据他的课程选择,你能向佛兰迪保证他会毕业吗? 输入 输入由几个测试用例组成。对于每种用例,输入的第一行…...
2024/4/15 17:23:06 - 练习:异常
定义三个整数变量a,b,c 判断能否构成一个三角形 如果不能请抛出一个自定义异常 public class Triangle {public static void main(String[] args) {try {isTriangle();} catch (TriangelException e) {System.out.println(e.getMessage());}}public static void isTriangle()…...
2024/4/20 5:48:38 - database连接错误(备忘)
显示错误如下: 解决办法:在Database后加上"?serverTimezoneUTC"即可...
2024/4/18 21:50:52 - vue入口文件
package.json 执行npm run dev 的时候在 rollup 后面传入了 scripts/config.js配置文件TARGET 后面的值是打包的版本,web是代表web平台下,full是完整版,dev开发版不对代码进行压缩 scripts/config.js config.js是一个基于node的模块&#x…...
2024/4/14 5:56:22 - 余弦定理计算两个句子的相似度
1、余弦计算相似度 余弦相似度用向量空间中两个向量夹角的余弦值作为衡量两个个体间差异的大小。余弦值越接近1,就表明夹角越接近0度,也就是两个向量越相似,这就叫"余弦相似性"。 对于多个不同的文本或者短文本对话消息要来计算他们…...
2024/4/14 5:56:17 - 固件库应用之点灯
文章目录stm32固件库点灯编程步骤1. 使能 GPIO 端口时钟1.1 为何要使能 GPIO 端口时钟1.2 怎么使能一个时钟1.3 应该使能哪一个时钟1.4 开启时钟程序2. 初始化 GPIO 目标引脚为推挽输出模式2.1 有哪些输出模式及使用场景2.2 GPIO初始化步骤2.2.1 定义GPIO初始化结构体并赋值2.2…...
2024/4/25 15:29:20 - JAVA中ThreadLocal详解
Java中的ThreadLocal详解 - 夏末秋涼 - 博客园...
2024/4/14 5:55:57 - 练习:常用类
1.制作一个工具类:DateUtil,进行日期和字符串之间的格式转换. 定义两个方法: 一个用于将字符串日期转为Date类型,并返回该Date类型 一个用于将Date类型转为指定格式的字符串形式,并返回该字符串 public class DateUtil {public static void main(String[] args) {DateUtil.t…...
2024/4/19 12:04:22 - 1153: 简易版最长序列(求帮我指点一下)
我的代码在自己的编译器上运行正确,而且得出的数据正确(试了一大部分数据),但在郑轻oj上提交显示运行错误,求大佬帮忙指点一下,感谢,感谢!!! 题目描述 : 给你…...
2024/4/20 12:45:03 - Java多线程中的锁
死锁、活锁、饥饿是关于多线程是否活跃、出现的运行阻塞障碍问题,如果线程出现了这三种情况,即线程不再活跃,不能再正常地执行下去 死锁 死锁是多线程中最差的一种情况,多个线程相互占用对方的资源锁,而又相互等对方释…...
2024/4/14 5:57:08 - Origin 快捷作图
Origin 快捷作图修改字体修改字体 Origin 作图默认是宋体,而论文中用到的图片对文字要求多为 Arial / Times New Roman ,因此可以在Origin软件中修改其默认设置,减少重复操作 设置 --> 选项 --> 文本字体 --> 根据需要改为对应的字体…...
2024/4/14 14:55:09
最新文章
- QT学习之QtXlsx
背景: 本来我是想提取xml中的信息存在xlsx文件中的,网上很多说是使用QtXlsx; 于是我找了一些帖, 像:https://www.cnblogs.com/liming19680104/p/14398459.html; 大家的说法都是安装第三方库到QT中ÿ…...
2024/4/30 17:10:19 - 梯度消失和梯度爆炸的一些处理方法
在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言,在此感激不尽。 权重和梯度的更新公式如下: w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...
2024/3/20 10:50:27 - 开源项目one-api的k8s容器化部署(上)-- 制作镜像及部署准备
一、背景 最近需要对开源项目one-api进行k8s容器化部署,主要分以下几个步骤: 制作docker镜像申请mysql和redis数据库docker-compose部署方式k8s部署方式 整个的篇幅比较长,将会分成上下两篇来阐述。 二、制作docker镜像 开源项目one-api…...
2024/4/23 7:41:44 - OpenHarmony开发-连接开发板调试应用
在 OpenHarmony 开发过程中,连接开发板进行应用调试是一个关键步骤,只有在真实的硬件环境下,我们才能测试出应用更多的潜在问题,以便后续我们进行优化。本文详细介绍了连接开发板调试 OpenHarmony 应用的操作步骤。 首先…...
2024/4/30 7:36:18 - Kafka入门到实战-第五弹
Kafka入门到实战 Kafka常见操作官网地址Kafka概述Kafka的基础操作更新计划 Kafka常见操作 官网地址 声明: 由于操作系统, 版本更新等原因, 文章所列内容不一定100%复现, 还要以官方信息为准 https://kafka.apache.org/Kafka概述 Apache Kafka 是一个开源的分布式事件流平台&…...
2024/4/30 4:52:22 - 【外汇早评】美通胀数据走低,美元调整
原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...
2024/4/29 23:16:47 - 【原油贵金属周评】原油多头拥挤,价格调整
原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...
2024/4/29 6:03:24 - 【外汇周评】靓丽非农不及疲软通胀影响
原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...
2024/4/29 2:29:43 - 【原油贵金属早评】库存继续增加,油价收跌
原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...
2024/4/29 14:21:50 - 【外汇早评】日本央行会议纪要不改日元强势
原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...
2024/4/27 17:58:04 - 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响
原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...
2024/4/27 14:22:49 - 【外汇早评】美欲与伊朗重谈协议
原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...
2024/4/28 1:28:33 - 【原油贵金属早评】波动率飙升,市场情绪动荡
原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...
2024/4/30 9:43:09 - 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试
原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...
2024/4/27 17:59:30 - 【原油贵金属早评】市场情绪继续恶化,黄金上破
原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...
2024/4/25 18:39:16 - 【外汇早评】美伊僵持,风险情绪继续升温
原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...
2024/4/28 1:34:08 - 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势
原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...
2024/4/26 19:03:37 - 氧生福地 玩美北湖(上)——为时光守候两千年
原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...
2024/4/29 20:46:55 - 氧生福地 玩美北湖(中)——永春梯田里的美与鲜
原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...
2024/4/25 18:39:14 - 氧生福地 玩美北湖(下)——奔跑吧骚年!
原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...
2024/4/26 23:04:58 - 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!
原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...
2024/4/27 23:24:42 - 「发现」铁皮石斛仙草之神奇功效用于医用面膜
原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...
2024/4/28 5:48:52 - 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者
原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...
2024/4/30 9:42:22 - 广州械字号面膜生产厂家OEM/ODM4项须知!
原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...
2024/4/30 9:43:22 - 械字号医用眼膜缓解用眼过度到底有无作用?
原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...
2024/4/30 9:42:49 - 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...
解析如下:1、长按电脑电源键直至关机,然后再按一次电源健重启电脑,按F8健进入安全模式2、安全模式下进入Windows系统桌面后,按住“winR”打开运行窗口,输入“services.msc”打开服务设置3、在服务界面,选中…...
2022/11/19 21:17:18 - 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。
%读入6幅图像(每一幅图像的大小是564*564) 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 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...
win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面,在等待界面中我们需要等待操作结束才能关机,虽然这比较麻烦,但是对系统进行配置和升级…...
2022/11/19 21:17:15 - 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...
有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows,请勿关闭计算机”的提示,要过很久才能进入系统,有的用户甚至几个小时也无法进入,下面就教大家这个问题的解决方法。第一种方法:我们首先在左下角的“开始…...
2022/11/19 21:17:14 - win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...
置信有很多用户都跟小编一样遇到过这样的问题,电脑时发现开机屏幕显现“正在配置Windows Update,请勿关机”(如下图所示),而且还需求等大约5分钟才干进入系统。这是怎样回事呢?一切都是正常操作的,为什么开时机呈现“正…...
2022/11/19 21:17:13 - 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...
Win7系统开机启动时总是出现“配置Windows请勿关机”的提示,没过几秒后电脑自动重启,每次开机都这样无法进入系统,此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一:开机按下F8,在出现的Windows高级启动选…...
2022/11/19 21:17:12 - 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...
有不少windows10系统用户反映说碰到这样一个情况,就是电脑提示正在准备windows请勿关闭计算机,碰到这样的问题该怎么解决呢,现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法:1、2、依次…...
2022/11/19 21:17:11 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...
今天和大家分享一下win7系统重装了Win7旗舰版系统后,每次关机的时候桌面上都会显示一个“配置Windows Update的界面,提示请勿关闭计算机”,每次停留好几分钟才能正常关机,导致什么情况引起的呢?出现配置Windows Update…...
2022/11/19 21:17:10 - 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...
只能是等着,别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚,只能是考虑备份数据后重装系统了。解决来方案一:管理员运行cmd:net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...
2022/11/19 21:17:09 - 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?
原标题:电脑提示“配置Windows Update请勿关闭计算机”怎么办?win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢?一般的方…...
2022/11/19 21:17:08 - 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...
关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!关机提示 windows7 正在配…...
2022/11/19 21:17:05 - 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...
钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...
2022/11/19 21:17:05 - 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...
前几天班里有位学生电脑(windows 7系统)出问题了,具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面,长时间没反应,无法进入系统。这个问题原来帮其他同学也解决过,网上搜了不少资料&#x…...
2022/11/19 21:17:04 - 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...
本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法,并在最后教给你1种保护系统安全的好方法,一起来看看!电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中,添加了1个新功能在“磁…...
2022/11/19 21:17:03 - 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...
许多用户在长期不使用电脑的时候,开启电脑发现电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机。。.这要怎么办呢?下面小编就带着大家一起看看吧!如果能够正常进入系统,建议您暂时移…...
2022/11/19 21:17:02 - 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...
配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!配置windows update失败 还原更改 请勿关闭计算机&#x…...
2022/11/19 21:17:01 - 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...
不知道大家有没有遇到过这样的一个问题,就是我们的win7系统在关机的时候,总是喜欢显示“准备配置windows,请勿关机”这样的一个页面,没有什么大碍,但是如果一直等着的话就要两个小时甚至更久都关不了机,非常…...
2022/11/19 21:17:00 - 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...
当电脑出现正在准备配置windows请勿关闭计算机时,一般是您正对windows进行升级,但是这个要是长时间没有反应,我们不能再傻等下去了。可能是电脑出了别的问题了,来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...
2022/11/19 21:16:59 - 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...
我们使用电脑的过程中有时会遇到这种情况,当我们打开电脑之后,发现一直停留在一个界面:“配置Windows Update失败,还原更改请勿关闭计算机”,等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢࿰…...
2022/11/19 21:16:58 - 如何在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