当前位置: 首页 > news >正文

【ConcurrentHashMap1.7源码】十分钟带你深入ConcurrentHashMap并发解析

ConcurrentHashMap1.7源码

3

四个核心要点

  1. 初始化
  2. PUT
  3. 扩容
  4. GET

Unsafe

img

初始化

五个构造方法

image-20230802175547296

    /*** Creates a new, empty map with the default initial table size (16).*/public ConcurrentHashMap() {}/*** Creates a new, empty map with an initial table size* accommodating the specified number of elements without the need* to dynamically resize.** @param initialCapacity The implementation performs internal* sizing to accommodate this many elements.* @throws IllegalArgumentException if the initial capacity of* elements is negative*/public ConcurrentHashMap(int initialCapacity) {if (initialCapacity < 0)throw new IllegalArgumentException();int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?MAXIMUM_CAPACITY :tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));this.sizeCtl = cap;}/*** Creates a new map with the same mappings as the given map.** @param m the map*/public ConcurrentHashMap(Map<? extends K, ? extends V> m) {this.sizeCtl = DEFAULT_CAPACITY;putAll(m);}/*** Creates a new, empty map with an initial table size based on* the given number of elements ({@code initialCapacity}) and* initial table density ({@code loadFactor}).** @param initialCapacity the initial capacity. The implementation* performs internal sizing to accommodate this many elements,* given the specified load factor.* @param loadFactor the load factor (table density) for* establishing the initial table size* @throws IllegalArgumentException if the initial capacity of* elements is negative or the load factor is nonpositive** @since 1.6*/public ConcurrentHashMap(int initialCapacity, float loadFactor) {this(initialCapacity, loadFactor, 1);}/*** Creates a new, empty map with an initial table size based on* the given number of elements ({@code initialCapacity}), table* density ({@code loadFactor}), and number of concurrently* updating threads ({@code concurrencyLevel}).** @param initialCapacity the initial capacity. The implementation* performs internal sizing to accommodate this many elements,* given the specified load factor.* @param loadFactor the load factor (table density) for* establishing the initial table size* @param concurrencyLevel the estimated number of concurrently* updating threads. The implementation may use this value as* a sizing hint.* @throws IllegalArgumentException if the initial capacity is* negative or the load factor or concurrencyLevel are* nonpositive*/public ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel) {if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)throw new IllegalArgumentException();if (initialCapacity < concurrencyLevel)   // Use at least as many binsinitialCapacity = concurrencyLevel;   // as estimated threadslong size = (long)(1.0 + (long)initialCapacity / loadFactor);int cap = (size >= (long)MAXIMUM_CAPACITY) ?MAXIMUM_CAPACITY : tableSizeFor((int)size);this.sizeCtl = cap;}

无参构造方法

    /*** Creates a new, empty map with a default initial capacity (16),* load factor (0.75) and concurrencyLevel (16).*/public ConcurrentHashMap() {this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);}

四个参数

  • initialCapacity-初始容量
static final int DEFAULT_INITIAL_CAPACITY = 16;
  • loadFactor-加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
  • concurrencyLevel-并发等级(最大支持线程)
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
    @SuppressWarnings("unchecked")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 argumentsint sshift = 0;// 关键int ssize = 1;// 1 < 16 2 < 16 4 < 16 8 < 16 最后ssize=16// 假如传入concurrencyLevel = 9 ,ssize = 16// 也就是找一个大于concurrencyLevel的2次幂数给ssizewhile (ssize < concurrencyLevel) {++sshift;ssize <<= 1;}this.segmentShift = 32 - sshift;this.segmentMask = ssize - 1;if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;// 以默认值为例c=16/16=1int c = initialCapacity / ssize;// 如果是initialCapacity=9,concurrencyLevel=8// 下面是向上取整,要确保要这么多容量if (c * ssize < initialCapacity)++c;// static final int MIN_SEGMENT_TABLE_CAPACITY = 2;// cap = 2,所以初始化的时候cap=2int cap = MIN_SEGMENT_TABLE_CAPACITY;// 保证cap容量是2的幂次方数while (cap < c)cap <<= 1;// create segments and segments[0]Segment<K, V> s0 =new Segment<K, V>(loadFactor, (int) (cap * loadFactor),(HashEntry<K, V>[]) new HashEntry[cap]);Segment<K, V>[] ss = (Segment<K, V>[]) new Segment[ssize];// 默认先在Segment数组里放了一个segments[0],里面是一个new HashEntry[cap]// 后面会以这个默认进行扩容UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]this.segments = ss;}

注意:默认初始化的时候,HashEntry数组默认是16x2=32个,而不是16个

Segment

继承了ReentrantLock,方便lock

    static final class Segment<K, V> extends ReentrantLock implements Serializable {private static final long serialVersionUID = 2249069246763182397L;/*** The maximum number of times to tryLock in a prescan before* possibly blocking on acquire in preparation for a locked* segment operation. On multiprocessors, using a bounded* number of retries maintains cache acquired while locating* nodes.*/static final int MAX_SCAN_RETRIES =Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;/*** The per-segment table. Elements are accessed via* entryAt/setEntryAt providing volatile semantics.*/transient volatile HashEntry<K, V>[] table;/*** The number of elements. Accessed only either within locks* or among other volatile reads that maintain visibility.*/transient int count;/*** The total number of mutative operations in this segment.* Even though this may overflows 32 bits, it provides* sufficient accuracy for stability checks in CHM isEmpty()* and size() methods.  Accessed only either within locks or* among other volatile reads that maintain visibility.*/transient int modCount;/*** The table is rehashed when its size exceeds this threshold.* (The value of this field is always <tt>(int)(capacity ** loadFactor)</tt>.)*/transient int threshold;/*** The load factor for the hash table.  Even though this value* is same for all segments, it is replicated to avoid needing* links to outer object.** @serial*/final float loadFactor;Segment(float lf, int threshold, HashEntry<K, V>[] tab) {this.loadFactor = lf;this.threshold = threshold;this.table = tab;}

PUT方法

    @SuppressWarnings("unchecked")public V put(K key, V value) {Segment<K, V> s;if (value == null)throw new NullPointerException();// 计算hash值int hash = hash(key);// segmentMask = ssize - 1// 下面算segment的下标int j = (hash >>> segmentShift) & segmentMask;// 判断segment是不是nullif ((s = (Segment<K, V>) UNSAFE.getObject          // nonvolatile; recheck(segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegments = ensureSegment(j);// segment不为空直接putreturn s.put(key, hash, value, false);}

创建segment

只需要一个线程来创建segment,另一个线程也是得到同一个Segment

/*** Returns the segment for the given index, creating it and* recording in segment table (via CAS) if not already present.** @param k the index* @return the segment*/@SuppressWarnings("unchecked")private Segment<K, V> ensureSegment(int k) {final Segment<K, V>[] ss = this.segments;long u = (k << SSHIFT) + SBASE; // raw offsetSegment<K, V> seg;// 只有一个线程拿到ss是null,一个线程进入ifif ((seg = (Segment<K, V>) UNSAFE.getObjectVolatile(ss, u)) == null) {// 原型模式Segment<K, V> proto = ss[0]; // use segment 0 as prototypeint cap = proto.table.length;float lf = proto.loadFactor;// 0.75x16 = 12int threshold = (int) (cap * lf);HashEntry<K, V>[] tab = (HashEntry<K, V>[]) new HashEntry[cap];// DCLif ((seg = (Segment<K, V>) UNSAFE.getObjectVolatile(ss, u))== null) { // recheck// 创建Segment对象Segment<K, V> s = new Segment<K, V>(lf, threshold, tab);while ((seg = (Segment<K, V>) UNSAFE.getObjectVolatile(ss, u))== null) {// 把新segment放入数组if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))break;}}}return seg;}

PUT

final V put(K key, int hash, V value, boolean onlyIfAbsent) {// 两个线程进来,tryLock(),拿到锁就可以走下面流程放入元素,没有的话就可以走scanAndLockForPut流程// 在等待锁的过程中可以执行相关代码,也就是自旋// lock是阻塞,trylock是非阻塞HashEntry<K, V> node = tryLock() ? null :scanAndLockForPut(key, hash, value);V oldValue;try {HashEntry<K, V>[] tab = table;// 计算HashEntry tab的下标int index = (tab.length - 1) & hash;HashEntry<K, V> first = entryAt(tab, index);// 以下就是hashmap的代码for (HashEntry<K, V> e = first; ; ) {if (e != null) {K k;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 {if (node != null)node.setNext(first);elsenode = new HashEntry<K, V>(hash, key, value, first);int c = count + 1;if (c > threshold && tab.length < MAXIMUM_CAPACITY)rehash(node);elsesetEntryAt(tab, index, node);++modCount;count = c;oldValue = null;break;}}} finally {unlock();}return oldValue;}

scanAndLockForPut

保证一定要Segment加上锁

/*** Scans for a node containing given key while trying to* acquire lock, creating and returning one if not found. Upon* return, guarantees that lock is held. UNlike in most* methods, calls to method equals are not screened: Since* traversal speed doesn't matter, we might as well help warm* up the associated code and accesses as well.** @return a new node if key not found, else null*/private HashEntry<K, V> scanAndLockForPut(K key, int hash, V value) {HashEntry<K, V> first = entryForHash(this, hash);HashEntry<K, V> e = first;HashEntry<K, V> node = null;int retries = -1; // negative while locating node// 自旋锁,等待的过程可以执行其他流程// 下面可以创建HashEntry node对象while (!tryLock()) {HashEntry<K, V> f; // to recheck first belowif (retries < 0) {// e 是头节点,如果e==null,表示遍历到链表最后if (e == null) {if (node == null) // speculatively create nodenode = new HashEntry<K, V>(hash, key, value, null);retries = 0;// 如果key已经存在,则不创建node对象} else if (key.equals(e.key))retries = 0;else// 相当于遍历链表e = e.next;// static final int MAX_SCAN_RETRIES =// Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;} else if (++retries > MAX_SCAN_RETRIES) {// 超过阈值就会保证加上锁lock();break;// retries & 1 保证偶数次重试的时候,判断头节点是不是一样的// 如果头节点不一样表示头节点被修改,插入了元素} else if ((retries & 1) == 0 &&(f = entryForHash(this, hash)) != first) {// 保证是最新的头节点e = first = f; // re-traverse if entry changedretries = -1;}}return node;}

GET方法

    /*** Returns the value to which the specified key is mapped,* or {@code null} if this map contains no mapping for the key.** <p>More formally, if this map contains a mapping from a key* {@code k} to a value {@code v} such that {@code key.equals(k)},* then this method returns {@code v}; otherwise it returns* {@code null}.  (There can be at most one such mapping.)** @throws NullPointerException if the specified key is null*/public V get(Object key) {Segment<K, V> s; // manually integrate access methods to reduce overheadHashEntry<K, V>[] tab;int h = hash(key);long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;if ((s = (Segment<K, V>) UNSAFE.getObjectVolatile(segments, u)) != null &&(tab = s.table) != null) {for (HashEntry<K, V> e = (HashEntry<K, V>) UNSAFE.getObjectVolatile(tab, ((long) (((tab.length - 1) & h)) << TSHIFT) + TBASE);e != null; e = e.next) {K k;if ((k = e.key) == key || (e.hash == h && key.equals(k)))return e.value;}}return null;}

Size方法

本质上是遍历每一个segment,加上所有的node节点

    public int size() {// Try a few times to get accurate count. On failure due to// continuous async changes in table, resort to locking.final Segment<K, V>[] segments = this.segments;int size;boolean overflow; // true if size overflows 32 bitslong sum;         // sum of modCountslong last = 0L;   // previous sumint retries = -1; // first iteration isn't retrytry {for (; ; ) {if (retries++ == RETRIES_BEFORE_LOCK) {for (int j = 0; j < segments.length; ++j)ensureSegment(j).lock(); // force creation}sum = 0L;size = 0;overflow = false;// 遍历每一个segmentsfor (int j = 0; j < segments.length; ++j) {Segment<K, V> seg = segmentAt(segments, j);if (seg != null) {sum += seg.modCount;int c = seg.count;if (c < 0 || (size += c) < 0)overflow = true;}}if (sum == last)break;last = sum;}} finally {if (retries > RETRIES_BEFORE_LOCK) {for (int j = 0; j < segments.length; ++j)segmentAt(segments, j).unlock();}}return overflow ? Integer.MAX_VALUE : size;}

总结

ConcurrentHashMap采用了分段锁的设计,当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道要放在哪一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就没有锁竞争,实现真正的并行插入。相比于对整个Map加锁的设计,分段锁大大的提高了高并发环境下的处理能力。但同时,由于不是对整个Map加锁,导致一些需要扫描整个Map的方法(如size(), containsValue())需要使用特殊的实现,另外一些方法(如clear())甚至放弃了对一致性的要求(ConcurrentHashMap是弱一致性的)。

假如new ConcurrentHashMap(32, 0.75, 16)就是新建了一个ConcurrentHashMap,他的容量是32,分段锁的个数是16,也就是每个Segment里面HashEntry[]数组的长度是2。但是new ConcurrentHashMap()时,每个Segment里面HashEntry[]数组的长度也是2,因为ConcurrentHashMap规定了Segment数组中HashEntry数组的长度是2。

相关文章:

【ConcurrentHashMap1.7源码】十分钟带你深入ConcurrentHashMap并发解析

ConcurrentHashMap1.7源码 四个核心要点 初始化PUT扩容GET Unsafe 初始化 五个构造方法 /*** Creates a new, empty map with the default initial table size (16).*/public ConcurrentHashMap() {}/*** Creates a new, empty map with an initial table size* accommodati…...

程序框架-事件中心模块-观察者模式

一、观察者模式 1.1 观察者模式定义 意图&#xff1a; 定义对象间的一种一对多的依赖关系&#xff0c;当一个对象的状态发生改变是&#xff0c;所有依赖于它的对象都能得到通知并自动更新。 适用性&#xff1a; 当一个对象状态的改变需要改变其他对象&#xff0c; 或实际对…...

通过AOP的ProceedingJoinPoint获取方法信息

文章目录 ProceedingJoinPoint用法 ProceedingJoinPoint用法 获得切点对应的方法&#xff08;Method&#xff09; 本处Method指的是java.lang.reflect.Method 若切入点表达式是方法&#xff0c;则获得的是切入点方法的信息。若切入点表达式是注解&#xff0c;则获得的是使用了…...

【JavaSE】初步认识类和对象

【本节目标】 1. 掌握类的定义方式以及对象的实例化 2. 掌握类中的成员变量和成员方法的使用 3. 掌握对象的整个初始化过程 目录 1. 面向对象的初步认知 2. 类定义和使用 3. 类的实例化 4. this引用 1. 面向对象的初步认知 1.1 什么是面向对象 Java是一门纯面向对象的语…...

python中的matplotlib画饼图(数据分析与可视化)

直接开始 1、先安装pandas和matplotlib pip install pandas pip install matplotlib2、然后在py文件中导入 import pandas as pd import matplotlib.pyplot as plt3、然后直接写代码 import pandas as pd import matplotlib.pyplot as pltpd.set_option("max_columns&…...

用Rust实现23种设计模式之 职责链模式

关注我&#xff0c;学习Rust不迷路&#xff01;&#xff01; 优点 解耦&#xff1a;职责链模式将请求发送者和接收者解耦&#xff0c;使得多个对象都有机会处理请求&#xff0c;而不是将请求的发送者和接收者紧密耦合在一起。灵活性&#xff1a;可以动态地改变或扩展处理请求…...

进销存管理中的技术创新和数字化转型

在进销存管理中&#xff0c;技术创新和数字化转型可以通过以下具体的应用案例来实现&#xff1a; 自动化仓储系统&#xff1a;利用自动化技术和机器人系统来管理仓库操作&#xff0c;包括货物的装卸、分拣和存储。这可以提高仓库的运作效率&#xff0c;减少人力成本&#xff0…...

与“云”共舞,联想凌拓的新科技与新突破

伴随着数字经济的高速发展&#xff0c;IT信息技术在数字中国建设中起到的驱动和支撑作用也愈发凸显。特别是2023年人工智能和ChatGPT在全球的持续火爆&#xff0c;更是为整个IT产业注入了澎湃动力。那么面对日新月异的IT信息技术&#xff0c;再结合疫情之后截然不同的经济环境和…...

【超细节】Vue3组件事件怎么声明,defineEmits与emit

目录 前言 一、基本语法 1. 子组件触发 2. 父组件监听 二、 事件参数 1. 传值 2. 接收值 三、 事件校验 四、注意事项 前言 组件事件是 Vue 组件之间进行通信的一种方式。它允许一个组件触发一个自定义事件&#xff0c;并且其他组件可以监听并响应这个事件。 一、基本…...

java Selenium 实现简单的网页操作

官方文档&#xff1a;入门指南 | Selenium Selenium是一个用于Web应用测试的工具。Selenium测试直接运行在浏览器中&#xff0c;就像真正的用户在操作一样。 所以使用这个前端测试话工具&#xff0c;可以自动化做很多事情&#xff0c;比如自动化抓取网页内容&#xff0c;俗称网…...

(数据库系统概论|王珊)第一章绪论-第一节:数据库系统概论

目录 一&#xff1a;四大基本概念 &#xff08;1&#xff09;数据(Data) &#xff08;2&#xff09;数据库(DataBase,DB) &#xff08;3&#xff09;数据库管理系统(DataBase Management System,DBMS) &#xff08;4&#xff09;数据库系统(Database System&#xff0c;DBS…...

深入理解TCP三次握手:连接可靠性与安全风险

目录 导言TCP简介和工作原理的回顾TCP三次握手的目的和步骤TCP三次握手过程中可能出现的问题和安全风险为什么TCP三次握手是必要的&#xff1f;是否可以增加或减少三次握手的次数&#xff1f;TCP四次挥手与三次握手的异同点 导言 在网络通信中&#xff0c;TCP&#xff08;Tra…...

基于人工智能的智能矿山解决方案

什么是智能矿山&#xff1f; 智能矿山是一种运用先进技术和智能化系统来管理和监控矿山运营的概念。它利用传感器、无线通信、数据分析和人工智能等技术&#xff0c;实现对矿山内部各个环节的实时监测、自动化控制和智能决策&#xff0c;从而提高矿山的效率、安全性和可持续性。…...

vue-cli3项目优化

首先添加两个量化的插件&#xff0c;方便对项目目前的情况进行分析&#xff1a; 1.添加speed-measure-webpack-plugin插件 —量化的指标可以看出前后对比 使用步骤&#xff1a; 安装speed-measure-webpack-plugin依赖 npm install speed-measure-webpack-plugin -D配置vue.c…...

Windows环境下VSCode安装PlatformIO Cero报错ERROR: HTTP error 403 while getting

安装PlatformIO插件成功&#xff0c;初始化失败 错误信息判断问题尝试访问https://pypi.tuna.tsinghua.edu.cn/simple/platformio/成功点击文件后报错如下&#xff1a; 解决问题- 换源 &#xff08; Windows下有两个地方需要更改&#xff09;cmd命令行Pip文件 总结&#xff1a;…...

git bash 安装sdkadmin

1.下载相关安装包,复制到git 安装目录 D:\software\Git\mingw64\bin 2. 运行 curl -s "https://get.sdkman.io" | bash...

如何在IEEE论文中添加伪代码pseudocode

前言 记录写论文过程中需要重复用的一些小技巧&#xff1a; 一、如何在IEEE论文中添加伪代码pseudocode pseudocode是经常需要在论文中使用的流程图&#xff0c;掌握如何写伪代码图是必须得。 1.引入库 代码如下&#xff08;示例&#xff09;&#xff1a; # 头部添加不可少的…...

【css】css隐藏元素

display:none&#xff1a;可以隐藏元素。该元素将被隐藏&#xff0c;并且页面将显示为好像该元素不在其中。visibility:hidden&#xff1a; 可以隐藏元素。但是&#xff0c;该元素仍将占用与之前相同的空间。元素将被隐藏&#xff0c;但仍会影响布局。 代码&#xff1a; <!…...

JUC并发编程(二)ForkJoinPool、Future、CompletableFuture、CAS

文章目录 ForkJoin分治工作窃取ForkJoinPool与ThreadPoolExecutor使用案例不带返回值的计算--RecursiveAction带返回值的计算--RecursiveTask Future 异步回调烧水案例join实现FutureTask实现 CompletableFuture为什么叫CompletableFuture?创建异步任务supplyAsyncrunAsync获取…...

大数据课程F2——HIve的安装操作

文章作者邮箱:yugongshiye@sina.cn 地址:广东惠州 ▲ 本章节目的 ⚪ 了解HIve的安装概念; ⚪ 掌握HIve安装步骤和Linux常用命令; ⚪ 掌握HIve安装的连接池jar包冲突和日志打印jar包冲突; ⚪ 掌握HIve安装的Hadoop安装配置; ⚪ 掌握HIve安装的JDK安装配…...

ComfyUI-Impact-Pack完整指南:3步掌握AI图像增强的强大工具包

ComfyUI-Impact-Pack完整指南&#xff1a;3步掌握AI图像增强的强大工具包 【免费下载链接】ComfyUI-Impact-Pack Custom nodes pack for ComfyUI This custom node helps to conveniently enhance images through Detector, Detailer, Upscaler, Pipe, and more. 项目地址: h…...

Chatbox:重新定义AI交互体验的全能客户端

Chatbox&#xff1a;重新定义AI交互体验的全能客户端 【免费下载链接】chatbox Powerful AI Client 项目地址: https://gitcode.com/GitHub_Trending/ch/chatbox 一、认知层&#xff1a;探索Chatbox的核心价值与技术优势 在AI应用快速发展的今天&#xff0c;选择合适的…...

忍者像素绘卷实战教程:微信小程序用户上传文字→返回像素图→支持长按保存

忍者像素绘卷实战教程&#xff1a;微信小程序用户上传文字→返回像素图→支持长按保存 1. 项目概述与核心价值 忍者像素绘卷是一款基于Z-Image-Turbo深度优化的图像生成工具&#xff0c;专为微信小程序环境设计。它能够将用户输入的文字描述转化为具有16-Bit复古游戏风格的像…...

拼多多商品价格监控实战:用Python爬虫+Excel自动生成竞品分析报告

拼多多竞品价格监控系统&#xff1a;从数据采集到商业决策的全链路实战 在电商行业&#xff0c;价格策略往往是决定销量的关键因素。想象一下这样的场景&#xff1a;你负责运营一家数码配件店铺&#xff0c;某天突然发现竞品的蓝牙耳机价格下调了15%&#xff0c;而你的库存还保…...

# Python 3.11/3.12/3.13 版本选择指南

Python采用年度发布节奏&#xff0c;三个版本处于不同的生命周期阶段&#xff0c;特性与稳定性差异显著&#xff1a;版本发布时间维护截止日期当前状态生态成熟度推荐指数3.112022.102027.10活跃维护后期99%★★★★☆3.122023.102028.10活跃维护中期95%★★★★★3.132024.102…...

Multisim电路仿真与Qwen3.5-2B结合:自动化生成电路分析报告

Multisim电路仿真与Qwen3.5-2B结合&#xff1a;自动化生成电路分析报告 1. 电子工程师的设计痛点 每个电子工程师都经历过这样的场景&#xff1a;在Multisim中反复调整电路参数&#xff0c;盯着示波器波形来回对比&#xff0c;手动记录各项性能指标&#xff0c;最后还要花大量…...

别再只盯着YOLO了!用ByteTrack在Python里实现一个简易的车辆跟踪器(附完整代码)

用PythonByteTrack打造高精度车辆追踪系统&#xff1a;从原理到实战 在智能交通和视频监控领域&#xff0c;目标追踪技术正发挥着越来越重要的作用。当我们需要分析交通流量、统计车辆类型或监测异常行为时&#xff0c;仅仅依靠目标检测是远远不够的——我们还需要知道同一个目…...

避坑指南:Raspberry Pi5安装LineageOS21常见问题全解(SSD启动/存储扩容/Play商店报错)

Raspberry Pi5安装LineageOS 21避坑指南&#xff1a;从SSD启动到Play商店认证全流程解析 当Raspberry Pi5遇上LineageOS 21&#xff0c;这个组合让单板计算机瞬间变身高性能Android设备。但实际安装过程中&#xff0c;从存储介质选择到Google服务集成&#xff0c;每个环节都可能…...

Java整合海康威视热成像SDK实战:从设备登录到实时测温数据获取的完整流程(附避坑指南)

Java整合海康威视热成像SDK实战&#xff1a;从设备登录到实时测温数据获取的完整流程&#xff08;附避坑指南&#xff09; 在工业检测、医疗诊断、安防监控等领域&#xff0c;热成像技术的应用越来越广泛。海康威视作为国内领先的安防设备供应商&#xff0c;其热成像设备凭借高…...

简单理解:C++为什么要写类,我单独定义函数不可以吗?

不写类&#xff08;单独函数&#xff09; vs 写类&#xff08;装进盒子&#xff09;对比项不写类&#xff08;单独函数&#xff09;写类&#xff08;LLM 类&#xff09;代码样子String answer() {...}void save_history() {...}class LLM { String answer(); void save_history…...