Java-01-源码篇-04集合-05-ConcurrentHashMap(1)

1.1 加载因子

        加载因子(Load Factor)是用来决定什么时候需要扩容的一个参数。具体来说,加载因子 = 当前元素数量 / 桶的数量,当某个桶的元素个数超过了 桶的数量 × 加载因子 时,就会触发扩容。

        我们都知道 ConcurrentHashMap 的默认大小是16,默认加载因子是:0.75。解析一下什么叫做加载因子,简单的讲就是触发扩容机制的阈值。比如我的集合容量大小是 100,加载因子是:0.75,那么触发扩容的阈值就是(100 x 0.75 = 75; 当数量达到75的时候触发扩容机制。而不是在容量达到满100的时候才触发扩容机制)。通过设置加载因子的好处如下:

平衡性能与空间利用率

        较低的加载因子(如 0.1)会使得哈希表在元素较少时扩容,从而减少哈希冲突,提高查找、插入和删除操作的性能。

        较高的加载因子(如 0.9)会延迟扩容,节省内存空间,但可能会增加哈希冲突,降低性能。

        在 ConcurrentHashMap 中,默认的加载因子是 0.75。这是一个经验值,能够在大多数情况下提供较好的性能和空间利用率。

控制加载频率        扩容是一个比较耗时的操作,因为它需要重新计算元素复制到新的桶里面。通过调整加载因子,在性能与内存之间达到一个平衡。

   

1.2 面试题:加载因子默认为什么是0.75,而不是9,或者5等等之类其他数字

        所以这道题面试题问的就是加载因子的作用,从平衡性能和内存,以及控制加载频率的角度去回答即可。

        比如:如果设置的加载因子过高(例如 0.1 或 0.9),意味着 ConcurrentHashMap 会在桶中存储更多的元素,减少扩容的次数,减少内存的开销。但是这样会也导致桶的元素过多,链表的长度过长。导致访问元素时的查找效率下降,发生更多的哈希冲突(hash collision)几率也逐渐增大。
        较低的加载因子(0.1)意味着 ConcurrentHashMap 会在桶中存储少量的元素,但扩容的次数上升,内存的开销增多。频繁的扩容处理会导致性能开销增大。

        所以取一个在性能与内存之间很好的折中植就是0.75。0.75 是一个经过大量测试验证的值,适用于大多数常见场景。它在查找效率、内存使用和扩容操作之间取得了一个很好的折中。

        当然,如果具体也可以根据需求调整
        对于性能要求高的应用

      尽量减少哈希冲突,提高查找、插入和删除操作的性能。降低加载因子(例如设置为 0.5 或更低)。

        【原因】

                较低的加载因子会使得哈希表在元素数量较少时触发扩容,从而减少哈希冲突。

                每个桶中的元素数量更少,查找、插入和删除操作的时间复杂度更接近 O(1)。

        【代价】

                哈希表的容量会更大,占用更多的内存空间

                扩容操作会更频繁,可能会带来一定的性能开销

        对于内存占用比较敏感的应用

        尽量减少内存占用,提高空间利用率。

        【原因】

                较高的加载因子会延迟扩容,使得哈希表在元素数量接近容量时才触发扩容。

                内存利用率更高,适合内存资源有限的场景。

        【代价】

                哈希冲突的概率会增加,尤其是在元素数量接近容量时,性能可能会下降。

                查找、插入和删除操作的时间复杂度可能会增加。


1.3 初始化功能 initTable()

首先,初始化功能在第一次put时会触发。代码分析如下
我们进一步观察initTable() 方法

public V put(K key, V value) { return putVal(key, value, false); }final V putVal(K key, V value, boolean onlyIfAbsent) {if (key == null || value == null) throw new NullPointerException();int hash = spread(key.hashCode());int binCount = 0;for (Node<K,V>[] tab = table;;) {Node<K,V> f; int n, i, fh; K fk; V fv;// 当集合第一次添加元素时,触发初始化if (tab == null || (n = tab.length) == 0) tab = initTable(); ...... // 忽略其他代码


我们进一步观察initTable() 方法

/** ConcurrentHashMap 的容器*/
transient volatile Node<K,V>[] table; 
/** sizeCtl的作用 sizeCtl (< 0) 负值时:sizeCtl = -1; 表示正在进行初始化其他负值(如 -2,-3 等):表示正在进行扩容,并且数字的大小指示了当前有多少个线程正在进行扩容操作。例如,-2 表示有一个线程在进行扩容,-3 表示有两个线程在进行扩容。sizeCtl = 0 时:当 sizeCtl == 0 时,表示表格的大小使用默认值。正值(用于表格大小的控制):在表格初始化完成之后,sizeCtl 变成一个正值,表示下一个阈值,即表格的元素数量达到此值时,需要进行扩容。例如,如果 sizeCtl 设置为 16,则表示当表格的元素数量达到 16 时,应该触发扩容操作。【总结】sizeCtl 控制表格的初始化、扩容,以及扩容过程中的线程同步。根据其值的不同,sizeCtl 在 ConcurrentHashMap 中承担着不同的角色
*/
private transient volatile int sizeCtl;/** Unsafe 类,里面提供CPU相关的操作指令,比如CAS。CAS: compareAndSet 比较并交换,CAS 通过比较变量的当前值与预期值是否相同,如果两值相同则将变量的值更新为新值,否则不做任何操作并且还支持跳过JVM内存管理相关操作
*/
private static final Unsafe U = Unsafe.getUnsafe();/** 通过 Unsafe 不安全类,绕过JVM的内存管理机制,直接访问硬件的内存获取 ConcurrentHashMap.class 类里面的 sizeCtl 的字段值
*/
private static final long SIZECTL= U.objectFieldOffset(ConcurrentHashMap.class, "sizeCtl");private final Node<K,V>[] initTable() {// 声明两个局部变量,而不直接使用全局的table, sizeCtl // 避免直接多次访问全局的 table,提高代码的效率。Node<K,V>[] tab; int sc;// 进入 while 循环时,首先检查 table 是否为 null 或者是否为空(长度为 0)。// 如果是,表示 table 还没有初始化while ((tab = table) == null || tab.length == 0) {// sizeCtl 负值集合处于初始化(-1)或者是扩容阶段(-2,-3....)if ((sc = sizeCtl) < 0) Thread.yield(); // 所以当前线程礼让出CPU资源(CPU时间分片)// U.compareAndSetInt 该方法就是int类型的CAS// 如果 sizeCtl 的当前值 不小于 0,我们尝试通过 CAS(Compare-And-Set)操作 // 来将 sizeCtl 的值从 sc 修改为 -1,表示当前线程正在进行初始化操作。else if (U.compareAndSetInt(this, SIZECTL, sc, -1)) {try {// 再次检查 table 是否已经初始化,如果没有,则进行初始化。if ((tab = table) == null || tab.length == 0) { // 如果 sc(sizeCtl) 有正值,则表示当前容量大小,否则使用默认大小16int n = (sc > 0) ? sc : DEFAULT_CAPACITY;// 初始化:就是创建一个指定的大小数组赋值给table,并记录sizeCtl的值。@SuppressWarnings("unchecked")Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];table = tab = nt;// 计算并更新 sc 的值。sc 记录下一个阈值,即元素数量达到多少时需要扩容。// 此计算方法是基于表格大小的 75%(即 n - (n >>> 2),// 实际上就是 n * 0.75)。当元素数量超过该阈值时,将会触发扩容。sc = n - (n >>> 2); // 可以理解成这样的 0.75 加载因子是写固定的0.75。}} finally {sizeCtl = sc;}break;}}return tab;
}

public class Test {public static void main(String[] args) {ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();map.put("key1", "value1");System.out.println(map);}
}


 


1.4 总结

ConcurrentHashMap 的初始化,如果是第一次进行初始化,则容量大小为 16,加载因子(0.75)计算的阈值为12。ConcurrentHashMap 由于是线程安全的,在初始化的体现通过 Unsafe不安全类提供CAS操作来实现。通过CAS 判断 sizeCtl 值。
sizeCtl 用于控制集合的扩容,初始化。比如 -1 表示正在初始化,其他的负数表示正在扩容 -2 一个线程扩容,-3 两个线程,以此类推,
sizeCtl = 0 表示 初始化完成了。
sizeCtl > 0 表示触发扩容的阈值。第一次为12 。



1.5 构造方法

    /** 最大值 */private static final int MAXIMUM_CAPACITY = 1 << 30;/** 默认容量大小 */private static final int DEFAULT_CAPACITY = 16;/** 默认加载因子大小 */private static final float LOAD_FACTOR = 0.75f;public ConcurrentHashMap() {}/*** @param initialCapacity 初始容量*/public ConcurrentHashMap(int initialCapacity) {this(initialCapacity, LOAD_FACTOR, 1);}/*** @param initialCapacity 初始容量* @param loadFactor 加载因子*/public ConcurrentHashMap(int initialCapacity, float loadFactor) {this(initialCapacity, loadFactor, 1);}/*** @param initialCapacity 初始容量* @param loadFactor 加载因子* @param concurrencyLevel 内部更新线程个数,估计将同时更新映射的线程数。* 此值用于帮助调整内部数据结构的大小,以减少线程间的竞争。*/public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)throw new IllegalArgumentException(); // 非法值,比如容量是负数之类// 如果初始容量小于并发级别,调整初始容量if (initialCapacity < concurrencyLevel) initialCapacity = concurrencyLevel;   // 确保桶的数量至少与并发线程数一样多// 根据初始容量和负载因子计算内部表格的大小long size = (long)(1.0 + (long)initialCapacity / loadFactor);// 确保计算出的大小不超过最大容量int cap = (size >= (long) MAXIMUM_CAPACITY) ?MAXIMUM_CAPACITY : tableSizeFor((int)size);this.sizeCtl = cap;}/** 该方法支持传入一个map */public ConcurrentHashMap(Map<? extends K, ? extends V> m) {this.sizeCtl = DEFAULT_CAPACITY; // 阈值大小为 16putAll(m);}

1.6 总结

        ConcurrentHashMap 的构造方法,提供手动设置初始容量和加载因子以及内部线程个数。限制容量的范围大小 0 到 MAXIMUM_CAPACITY(1 << 30 )。
并且从里面也可以得知,在设置阈值大小的时候都是通过 sizeCtl (size Control ) 来接收。所以从这样也可以知道初始化的时候,用它来进行一个参看和判断。正数的就表示扩容阈值大小,等触发扩容,或者是初始化时,将其sizeCtl 改为 负值。 -1 表示 初始值,-2 表示一个线程扩容,-3 表示两个线程扩容,以此类推,毕竟ConcurrentHashMap支持线程安全。扩容完之后又将计算新的阈值大小赋值给 sizeCtl.
所以才说 sizeCtl 是掌管控制ConcurrentHashMap的初始化,扩容的一个重要变量。


1.7 新增功能

public V put(K key, V value) { return putVal(key, value, false); }


1.8 面试题:直接从上到下,添加一次就可以吗?为什么要通过一个for 进行无限循环添加?

        其实这并非是集合章节的知识点,这个是多线程章节的知识点。因为ConcurrentHashMap支持多线程并发,所以需要考虑到多线程的情况下。

        作用:持续重试和处理并发操作
        在并发数据结构中,许多操作(如插入、删除、扩容等)都可能需要在多个线程同时访问的环境下执行。使用无限循环可以确保:
        重试操作:如果某个操作(如插入、更新或删除)由于并发冲突(例如,另一个线程正在修改同一个桶或节点)而无法立即成功执行,程序可以在内部重新尝试执行该操作。
        保证条件满足后继续执行:在并发环境下,操作的执行结果通常依赖于其他线程的状态,因此可能需要在条件不满足时继续尝试直到成功。
        处理扩容等需要重新计算大小的操作:
        例如,在 ConcurrentHashMap 中,当插入元素导致负载过高时,可能需要扩容哈希表。扩容通常涉及以下步骤:
        1 重新计算哈希表的大小
        2 重新分配内存
        3 重新散列所有现有元素
        这些操作往往需要多次尝试,直到哈希表的大小适合当前元素的数量。在此过程中,循环结构可以帮助处理这一过程,直到操作完成。

        确保原子性操作
        在高并发环境下,有时需要使用 CAS(比较并交换) 或类似的机制(JDK7 的线程安全是通过 分段锁 Segment )来确保线程安全。例如,可能需要反复检查某个条件(如节点是否已被更新),直到确认操作成功。

        避免死锁或竞争条件
        使用无限循环可以防止由于竞争条件引起的操作失败。通过在循环中不断尝试,确保操作的成功执行,同时避免因并发冲突或竞争条件导致程序永久卡住。

        操作的延迟执行
        有些操作可能会依赖某些状态或条件,并且需要在特定的时机才执行。例如,在表格扩容时,可能需要等到某些条件满足才继续执行下一个步骤。无限循环允许在这些条件准备好时才继续操作。


1.9下面看新增逻辑分析

final V putVal(K key, V value, boolean onlyIfAbsent) {// 这里可以说明 ConcurrentHashMap 不支持key,value为null// 这样设计是为了避免二义性:何为二义性。/**在并发环境下,null 值可能会导致语义上的歧义,无法区分以下两种情况:key 不存在:调用 get(key) 返回 null,可能表示该 key 不存在。key 存在,但 value 为 null:调用 get(key) 返回 null,也可能表示该 key 存在,但其对应的 value 是 null。这种二义性会导致程序逻辑难以处理,尤其是在并发场景下,可能会引发难以调试的问题。*/if (key == null || value == null) throw new NullPointerException();// 获取键 key 的哈希值,用于确定元素的位置,spread()进一步优化并降低哈希冲突的概率int hash = spread(key.hashCode()); /** 记录当前桶中的元素个数 */int binCount = 0;// 忽略添加逻辑addCount(1L, binCount); // 记录当前桶的元素数量+1return null;}

        补充一下ConcurrentHashMap 当中桶 (bin) 指的是什么,指定的就是 Node<K, V>[] table 。table[] 有多大,就有多少个桶,而我们知道 table 。是在初始化或者扩容的时候进行更新。所以这里的 binCount 就是指定当前桶的元素个数。而元素放在哪个桶的位置,是由计算的hash值来决定的。

        新增逻辑大概考虑到五种情况

        【情况一】:第一次新增触发初始化

        【情况二】: 矫正hash计算得出的索引没有桶

        【情况三】:新增遇到正在扩容处理,通过helpTransfer 来处理

        【情况四】:处理key值相同,value不覆盖(ConcurrentHashMap默认都是key相同,value覆盖,有参数 onlyIfAbsent来决定,onlyIfAbsent 意思就是:是否只有值缺席的时候才添加。)

        【情况五】:正常插入元素。(链表树化成红黑树也在这里处理)

final V putVal(K key, V value, boolean onlyIfAbsent) {if (key == null || value == null) throw new NullPointerException();int hash = spread(key.hashCode());int binCount = 0;for (Node<K,V>[] tab = table;;) {Node<K,V> f; int n, i, fh; K fk; V fv;// 【情况一】:第一次新增触发初始化if (tab == null || (n = tab.length) == 0)tab = initTable();/*** 【情况二】:如果计算得到索引所对应的桶为null: (f = null) == null*   当索引对应的桶为null,就通过CAS重新将元素添加到桶里面。并break;表示新增结束*  额外讲解:避免了哈希值超过桶的范围*     i 表示 索引,(n - 1) & hash 作用*     假如当桶大小只有32时,但是计算出的索引超过了32,比如 55。*     n = 32,n - 1 = 31(二进制 11111)。*	   如果 hash = 55,二进制表示为 110111。*     (55 & 31) 的结果是 55 & 11111 = 111,即索引为 7。*/else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))break;                   // no lock when adding to empty bin}/*** 【情况三】:如果桶的哈希值为 MOVED,表示该位置正在进行哈希表扩容操作。* 此时调用 helpTransfer() 来帮助完成扩容*/else if ((fh = f.hash) == MOVED)tab = helpTransfer(tab, f);/*** 【情况四】:* 如果 onlyIfAbsent 为 true* key 相同,value就不更新了。* 而put方法,put(K key, V value) { return putVal(key, value, false); }* 很显然是key相同,value覆盖更新 */else if (onlyIfAbsent // check first node without acquiring lock&& fh == hash&& ((fk = f.key) == key || (fk != null && key.equals(fk)))&& (fv = f.val) != null)return fv;// 【情况五】:正常的插入与更新else {V oldVal = null;synchronized (f) { //加锁 synchronized (f),确保线程安全。if (tabAt(tab, i) == f) {/** * 链表(fh >= 0):遍历当前桶中的链表,查找键是否存在。* 如果存在,更新值;如果不存在,将新节点添加到链表末尾。* 俗称:key 相同时,key不变,value覆盖*/if (fh >= 0) {binCount = 1;for (Node<K,V> e = f;; ++binCount) {K ek;// key 冲突,value覆盖逻辑if (e.hash == hash &&((ek = e.key) == key ||(ek != null && key.equals(ek)))) {oldVal = e.val;if (!onlyIfAbsent)e.val = value;break;}// key 不冲突,直接添加key, value // (ConcurrentHashMap里面的元素都是以Node为基础。)// 所以通过新增的put(key, value); 都会封装成一个Node 元素。// 这个Node里面存储的真正key和valueNode<K,V> pred = e;if ((e = e.next) == null) {pred.next = new Node<K,V>(hash, key, value);break;}}}// 如果当前桶已经是红黑树,则通过 putTreeVal() 方法处理。else if (f instanceof TreeBin) {Node<K,V> p;binCount = 2;if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,value)) != null) {oldVal = p.val;if (!onlyIfAbsent)p.val = value;}}// 保留节点处理(f instanceof ReservationNode):// 如果桶中是保留节点,则抛出异常else if (f instanceof ReservationNode)throw new IllegalStateException("Recursive update");}}// 链表树化:桶中的元素数量大于等于 TREEIFY_THRESHOLD(默认 8),转红黑树if (binCount != 0) {if (binCount >= TREEIFY_THRESHOLD)treeifyBin(tab, i);if (oldVal != null)return oldVal;break;}}}addCount(1L, binCount);return null;}

2.0 扩容机制 tryPresize

        tryPresize 旨在根据给定的 size 计算新的容量,并尝试扩容哈希表。它会根据表的当前状态,所需容量和线程安全的操作来决定是否进行扩容。

在代码内部我们可以看出,两处需要进行扩容处理。一处是putAll()方法。一个是在新增时有一处是 链表树化的扩容。

public void putAll(Map<? extends K, ? extends V> m) {tryPresize(m.size());for (Map.Entry<? extends K, ? extends V> e : m.entrySet())putVal(e.getKey(), e.getValue(), false);
}
    private final void treeifyBin(Node<K,V>[] tab, int index) {Node<K,V> b; int n;if (tab != null) {if ((n = tab.length) < MIN_TREEIFY_CAPACITY)tryPresize(n << 1);// ...... 忽略其他代码

看扩容逻辑代码

private final void tryPresize(int size) {/** * 扩容的大小 = c* 根据传入的size大小来决定,如果size大于最大值MAXIMUM_CAPACITY,* 那么c就是最大值,调用 tableSizeFor 方法计算一个合适的容量。* tableSizeFor 的作用是返回大于等于给定值的最小 2 的幂次方*/int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :tableSizeFor(size + (size >>> 1) + 1);int sc;/** 这里我们都知道,1.3 初始化功能的时候提到过sizeCtl 有多重身份,sizeCtl < 0 时,表示正在处于初始化或者是扩容当中, -1 表示初始化, -2,-3表示扩容,-2是一条线程,-3是两条线程扩容,依次类推sizeCtl = 0 时,初始化完成了,sizeCtl > 0 时,sizeCtl表示加载的阈值(有容量大小 X 加载因子得来的)*/while ((sc = sizeCtl) >= 0) {Node<K,V>[] tab = table; int n;// 处理初始化if (tab == null || (n = tab.length) == 0) {n = (sc > c) ? sc : c; // n = 本次最终扩容的大小if (U.compareAndSetInt(this, SIZECTL, sc, -1)) { // 通过CAS进行更新try {if (table == tab) {@SuppressWarnings("unchecked")Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];table = nt;// 重新计算 sizeCtl 新的阈值(n - (n >>> 2),即 0.75 * n)// 这里是通过位运算来的。写死了 0.75 加载因子sc = n - (n >>> 2);}} finally {sizeCtl = sc;}}}// 目标容量不在范围内,不处理扩容了// 如果目标容量 c 小于等于当前阈值 sc,或者当前容量 n 已经达到最大容量 MAXIMUM_CAPACITYelse if (c <= sc || n >= MAXIMUM_CAPACITY)break;// 触发扩容机制else if (tab == table) {int rs = resizeStamp(n);if (U.compareAndSetInt(this, SIZECTL, sc,(rs << RESIZE_STAMP_SHIFT) + 2))transfer(tab, null);}}
}

   

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/70699.shtml

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

相关文章

vue3: directive自定义指令防止重复点击

第一章 前言 相信很多小伙伴会在各个渠道上搜如何防止重复点击&#xff0c;之后会推荐什么防抖、节流来避免这一操作&#xff0c;该方法小编就不继续往下说了。接下来说说小编的场景&#xff0c;项目已经完成的差不多了&#xff0c;但是由于之前大家都是直接点击事件调用方法的…

忽略Git文件的修改,让它不被提交

使用Git托管的工程中&#xff0c;经常有这样的需求&#xff0c;希望文件只是本地修改&#xff0c;不提交到服务端。 如果仅仅是本地存在的文件&#xff0c;我们可以通过.gitignore配置避免文件被提交。 有的时候文件是由git托管的&#xff0c;但是我们希望只在本地修改&#…

Zap:Go 的高性能日志库

文章目录 Zap&#xff1a;Go 高性能日志库一、Zap 的核心优势二、快速入门 Zap1. 安装2. 基本用法输出示例 三、Logger 与 SugaredLogger&#xff1a;如何选择&#xff1f;1. **Logger&#xff08;高性能模式&#xff09;**2. **SugaredLogger&#xff08;开发友好模式&#xf…

每日一题——顺时针旋转矩阵

顺时针旋转矩阵 目录 一、问题描述二、解题思路 1. 原地旋转矩阵2. 旋转逻辑3. 代码实现 三、代码解析 1. 参数说明2. 原地旋转逻辑3. 返回矩阵 四、示例测试代码五、复杂度分析 1. 时间复杂度2. 空间复杂度 一、问题描述 以下是内容转换为 CSDN 的 Markdown 格式&#xf…

接雨水的算法

题目 代码 # 接雨水算法 def trap(height):# 1. 特殊情况&#xff1a;数组为空 则返回0if not height:return 0n len(height)# 2. 初始化左右指针&#xff0c;左右最大值&#xff0c;结果left, right 0, n - 1# maxleft代表左边最大值&#xff0c;maxright代表右边最大值max…

会话对象 HttpSession 二、HttpSession失效

session失效有如下几个原因&#xff1a; session.invalidate()方法注销sessionsession超时 <session-config><!-- session的超时时间&#xff0c;以分钟为单位 --><session-timeout>1</session-timeout> </session-config>Cookie被禁用

Jenkins 创建 Node 到 Windows

Jenkins 创建 Node 到 Windows 一. 新建 Node Dashboard -> Manage Jenkins -> Manage Nodes and Clouds Dashboard -> Nodes -> New Node 二. 配置节点 Node&#xff1a;节点名 Description&#xff1a;节点描述 Number of executors&#xff1a;节点最大同…

Opengl常用缓冲对象功能介绍及使用示例(C++实现)

本文整理了常用的opengl缓冲区对象并安排了使用示例 名称英文全称作用简述顶点数组对象Vertex Array Object (VAO)管理 VBO 和 EBO 的配置&#xff0c;存储顶点属性设置&#xff0c;简化渲染流程&#xff0c;避免重复设置状态顶点缓冲区对象Vertex Buffer Object (VBO)存储顶点…

矩阵加减乘除的意义与应用

矩阵加法 数学意义 线性空间的封闭性线性变换的叠加矩阵分解与表示 实际应用 数据聚合与统计图像处理与计算机视觉物理学与工程学动态系统与优化经济学与运筹学信号处理与通信游戏开发与计算机图形学环境科学与地理信息矩阵加法的关键特点 矩阵减法 数学意义线性空间封闭性 线…

【Redis原理】底层数据结构 五种数据类型

文章目录 动态字符串SDS(simple dynamic string )SDS结构定义SDS动态扩容 IntSetIntSet 结构定义IntSet的升级 DictDict结构定义Dict的扩容Dict的收缩Dict 的rehash ZipListZipListEntryencoding 编码字符串整数 ZipList的连锁更新问题 QuickListQuickList源码 SkipListRedisOb…

微信小程序 - 页面跳转(wx.navigateTo、wx.redirectTo、wx.switchTab、wx.reLaunch)

API 跳转 1、wx.navigateTo &#xff08;1&#xff09;基本介绍 功能&#xff1a;保留当前页面&#xff0c;跳转到应用内的某个页面&#xff0c;使用该方法跳转后可以通过返回按钮返回到原页面 使用场景&#xff1a;适用于需要保留当前页面状态&#xff0c;后续还需返回的情…

Qt 中集成mqtt协议

一&#xff0c;引入qmqtt 库 我是将整个头文件/源文件都添加到了工程中进行编译&#xff0c;这样 跨平台时 方便&#xff0c;直接编译就行了。 原始仓库路径&#xff1a;https://github.com/emqx/qmqtt/tree/master 二&#xff0c;使用 声明一个单例类&#xff0c;将订阅到…

分布式之Raft算法

参考&#xff1a; 分布式算法 - Raft算法 | Java 全栈知识体系 Raft 算法详解 | JavaGuide 分布式 | CS-Notes 面试笔记

安装PHPStudy 并搭建DVWA靶场

目录 一、PHPStudy 简介 二、DVWA 简介 三、安装 PHPStudy 四&#xff1a;安装 DVWA 一、PHPStudy 简介 phpstudy傻瓜式的一键启动&#xff0c;支持WAMP、WNMP、LAMP、LNMP&#xff0c;一键切换环境&#xff08;nginxapahce&#xff09;,一键切换PHP版本&#xff08;5.1-7…

孜然单授权系统V2.0PHP授权系统

孜然单授权V1.0系统&#xff0c;延续了2022年开发的孜然多应用授权系统V2.0 变更&#xff1a;多应用变单系统&#xff0c;去除没用的垃圾代码&#xff0c;从0开发&#xff0c;去除了一些没用的功能 完善了开发文档&#xff0c;之前那套是我写着玩的屎山代码&#xff0c;V1.0将展…

红帽7基于kickstart搭建PXE环境

Kickstart 文件是一种配置文件&#xff0c;用于定义 Linux 系统安装过程中的各种参数&#xff0c;如分区、网络配置、软件包选择等。system-config-kickstart 提供了一个图形界面&#xff0c;方便用户快速生成这些配置文件。 用户可以通过图形界面进行系统安装的详细配置&…

怎么合并主从分支,要注意什么

在 Git 中合并主从分支&#xff08;例如将 feature 分支合并到 main 分支&#xff09;是一个常见操作。以下是具体步骤和注意事项&#xff1a; 合并分支的步骤 切换到主分支 git checkout main确保当前在 main 分支。 拉取最新代码 git pull origin main确保 main 分支是最…

Java数据结构第十二期:走进二叉树的奇妙世界(一)

专栏&#xff1a;数据结构(Java版) 个人主页&#xff1a;手握风云 目录 一、树型结构 1.1. 树的定义 1.2. 树的基本概念 1.3. 树的表示形式 二、二叉树 2.1. 概念 2.2. 两种特殊的二叉树 2.3. 二叉树的性质 2.4. 二叉树的存储 三、二叉树的基本操作 一、树型结构 1.…

匹配算法:向下就近原则,向下没有就向上

匹配算法&#xff1a;向下就近原则&#xff0c;向下没有就向上 实现方式一实现方式二总结 实现方式一 private static List<Integer> findMatches(List<Integer> sourceList, List<Integer> searchValues) {List<Integer> sortedList sourceList.stre…

基于 Python Django 的校园互助平台(附源码,文档)

博主介绍&#xff1a;✌Java徐师兄、7年大厂程序员经历。全网粉丝13w、csdn博客专家、掘金/华为云等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3fb; 不…