学习笔记08——ConcurrentHashMap实现原理及源码解析
1. 概述
为什么需要ConcurrentHashMap?
-
解决HashMap线程不安全问题:多线程put可能导致死循环(JDK7)、数据覆盖(JDK8)
-
优化HashTable性能:通过细粒度锁替代全局锁,提高并发度
对比表
特性 | HashMap | HashTable | ConcurrentHashMap |
---|---|---|---|
线程安全 | 否 | 是 | 是 |
锁粒度 | 无锁 | 全局锁 | 分段锁/CAS+synchronized |
并发性能 | 高 | 极低 | 高 |
Null键/值 | 允许 | 不允许 | 不允许 |
JDK版本差异
-
JDK7:基于Segment分段锁(默认16段)
-
JDK8+:数组+链表+红黑树,使用CAS + synchronized锁头节点
2. 线程安全实现机制
JDK8的锁优化
final V putVal(K key, V value, boolean onlyIfAbsent) {// ...for (Node<K,V>[] tab = table;;) {Node<K,V> f; int n, i, fh;if (tab == null || (n = tab.length) == 0)tab = initTable();else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {// CAS插入新节点(无锁)if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))break;}else if ((fh = f.hash) == MOVED)tab = helpTransfer(tab, f);else {synchronized (f) { // 锁住链表头节点// 处理链表/红黑树插入}}}// ...
}
-
CAS:用于无竞争情况下的快速插入(如空桶)
-
synchronized锁头节点:仅锁定当前哈希桶,不影响其他桶操作
3. 核心数据结构
Node节点结构
static class Node<K,V> implements Map.Entry<K,V> {final int hash;final K key;volatile V val;volatile Node<K,V> next;// ...
}
-
volatile修饰:保证可见性,确保读线程能立即看到更新
-
红黑树节点:
TreeNode
继承Node
,用于处理长链表(阈值=8)
扩容机制
-
触发条件:元素数量超过
sizeCtl
-
多线程协助:通过
ForwardingNode
标记正在迁移的桶,其他线程可参与迁移
4. 关键源码解析
以下是对 ConcurrentHashMap
核心源码的深度解读,结合 JDK8 的实现进行分析:
4.1核心方法源码解析
1. 初始化:initTable()
private final Node<K,V>[] initTable() {Node<K,V>[] tab; int sc;while ((tab = table) == null || tab.length == 0) {// sizeCtl < 0 表示其他线程正在初始化if ((sc = sizeCtl) < 0)Thread.yield(); // 让出 CPU,等待初始化完成else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { // CAS 抢锁try {// 双重检查,防止重复初始化if ((tab = table) == null || tab.length == 0) {int n = (sc > 0) ? sc : DEFAULT_CAPACITY;Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];table = tab = nt;sc = n - (n >>> 2); // 计算扩容阈值(0.75n)}} finally {sizeCtl = sc; // 释放锁,设置阈值}break;}}return tab;
}
-
CAS 抢锁:通过
sizeCtl
标记初始化状态(-1 表示初始化中)。 -
双重检查:避免多线程重复初始化。
-
负载因子固定为 0.75:通过
sc = n - (n >>> 2)
实现。
2. put 方法:putVal()
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;if (tab == null || (n = tab.length) == 0)tab = initTable(); // 懒初始化else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { // 桶为空if (casTabAt(tab, i, null, new Node<>(hash, key, value))) // CAS 插入break;} else if ((fh = f.hash) == MOVED) // 正在扩容tab = helpTransfer(tab, f); // 协助扩容else {V oldVal = null;synchronized (f) { // 锁住头节点if (tabAt(tab, i) == f) { // 再次验证头节点未变if (fh >= 0) { // 链表节点binCount = 1;for (Node<K,V> e = f;; ++binCount) {K ek;if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) {oldVal = e.val;if (!onlyIfAbsent)e.val = value; // 更新值break;}Node<K,V> pred = e;if ((e = e.next) == null) {pred.next = new Node<>(hash, key, value); // 追加到链表尾部break;}}} 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;}}}}if (binCount != 0) {if (binCount >= TREEIFY_THRESHOLD) // 链表长度 >=8treeifyBin(tab, i); // 可能树化if (oldVal != null)return oldVal;break;}}}addCount(1L, binCount); // 更新元素计数return null;
}
关键逻辑:
-
哈希计算:
spread()
方法通过(h ^ (h >>> 16)) & HASH_BITS
保证哈希值为正数。 -
CAS 插入空桶:若桶为空,直接通过
casTabAt
插入新节点(无锁优化)。 -
锁头节点处理冲突:对非空桶使用
synchronized
锁定头节点,处理链表或红黑树。 -
树化条件:链表长度 >=8 且数组长度 >=64 时树化,否则优先扩容。
-
并发扩容协作:若发现桶正在迁移(
MOVED
标记),当前线程会协助迁移。
3. 扩容机制:transfer()
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {int n = tab.length, stride;// 计算每个线程处理的桶区间(最小 16)if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)stride = MIN_TRANSFER_STRIDE;if (nextTab == null) { // 初始化新数组try {Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];nextTab = nt;} catch (Throwable ex) { sizeCtl = Integer.MAX_VALUE; return; }nextTable = nextTab;transferIndex = n; // 迁移起点为旧数组末尾}// 多线程协同迁移逻辑ForwardingNode<K,V> fwd = new ForwardingNode<>(nextTab);boolean advance = true;boolean finishing = false;for (int i = 0, bound = 0;;) {Node<K,V> f; int fh;while (advance) {int nextIndex, nextBound;if (--i >= bound || finishing)advance = false;else if ((nextIndex = transferIndex) <= 0) {i = -1;advance = false;} else if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex,nextBound = (nextIndex > stride ? nextIndex - stride : 0))) {bound = nextBound;i = nextIndex - 1;advance = false;}}// 实际迁移逻辑(略,处理链表/树拆分到新数组)}
}
核心设计:
-
多线程任务分配:通过
transferIndex
全局指针分配迁移区间(类似“工作窃取”)。 -
ForwardingNode 占位符:迁移中的桶会被标记为
ForwardingNode
,读请求会转发到新数组。 -
链表拆分优化:根据哈希值的高位决定节点留在旧桶还是迁移到新桶(
newIndex = oldIndex + oldCap
)。
4. 无锁读:get()
public V get(Object key) {Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;int h = spread(key.hashCode());if ((tab = table) != null && (n = tab.length) > 0 &&(e = tabAt(tab, (n - 1) & h)) != null) {if ((eh = e.hash) == h) {if ((ek = e.key) == key || (ek != null && key.equals(ek)))return e.val; // 命中头节点} else if (eh < 0) // 特殊节点(红黑树或 ForwardingNode)return (p = e.find(h, key)) != null ? p.val : null;while ((e = e.next) != null) { // 遍历链表if (e.hash == h && ((ek = e.key) == key || (ek != null && key.equals(ek))))return e.val;}}return null;
}
无锁保障:
-
tabAt()
使用Unsafe.getObjectVolatile
保证读取最新内存值。 -
链表遍历期间依赖
volatile
修饰的next
指针保证可见性。 -
遇到
ForwardingNode
时自动跳转到新数组查询。
4.2.关键优化总结
优化点 | 实现方式 |
---|---|
锁粒度 | 仅锁单个桶的头节点(JDK8),替代 JDK7 的分段锁 |
CAS 无锁插入 | 空桶通过 casTabAt 直接插入,避免锁竞争 |
多线程协作扩容 | 通过 ForwardingNode 和 transferIndex 分配任务区间 |
计数优化 | 使用 CounterCell[] 分散竞争,避免 size() 成为性能瓶颈 |
红黑树退化机制 | 当树节点 <=6 时退化为链表,避免频繁树化开销 |
4.3源码分析的思考题
-
为什么在链表长度超过 8 时可能选择扩容而不是直接树化?
-
数组较短时(<64),扩容能更有效减少哈希冲突。
-
-
如何保证扩容期间读操作的正确性?
-
通过
ForwardingNode
将读请求转发到新数组,写操作会等待迁移完成。
-
-
size() 方法为什么不是精确值?
-
高并发场景下精确统计代价过高,采用分片计数(
CounterCell
)近似值。
-
5. 并发操作分析
扩容期间读写协调
-
读操作:遇到
ForwardingNode
时转到新表查询 -
写操作:协助迁移当前桶后再执行插入
线程协作扩容
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {// 每个线程处理一个区间,完成后再领取新区间 }
-
通过
stride
步长划分任务区间 -
使用
transferIndex
指针协调多线程任务分配
6. 总结
设计亮点
-
锁分离:降低锁竞争概率
-
乐观锁优先:CAS无锁化尝试
-
粒度细化:从分段锁到桶级别锁
-
无锁读:volatile变量保证可见性
7.面试回答模板
面试官:请说一下ConcurrentHashMap的实现原理。 回答:
ConcurrentHashMap在JDK1.8中采用了数组+链表/红黑树的结构,通过CAS和synchronized实现高并发。
写操作:如果桶为空,用CAS插入头节点;否则锁住头节点处理链表或红黑树。
读操作:完全无锁,依赖volatile保证可见性。
扩容:多线程协作迁移数据,通过ForwardingNode标记已处理的桶。
线程安全:锁粒度细化到单个桶,结合CAS减少竞争。 对比HashTable,它的并发性能更高,且支持更高的并发度。
8.源码分析技巧
-
关注核心方法:
putVal
、get
、transfer
(扩容)、helpTransfer
。 -
调试参数:通过
-XX:+PrintConcurrentLocks
观察锁竞争情况。 -
结合JMM:分析
volatile
变量(如table
、sizeCtl
)的内存可见性保证。
9.常见面试题
以下是关于 Java ConcurrentHashMap
的常见面试题及其详细解答,涵盖底层实现、线程安全机制、性能优化等核心内容:
1. ConcurrentHashMap 如何实现线程安全?
回答要点:
-
JDK7 的分段锁(Segment): 将整个哈希表分成多个段(默认 16 段),每个段独立加锁,不同段的操作可以并发执行。
-
优点:降低锁粒度,减少竞争。
-
缺点:内存占用较高(每个段独立维护数组),并发度受段数限制。
-
-
JDK8 的 CAS + synchronized 锁优化:
-
CAS 无锁插入:当桶为空时,直接通过 CAS 插入新节点,避免加锁。
-
synchronized 锁头节点:当桶非空时,仅锁住链表或红黑树的头节点,锁粒度细化到单个桶。
-
扩容协作:多线程可以协同迁移数据,减少扩容时间。
-
2. ConcurrentHashMap 与 HashMap、HashTable 的区别?
特性 | HashMap | HashTable | ConcurrentHashMap |
---|---|---|---|
线程安全 | 非线程安全 | 全局锁(方法级同步) | CAS + 细粒度锁(桶级别) |
Null 键/值 | 允许 | 禁止 | 禁止 |
性能 | 高(无锁) | 极低(全局锁) | 高(并发优化) |
迭代器一致性 | 快速失败 | 快速失败 | 弱一致性 |
实现机制 | 数组+链表+红黑树 | 数组+链表 | 数组+链表+红黑树(JDK8+) |
3. JDK7 和 JDK8 的 ConcurrentHashMap 实现差异?
-
JDK7:
-
基于 分段锁(Segment),每个段类似一个独立的哈希表。
-
默认 16 段,并发度固定,无法动态扩展。
-
内存占用较高(每个段维护独立的数组)。
-
-
JDK8:
-
抛弃分段锁,采用 CAS + synchronized 锁头节点,锁粒度细化到单个桶。
-
引入 红黑树,当链表长度 >=8 且数组长度 >=64 时树化,提升查询效率。
-
内存利用率更高(共享一个数组),并发度动态扩展。
-
支持 多线程协作扩容,迁移效率更高。
-
4. ConcurrentHashMap 的 put 方法流程?
-
计算哈希:
spread(key.hashCode())
(保证哈希值为正数)。 -
懒初始化:若数组为空,调用
initTable()
初始化。 -
定位桶:
(n-1) & hash
计算桶下标。 -
CAS 插入空桶:若桶为空,直接通过 CAS 插入新节点。
-
处理哈希冲突:
-
若桶正在迁移(
ForwardingNode
),当前线程协助迁移。 -
非空桶锁住头节点,处理链表或红黑树的插入/更新。
-
-
树化检查:链表长度 >=8 时可能触发树化。
-
更新计数:调用
addCount()
更新元素总数(基于CounterCell[]
分片计数)。
5. ConcurrentHashMap 的 size() 方法为什么不是完全精确的?
-
分片计数优化: 使用
CounterCell[]
数组分散计数更新,避免多线程竞争同一变量。-
当无竞争时,直接更新
baseCount
。 -
当检测到竞争时,使用
CounterCell
分片统计,最终结果为baseCount + ∑CounterCell[i]
。
-
-
设计权衡: 高并发场景下,精确统计需要全局锁,性能代价过高。
size()
返回的是一个近似值,但实际开发中足够使用。
6. 如何保证 get() 操作的无锁和高性能?
-
volatile 变量: 桶数组和节点的
val
、next
字段用volatile
修饰,保证可见性。 -
无锁读设计:
-
tabAt()
使用Unsafe.getObjectVolatile
直接读取内存最新值。 -
遍历链表或红黑树时不加锁,依赖
volatile
保证数据可见性。
-
-
扩容期间的读操作: 遇到
ForwardingNode
时,自动跳转到新数组查询数据。
7. ConcurrentHashMap 的扩容机制如何实现?
核心步骤:
-
触发条件:元素数量超过
sizeCtl
(扩容阈值 = 0.75 * 数组长度)。 -
多线程协作:
-
首个线程创建新数组(长度翻倍),并分配迁移任务区间。
-
其他线程通过
transferIndex
领取迁移任务(步长stride
)。
-
-
迁移逻辑:
-
将旧数组的桶拆分为高低位链表,高位链表迁移到
旧下标 + 旧容量
的位置。 -
迁移完成的桶用
ForwardingNode
占位,表示已处理。
-
-
读写协调:
-
读操作:遇到
ForwardingNode
时跳转到新数组查询。 -
写操作:先协助迁移当前桶,再执行插入。
-
8. 链表何时会转为红黑树?何时会退化为链表?
-
树化条件:
-
链表长度 >=8(
TREEIFY_THRESHOLD
)。 -
数组长度 >=64(
MIN_TREEIFY_CAPACITY
),否则优先扩容。
-
-
退化条件:
-
红黑树节点数 <=6(
UNTREEIFY_THRESHOLD
)时退化为链表。
-
-
设计目的: 平衡查询效率和空间占用,避免极端情况下的性能问题。
9. ConcurrentHashMap 为什么不允许 Null 键或 Null 值?
-
歧义性问题:
get(key)
返回null
时,无法区分是键不存在还是键对应的值为null
。 -
线程安全风险: 若允许
null
,可能因并发操作导致隐式的NullPointerException
(如containsKey(key)
和get(key)
之间的竞争)。 -
设计一致性:
ConcurrentHashMap
的设计目标是为并发场景提供明确的语义,禁止null
可减少不确定性。
10. ConcurrentHashMap 的迭代器是强一致性还是弱一致性?
-
弱一致性: 迭代器在遍历时不会锁定整个哈希表,可能反映部分已完成的更新操作。
-
迭代过程中可能看到新增、删除或修改的键值对,但不保证完全一致。
-
设计目的是避免迭代期间阻塞其他线程,提升并发性能。
-
-
对比其他集合:
HashMap
的迭代器是快速失败的(fail-fast
),而ConcurrentHashMap
的迭代器是安全的(fail-safe
)。
11. 为什么 JDK8 改用 synchronized 而不是 ReentrantLock?
-
锁粒度细化:
synchronized
在 JDK6 后进行了大量优化(如偏向锁、轻量级锁),性能与ReentrantLock
接近。 -
内存消耗:
ReentrantLock
需要额外维护AQS
队列,内存开销更大。 -
代码简洁性:
synchronized
语法更简洁,无需手动释放锁,减少编码错误。
12. ConcurrentHashMap 是否完全线程安全?
-
基本操作线程安全:
put
、get
、remove
等单操作是原子的,线程安全。 -
组合操作非原子:
if (!map.containsKey(key)) map.put(key, value); // 非原子操作
此类组合操作需使用
putIfAbsent()
或外部同步。 -
迭代器弱一致性: 迭代期间可能看到其他线程的修改,但不会抛出
ConcurrentModificationException
。
13. 如何理解 sizeCtl 字段的作用?
sizeCtl
是一个控制状态变量,具体含义:
-
>0:表示下一次扩容的阈值(0.75 * 当前容量)。
-
=0:默认初始状态。
-
=-1:表示哈希表正在初始化。
-
<-1:表示正在扩容,高 16 位表示扩容标识戳,低 16 位表示参与扩容的线程数 +1。
14. ConcurrentHashMap 的应用场景?
-
高并发缓存:如缓存用户会话信息、商品库存等。
-
实时统计:如多线程计数(需结合
addCount()
机制)。 -
替代 HashTable:所有需要线程安全哈希表的场景,性能更优。
-
分布式计算:如 MapReduce 任务中的局部结果聚合。
15. 如何设计一个线程安全的哈希表?
-
锁粒度优化: 从全局锁 → 分段锁 → 桶级别锁,逐步减少竞争。
-
无锁化尝试: 优先使用 CAS 处理无竞争场景(如空桶插入)。
-
并发扩容协作: 允许多线程协同迁移数据,提升扩容效率。
-
数据结构优化: 引入红黑树平衡查询效率,动态退化避免空间浪费。
相关文章:
学习笔记08——ConcurrentHashMap实现原理及源码解析
1. 概述 为什么需要ConcurrentHashMap? 解决HashMap线程不安全问题:多线程put可能导致死循环(JDK7)、数据覆盖(JDK8) 优化HashTable性能:通过细粒度锁替代全局锁,提高并发度 对比…...
redis slaveof 命令 执行后为什么需要清库重新同步
在 Redis 中,执行 SLAVEOF(或 REPLICAOF)命令后,从节点需要清空现有数据并重新同步的主要原因如下: 1. 保证数据一致性 核心目标:确保从节点的数据与主节点 完全一致。问题场景: 如果从节点之前…...
6-1JVM的执行引擎处理
一、执行引擎的组成结构 解释器(Interpreter) 逐条解释执行字节码指令,启动速度快但执行效率较低。适用于短生命周期或对启动时间敏感的场景,如调试环境。 即时编译器(JIT Compiler) 通过动态…...
CMU15445(2023fall) Project #4 - Concurrency Control踩坑历程
把树木磨成月亮最亮时的样子, 就能让它更快地滚下山坡, 有时会比骑马还快。 完整代码见: SnowLegend-star/CMU15445-2023fall: Having Conquered the Loftiest Peak, We Stand But a Step Away from Victory in This Stage. With unwavering…...
腿足机器人之十三-强化学习PPO算法
腿足机器人之十三-强化学习PPO算法 腿足机器人位姿常用强化学习算法PPO算法核心原理PPO算法的创新设计PPO算法典型流程优势函数 对于复杂地形适应性(如楼梯、碎石路),传统的腿足机器人采用基于模型的控制器,该方法依赖精确动力学建…...
ubuntu下r8125网卡重启丢失修复案例一则
刚装的一台服务器,ubuntu24.04,主板网卡是r8125,安装服务后会莫名其妙丢失驱动 按照官网的方法下载最新8125驱动包: Realtek 然后卸载驱动 rmmod r8125 然后在驱动包里安装(幸好我之前装了build-essential&#x…...
设计一个“车速计算”SWC,通过Sender-Receiver端口输出车速信号。
1. 需求分析 功能目标:根据车轮脉冲信号(轮速传感器输入)计算当前车速,并将结果通过Sender端口发送给其他SWC。 输入:轮速脉冲数(如WheelPulse,类型uint32)。 输出:车速(如VehicleSpeed,类型float32,单位km/h)。 触发方式:周期性计算(例如每10ms执行一次)。 2.…...
DeepSeek 使用窍门与提示词写法指南
一、通用提示词技巧 窍门分类技巧说明示例提示词明确需求用“角色任务要求”明确目标作为健身教练,为30岁上班族设计一周减脂计划,需包含饮食和15分钟居家训练结构化提问分步骤、分模块提问第一步:列出Python爬虫必备的5个库;第二…...
MySQL零基础教程12—聚合查询(聚合函数)
背景 有时候我们需要汇总一些数据,比如查询一个班级的平均分数,这个时候我们需要的是把分数汇总,然后计算出一个平均值进行返回,并不需要返回某一列的值,针对这种场景,mysql中提供了一些聚合函数帮助快速完…...
JMeter 引入 JAR 包的几种方法
JMeter 支持加载外部 JAR 文件,用于: 扩展 JMeter 功能使用 Java 代码(BeanShell / JSR223)连接数据库 / 解析 Excel / 读取 CSV 📌 1. JMeter 引入 JAR 包的方式 ✅ 方式 1:将 JAR 放入 lib/ 或 lib/ext…...
一周一个Unity小游戏2D反弹球游戏 - 球板的发球
前言 本文将实现当游戏开始时球在球板上,且不具备物理性,在Windows平台上通过点击屏幕来球发射,安卓平台上当手指触摸到屏幕上时进行发球,并此时开始具备物理性。 发球逻辑 首先在球板上创建一个球的发射点,新建一个空的游戏物体,并命名为BallPoint,并将其作为SpringBoa…...
C++Primer学习(4.8位运算符)
4.8位运算符 位运算符作用于整数类型的运算对象,并把运算对象看成是二进制位的集合。位运算符提供检查和设置二进制位的功能,如17.2节(第640页)将要介绍的,一种名为bitset的标准库类型也可以表示任意大小的二进制位集合,所以位运算符同样能用…...
Linux | Ubuntu 与 Windows 双系统安装 / 高频故障 / UEFI 安全引导禁用
注:本文为 “buntu 与 Windows 双系统及高频故障解决” 相关文章合辑。 英文引文,机翻未校。 How to install Ubuntu 20.04 and dual boot alongside Windows 10 如何将 Ubuntu 20.04 和双启动与 Windows 10 一起安装 Dave’s RoboShack Published in…...
SpringSecurity 实现token 认证
配置类 Configuration EnableWebSecurity EnableGlobalMethodSecurity(prePostEnabledtrue) public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { Bean Override public AuthenticationManager authenticationManagerBean() throws Exception {return s…...
C语言基础要素(007):使用变量
定义变量的同时可以给定一个值,这叫初始化变量;未初始化的变量,其值在程序运行时是不确定的。变量在定义之后可以多次设置值,这称为赋值。定义使得变量从无到有,而赋值则改变已有变量的状态。变量只能被初始化一次&…...
6. Nginx 动静分离配置案例(附有详细说明+配图)
6. Nginx 动静分离配置案例(附有详细说明配图) 文章目录 6. Nginx 动静分离配置案例(附有详细说明配图)1. 动静分离概述说明2. 先使用传统方式实现,不使用 Nginx3. 使用上 Nginx 实现动静分离优化步骤4. 最后: 1. 动静分离概述说明 什么是动静分离&…...
Deepseek对ChatGPT的冲击?
从测试工程师的视角来看,DeepSeek对ChatGPT的冲击主要体现在**测试场景的垂直化需求与通用模型局限性之间的博弈**。以下从技术适配性、效率优化、风险控制及未来趋势四个维度展开分析: --- ### **一、技术适配性:垂直领域能力决定工具选择…...
在已安装二进制movit2的情况下使用自编译moveit2
在已安装二进制movit2的情况下,想使用自编译moveit2,只要引入一下自编译moveit2库的环境变量即可。主要是想搞明白这个过程发生了什么,也就是引入环境后有什么变化,以及如何对编译过程产生影响 一、setup.bash流程 所有资料上都…...
React 常见面试题及答案
记录面试过程 常见问题,如有错误,欢迎批评指正 1. 什么是虚拟DOM?为什么它提高了性能? 虚拟DOM是React创建的一个轻量级JavaScript对象,表示真实DOM的结构。当状态变化时,React会生成新的虚拟DOM…...
2025-03-01 学习记录--C/C++-C语言 整数类型对比
C语言 整数类型对比 类型位数范围(有符号)范围(无符号)格式化符号char8-128 到 1270 到 255%c 或 %hhdshort16-32,768 到 32,7670 到 65,535%hdint32-2,147,483,648 到 2,147,483,6470 到 4,294,967,295%dlong32 或 64-2,147,483…...
金融赋能绍兴纺织 民生银行助力外贸中小微企业“走出去”
在浙江绍兴,纺织业作为一张熠熠生辉的产业名片,承载着深厚的历史底蕴与蓬勃的发展活力。这里依傍长三角经济圈,交通网络纵横交错,将原材料产地与广阔市场紧密相连;产业集群高度成熟,上下游产业链完备&#…...
TCP的三握四挥
TCP协议 TCP( Transmission control protocol )即传输控制协议,是一种面向连接、可靠的数据传输协议,它是为了在不可靠的互联网上提供可靠的端到端字节流而专门设计的一个传输协议。 TCP的基本特点 面向连接:通信双方在进行数据传输之前&…...
Phpstudy中的MySQL无法正常启动或启动后自动暂停,以及sqlilab环境搭建出现的问题解决方法
【解决方法】 无法启动的原因是Phpstudy中的MySQL与本地的mysql重名,导致无法正常启动;所以这时我们就需要将本地的MySQL进行修改名称; 或者修改phpstudy中数据库的端口号,但是我觉得还是不是很好解决这种问题 最后一个方法&#…...
用C语言实现一个链表(四)
用C语言实现一个链表(四) 在上期内容中,我们探讨了实现一个双向循环链表的准备工作以及一些功能——创建新结点,初始化头结点,尾插数据,尾删数据,遍历的代码,上期内容留下了一个判断…...
【我的 PWN 学习手札】House of Kiwi
House of Kiwi 之前我们利用IO_FILE一般是通过劫持vtable来实现的, House of Kiwi虽然不是通过劫持vtable来实现,但实质上是劫持vtable指向的全局的_IO_file_jumps_表来实现的。注意:对于某些版本的glibc,_IO_file_jumps_并不可写…...
象棋笔记-实战记录
文章目录 实战没发现杀招2024-06-16 实战又漏杀了,尴尬,炮震五子,3路炮有下底的机会,本来是绝杀,没算明白,以为窝心马和象都能看住这个点。。。2024-07-06 实战有进炮串打的机会,又错过了&#…...
RabbitMQ系列(六)基本概念之Routing Key
在 RabbitMQ 中,Routing Key(路由键) 是用于将消息从交换机(Exchange)路由到指定队列(Queue)的关键参数。其核心作用是通过特定规则匹配绑定关系,确保消息被正确分发。以下是其核心机…...
企业微信里可以使用的企业内刊制作工具,FLBOOK
如何让员工及时了解公司动态、行业资讯、学习专业知识,并有效沉淀企业文化?一份高质量的企业内刊是不可或缺的。现在让我来教你该怎么制作企业内刊吧 1.登录与上传 访问FLBOOK官网,注册账号后上传排版好的文档 2.选择模板 FLBOOK提供了丰富的…...
JAVA笔记【一】
现实 (抽象) 类 (创建) 对象 特点: 1.面向对象 2.跨平台 3.安全性 4.多线程 java程序基本结构 1. java源代码文件实际是普通的文本文件,源代码文件必须是.java扩展名,且必须小写 2. …...
Mybatis做批量操作
前面我们将动态标签foreach的时候,做过批量操作,但是foreach只能处理记录数不多的批量操作,数据量大了后,先不说效率,能不能成功操作都是问题,所以这里讲一讲Mybatis正确的批量操作方法: 在获取…...
C/C++动静态库的制作与原理 -- 静态库,动态库,目标文件,ELF文件,动态链接,静态链接
目录 1. 什么是库 2. 静态库 2.1 静态库的制作 2.2 静态库的使用 3. 动态库 3.1 动态库的制作 3.2 动态库的使用 4. 目标文件 5. ELF文件 6. ELF从形成到加载轮廓 6.1 ELF形成可执行 7.2 ELF可执行文件加载 7. 理解链接和加载 7.1 静态链接 7.2 ELF加载与进程地…...
Java 并发编程之synchronized
一、前言 在并发编程中,多个线程访问同一个共享资源时,我们必须考虑如何维护数据的原子性。在JDK1.5之前,Java是依靠Synchronized关键字实现锁功能来做到这点的。Synchronized是JVM实现的一种内置锁,锁的获取和释放是由JVM隐式实…...
Windows 11【1001问】查看Windows 11 版本的18种方法
随着技术的飞速发展,操作系统作为连接硬件与软件的核心桥梁,其版本管理和更新变得尤为重要。对于用户而言,了解自己设备上运行的具体Windows 11版本不仅有助于优化系统性能,还能确保安全性和兼容性。然而,不同场景和需…...
python 元组tuple
元组:有序不可变列表 (相当于只读的list) 注意:元组里的普通元素不可以修改,但是元组里的list可以修改 index(元素) 查找某个元素,有的话返回下标,没有的话报错 count(元素) 统计某元素在元组中出现的次数 len(元组) 统计元组内的元素个数 #定义元组,元组支持嵌套 t1("…...
485 多路信号采集,校验干扰问题
在RS-485总线中同时采集多路信号时,若某一路出现CRC校验失败,通常由总线冲突、信号干扰或硬件设计缺陷引起。以下是具体影响分析和解决方案: 一、多路信号同时采集的影响 1. 总线冲突风险 现象:多路信号同时发送时,485总线(半双工)无法区分信号,导致数据叠加损坏。 后…...
【Eureka 缓存机制】
今天简单介绍一下Eureka server 的缓存机制吧✌️✌️✌️ 一、先来个小剧场:服务发现的"拖延症" 想象你是个外卖小哥(客户端),每次接单都要打电话问调度中心(Eureka Server):“现在…...
MySQL并发知识(面试高频)
mysql并发事务解决 不同隔离级别下,mysql解决并发事务的方式不同。主要由锁机制和MVCC(多版本并发控制)机制来解决并发事务问题。 1. mysql中的锁有哪些? 表级锁: 场景:表级锁适用于需要对整个表进行操作的情况,例如…...
Git GitHub基础
git是什么? Git是一个分布式版本控制系统,用于管理源代码的变更。它允许多个开发者在同一个项目上协作,同时跟踪每个修改的历史记录。 关键词: 分布式版本控制软件 软件 安装到我们电脑上的一个工具 版本控制 例如论文&…...
Rabbit MQ 高频面试题【刷题系列】
文章目录 一、公司生产环境用的什么消息中间件?二、Kafka、ActiveMQ、RabbitMQ、RocketMQ有什么优缺点?三、解耦、异步、削峰是什么?四、消息队列有什么缺点?五、RabbitMQ一般用在什么场景?六、简单说RabbitMQ有哪些角…...
Ubantu22.04系统docker部署Open WebUI+Ollama【教程】
Open WebUI 是一个可扩展、功能丰富且用户友好的自托管 AI 平台,旨在完全离线运行。它支持各种 LLM 运行器,如 Ollama 和 OpenAI 兼容的 API,并内置了 RAG 推理引擎,使其成为强大的 AI 部署解决方案。 1.docker拉取镜像 &#x…...
知识图谱科研文献推荐系统vue+django+Neo4j的知识图谱
文章结尾部分有CSDN官方提供的学长 联系方式名片 文章结尾部分有CSDN官方提供的学长 联系方式名片 关注B站,有好处! 📑 编号:D030 📑 vuedjangoneo4jmysql 前后端分离架构、图数据库 📑 文献知识图谱&#…...
我的世界开发模组的心得体会
最头疼的问题 本人也是小白,也就跟着ai学学怎么开发模组,不会的上网搜搜,但是目前最令我头疼的就是运行rundata和runcilent时的模块冲突,解决办法就是使用以下的build.gradle代码,不要接受人工智能的建议,…...
HTML:自闭合标签简单介绍
1. 什么是自结束标签? 定义:自结束标签(Self-closing Tag)是指 不需要单独结束标签 的 HTML 标签,它们通过自身的语法结构闭合。语法形式: 在 HTML5 中:直接写作 <tag>,例如 …...
Oracle性能调优(一):时间模型统计
Oracle性能调优(一):时间模型统计 时间模型统计视图时间模型统计指标时间模型统计视图 📖 DB Time的含义: DB Time表示前台会话在数据库调用中所花费的总时间,它是衡量数据库实例总负载的一个重要指标。DB Time是从实例启动时开始累计测量的,其计算方法是将所有前台会话…...
MacBook Pro使用FFmpeg捕获摄像头与麦克风推流音视频
FFmpeg查看macos系统音视频设备列表 ffmpeg -f avfoundation -list_devices true -i "" 使用摄像头及麦克风同时推送音频及视频流: ffmpeg -f avfoundation -pixel_format yuyv422 -framerate 30 -i "0:1" -c:v libx264 -preset ultrafast -b:v 1000k -…...
【构建工具】Gradle Kotlin DSL中的大小写陷阱:BuildConfigField
在Android开发当中,BuildConfig是一个非常有用的功能,它允许我们在构建过程中定义常量,并在运行时使用它们。But!!当我们从传统的Groovy DSL迁移到Kotlin DSL时或者被Android Studio坑的时候,有一些细微的差…...
Linux网络 TCP全连接队列与tcpdump抓包
TCP全连接队列 在 Linux 网络中,TCP 全连接队列(也称为 Accept 队列)是一个重要的概念,用于管理已经完成三次握手,即已经处于 established 状态但尚未被应用程序通过 accept( ) 函数处理的 TCP 连接,避免因…...
ChatGPT与DeepSeek:开源与闭源的AI模型之争
目录 一、模型架构与技术原理 二、性能能力与应用场景 三、用户体验与部署灵活性 四、成本与商业模式 五、未来展望与市场影响 六、总结 随着人工智能技术的飞速发展,ChatGPT和DeepSeek作为两大领先的AI语言模型,成为了行业内外关注的焦点。它们在…...
泛微Ecode新增Button调用服务器中的JSP页面里的方法
前言 前端Ecode调用 后端接口编写 JSP文件方法 总结 前言 因为我们是从之前E8版本升级到E9的,所以会有一些接口是通过jsp文件来实现前后端调用的,这里介绍的就是如果你有接口是写在jsp文件里面调用的,但是你又想在Ecode中调用的对应的接…...
知识图谱+智能问诊预诊系统vue+django+neo4j架构、带问诊历史
文章结尾部分有CSDN官方提供的学长 联系方式名片 文章结尾部分有CSDN官方提供的学长 联系方式名片 关注B站,有好处! 🤍编号:D032 🤍智能问答:智能问答自诊、预诊功能,同时可以保存问答历史 &…...