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.rhkb.cn/news/22982.html

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

相关文章

一文详解U盘启动Legacy/UEFI方式以及GPT/MBR关系

对于装系统的老手而说一直想研究一下装系统的原理&#xff0c;以及面对一些问题时的解决思路&#xff0c;故对以前的方法进行原理上的解释&#xff0c;主要想理解其底层原理。 引导模式 MBR分区可以同时支持UEFI和Legacy引导&#xff0c;我们可以看一下微pe制作的启动盘&#…

【多线程-第三天-NSOperation的练习-tableView异步下载网络图片-下载操作缓存池 Objective-C语言】

一、下载操作缓存池 1.下面我们来看操作缓存池,我们先演示一下问题,看看为什么要加这么一个操作缓存池,什么是操作缓存池,不用管呢,我们先来看啊,首先有什么问题, 看这个问题之前,我这儿写一个touch,点击屏幕的时候调用, 额,不能点击屏幕啊,因为现在屏幕点不着,我…

Windows 中的启动项如何打开?管理电脑启动程序的三种方法

在日常使用电脑时&#xff0c;我们经常会发现一些应用程序在开机时自动启动&#xff0c;这不仅会拖慢系统的启动速度&#xff0c;还可能占用不必要的系统资源。幸运的是&#xff0c;通过几个简单的步骤&#xff0c;你可以轻松管理这些开机自启的应用程序。接下来&#xff0c;我…

具备智能广告拦截、个性化定制的便捷网页浏览器

软件介绍 今天要给大家介绍一款源自俄罗斯的国民级软件&#xff0c;它来自俄罗斯最大互联网公司之一的 Yandex。这家公司不仅有搜索引擎业务&#xff0c;还打造出诸多热门软件&#xff0c;其中就有我们要讲的这款网页浏览器。它由 Yandex 公司依托 Chromium 开源项目开发&…

LangChain-基础(prompts、序列化、流式输出、自定义输出)

LangChain-基础 我们现在使用的大模型训练数据都是基于历史数据训练出来的&#xff0c;它们都无法处理一些实时性的问题或者一些在训练时为训练到的一些问题&#xff0c;解决这个问题有2种解决方案 基于现有的大模型上进行微调&#xff0c;使得它能适应这些问题&#xff08;本…

119. 杨辉三角 II

给定一个非负索引 rowIndex&#xff0c;返回「杨辉三角」的第 rowIndex 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 示例 1: 输入: rowIndex 3 输出: [1,3,3,1]示例 2: 输入: rowIndex 0 输出: [1]示例 3: 输入: rowIndex 1 输出: [1,1]提示…

Unity Android SDK 升级、安装 build-tools、platform-tools

Unity Android SDK 升级、安装 build-tools、platform-tools 通过 Unity Hub 安装的 Android SDK 需要下载 特定版本的 build-tools、platform-tools 如何操作&#xff1f; 以 Unity 2022.3.26f1 为例&#xff0c;打开安装目录&#xff0c;找到如下目录 2022.3.26f1\Editor\…

网络空间安全(3)web渗透测试学习框架

前言 Web渗透测试是一种安全评估方法&#xff0c;旨在通过模拟黑客攻击来检测Web应用程序中的安全漏洞。 一、学习基础 在学习Web渗透测试之前&#xff0c;需要掌握一些基础知识&#xff0c;包括计算机网络、Web开发技术&#xff08;如HTML、JavaScript、PHP等&#xff09;、数…

人工智能之自动驾驶技术体系

自动驾驶技术体系 自动驾驶技术是人工智能在交通领域的重要应用&#xff0c;旨在通过计算机视觉、传感器融合、路径规划等技术实现车辆的自主驾驶。自动驾驶不仅能够提高交通效率&#xff0c;还能减少交通事故和环境污染。本文将深入探讨自动驾驶的技术体系&#xff0c;包括感…

25会计研究生复试面试问题汇总 会计专业知识问题很全! 会计复试全流程攻略 会计考研复试真题汇总

宝子们&#xff0c;会计考研复试快到了&#xff0c;是不是有点慌&#xff1f;别怕&#xff01;今天学姐给你们支招&#xff0c;手把手教你搞定复试面试&#xff0c;直接冲上岸&#xff01;快来看看怎么准备吧&#xff0c;时间紧直接背第三部分的面试题&#xff01; 目录 一、复…

本地化部署 DeepSeek:从零到一的完整指南

本地化部署 DeepSeek&#xff1a;从零到一的完整指南 个人主页&#xff1a;顾漂亮 文章专栏&#xff1a;AI学习 目录 引言什么是 DeepSeek&#xff1f;为什么选择本地化部署&#xff1f;DeepSeek 本地化部署的前期准备 硬件需求软件需求环境配置 DeepSeek 本地化部署步骤 步骤…

【深度学习】Unet的基础介绍

U-Net是一种用于图像分割的深度学习模型&#xff0c;特别适合医学影像和其他需要分割细节的任务。如图&#xff1a; Unet论文原文 为什么叫U-Net&#xff1f; U-Net的结构像字母“U”&#xff0c;所以得名。它的结构由两个主要部分组成&#xff1a; 下采样&#xff08;编码…

【学习笔记】Cadence电子设计全流程(二)原理图库的创建与设计(8-15)

【学习笔记】Cadence电子设计全流程&#xff08;二&#xff09;原理图库的创建与设计&#xff08;下&#xff09; 2.8 Cadence 软件自带元件库2.9 原理图元器件关联PCB2.10 原理图元器件库的移植2.11 已有原理图输出元器件库2.12 原理图设计中调用元器件库2.13 原理图元器件库关…

DeepSeek从入门到精通

1_DeepSeek从入门到精通 (1).pdf官方版下载丨最新版下载丨绿色版下载丨APP下载-123云盘123云盘为您提供1_DeepSeek从入门到精通 (1).pdf最新版正式版官方版绿色版下载,1_DeepSeek从入门到精通 (1).pdf安卓版手机版apk免费下载安装到手机,支持电脑端一键快捷安装https://www.123…

Comfyui Windows Desktop桌面版便携版安装教程

前段时间Comfyui 的便携包安装写了一篇&#xff0c;最近comfyui发布了新的桌面版本0.4.5&#xff0c;我也试着安装了一下&#xff0c;感觉使用体验比便携包要舒适一点点。 下面是安装指南。 安装地址 官方给了下载包&#xff0c;分为N卡和Mac。地址&#xff1a;Notion – Th…

DeepSeek 提示词:定义、作用、分类与设计原则

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…

【Linux网络编程】数据链路层和网络层的几个问题:MTU,校验和,全球网段,路由表

目录 1.MTU 2.CRC校验和 3.全球网段 4.子网掩码 5.路由 1.MTU MTU是以太网的最大传输单位&#xff0c;大小是1500字节&#xff0c;表示IP&#xff08;网络层传下来的最多只能1500字节&#xff09;。 如果超过了这个数&#xff0c;就要网络层自己做分包。数据链路层是不帮…

浅谈死锁的原因以及解决方案

目录 1 死锁是什么? 2 死锁的三种典型情况 3 死锁产生的必要条件​编辑 4 如何解决死锁问题 1 死锁是什么? 它是指两个或多个线程因为互相等待对方持有的资源而无法继续执行的情况。换句话说&#xff0c;每个线程都在等待另一个线程释放资源&#xff0c;但没有任何一个…

【STM32】内存管理

【STM32】内存管理 文章目录 【STM32】内存管理1、内存管理简介疑问&#xff1a;为啥不用标准的 C 库自带的内存管理算法&#xff1f;2、分块式内存管理&#xff08;掌握&#xff09;分配方向分配原理释放原理分块内存管理 管理内存情况 3、内存管理使用&#xff08;掌握&#…

【SpringMVC】十分钟跑起来一个SpringMVC项目

目录标题 1 项目概述1.项目结构解析2. MVC项目的结构和每个组件的作用&#xff1a;3. 项目的工作流程&#xff1a;4 后期可以扩展的点&#xff1a;2.源码学习1. HelloController 类&#xff0c;Spring MVC控制器2 springmvc-servlet.xml - Spring MVC的主要配置文件3.web 目录 …