ThreadLocal :在 Java中隱匿的魔法之力

优质博文:IT-BLOG-CN

ThreadLocal 并不是一个Thread,而是 ThreadLocalVariable(线程局部变量)。也许把它命名为 ThreadLocalVar更加合适。线程局部变量就是为每一个使用该变量的线程都提供一个变量值的副本,是 Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。ThreadLocal是除了加锁这种同步方式之外的另一种保证多线程访问出现线程不安全的方式。

从线程的角度看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的,ThreadLocal实例就是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。

通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM 为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。在 ThreadLocal类中有一个 Map,用于存储每一个线程变量的副本,Map中元素的键为线程对象,而值为对应线程的变量副本。ThreadLocal采用了 “以空间换时间” 的方式。为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

一、API 说明

【1】new ThreadLocal(): 创建一个线程本地变量。
【2】T get(): 返回此线程局部变量的当前线程副本中的值,如果这是线程第一次调用该方法,则创建并初始化此副本。
【3】void set(T value): 将此线程局部变量的当前线程副本中的值设置为指定值。许多应用程序不需要这项功能,它们只依赖于 initialValue() 方法来设置线程局部变量的值。
【4】void remove(): 移除此线程局部变量的值。这可能有助于减少线程局部变量的存储需求。如果再次访问此线程局部变量,那么在默认情况下它将拥有其 initialValue。
【5】protected T initialValue(): 返回此线程局部变量的当前线程的初始值。最多在每次访问线程来获得每个线程局部变量时调用此方法一次,即线程第一次使用 get() 方法访问变量的时候。如果线程先调用 set(T) 方法再调用 get() 方法,则不会在线程中再调用 initialValue() 方法。若该实现只返回 null;如果程序员希望将线程局部变量初始化为 null 以外的某个值,则必须为 ThreadLocal 创建子类,并重写此方法。通常,将使用匿名内部类。initialValue 的典型实现将调用一个适当的构造方法,并返回新构造的对象。

在程序中一般都重写 initialValue方法,以给定一个特定的初始值。

二、ThreadLocal 简单使用

下面的例子中,开启两个线程,在每个线程内部设置了本地变量的值,然后调用 print方法打印当前本地变量的值。如果在打印之后调用本地变量的 remove方法会删除本地内存中的变量,代码如下所示:

package test;public class ThreadLocalTest {static ThreadLocal<String> localVar = new ThreadLocal<>();static void print(String str) {//打印当前线程中本地内存中本地变量的值System.out.println(str + " :" + localVar.get());//清除本地内存中的本地变量localVar.remove();}public static void main(String[] args) {Thread t1  = new Thread(new Runnable() {@Overridepublic void run() {//设置线程1中本地变量的值localVar.set("localVar1");//调用打印方法print("thread1");//打印本地变量System.out.println("after remove : " + localVar.get());}});Thread t2  = new Thread(new Runnable() {@Overridepublic void run() {//设置线程1中本地变量的值localVar.set("localVar2");//调用打印方法print("thread2");//打印本地变量System.out.println("after remove : " + localVar.get());}});t1.start();t2.start();}
}

下面是运行后的结果:

thread1 :localVar1
after remove : null
thread2 :localVar2
after remove : null

三、ThreadLocal 的实现原理

下面是 ThreadLocal的类图结构,从图中可知:Thread类中有两个变量 threadLocals和 inheritableThreadLocals,二者都是 ThreadLocal内部类 ThreadLocalMap类型的变量,我们通过查看内部内 ThreadLocalMap可以发现实际上它类似于一个HashMap。在默认情况下,每个线程中的这两个变量都为null。

ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

只有当线程第一次调用 ThreadLocal的 set或者 get方法的时候才会创建他们(后面我们会查看这两个方法的源码)。除此之外,每个线程的本地变量不是存放在 ThreadLocal实例中,而是放在调用线程的 ThreadLocals变量里面(前面也说过,该变量是Thread类的变量)。也就是说,ThreadLocal类型的本地变量是存放在具体的线程空间上,其本相当于一个装载本地变量的工具壳,通过 set方法将 value添加到调用线程的 threadLocals中,当调用线程调用 get方法时候能够从它的 threadLocals中取出变量。如果调用线程一直不终止,那么这个本地变量将会一直存放在他的 threadLocals中,所以不使用本地变量的时候需要调用 remove方法将 threadLocals中删除不用的本地变量。下面我们通过查看 ThreadLocal的set、get以及remove方法来查看 ThreadLocal具体实怎样工作的。

【1】set方法源码

public void set(T value) {//(1)获取当前线程(调用者线程)Thread t = Thread.currentThread();//(2)以当前线程作为key值,去查找对应的线程变量,找到对应的mapThreadLocalMap map = getMap(t);//(3)如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值if (map != null)map.set(this, value);//(4)如果map为null,说明首次添加,需要首先创建出对应的mapelsecreateMap(t, value);
}

在上面的代码中,(2)处调用 getMap方法获得当前线程对应的 threadLocals(参照上面的图示和文字说明),该方法代码如下:

ThreadLocalMap getMap(Thread t) {return t.threadLocals; //获取线程自己的变量threadLocals,并绑定到当前调用线程的成员变量threadLocals上
}

如果调用 getMap方法返回值不为 null,就直接将 value值设置到 threadLocals中(key为当前线程引用,值为本地变量);如果getMap方法返回 null说明是第一次调用 set方法(前面说到过,threadLocals默认值为null,只有调用 set方法的时候才会创建map),这个时候就需要调用 createMap方法创建 threadLocals,该方法如下所示:createMap方法不仅创建了threadLocals,同时也将要添加的本地变量值添加到了 threadLocals中。

void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}

【2】get方法源码: 在 get方法的实现中,首先获取当前调用者线程,如果当前线程的 threadLocals不为null,就直接返回当前线程绑定的本地变量值,否则执行 setInitialValue方法初始化 threadLocals变量。在 setInitialValue方法中,类似于 set方法的实现,都是判断当前线程的 threadLocals变量是否为null,是则添加本地变量(这个时候由于是初始化,所以添加的值为null),否则创建 threadLocals变量,同样添加的值为null。

public T get() {//(1)获取当前线程Thread t = Thread.currentThread();//(2)获取当前线程的threadLocals变量ThreadLocalMap map = getMap(t);//(3)如果threadLocals变量不为null,就可以在map中查找到本地变量的值if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}//(4)执行到此处,threadLocals为null,调用该更改初始化当前线程的threadLocals变量return setInitialValue();
}private T setInitialValue() {//protected T initialValue() {return null;}T value = initialValue();//获取当前线程Thread t = Thread.currentThread();//以当前线程作为key值,去查找对应的线程变量,找到对应的mapThreadLocalMap map = getMap(t);//如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值if (map != null)map.set(this, value);//如果map为null,说明首次添加,需要首先创建出对应的mapelsecreateMap(t, value);return value;
}

【3】remove方法的实现: remove方法判断当前线程对应的 threadLocals变量是否为null,不为 null就直接删除当前线程中指定的 threadLocals变量。

public void remove() {//获取当前线程绑定的threadLocalsThreadLocalMap m = getMap(Thread.currentThread());//如果map不为null,就移除当前线程中指定ThreadLocal实例的本地变量if (m != null)m.remove(this);
}

【4】如下图所示: 每个线程内部有一个名为 threadLocals的成员变量,该变量的类型为 ThreadLocal.ThreadLocalMap类型(类似于一个HashMap),其中的 key为当前定义的 ThreadLocal变量的 this引用,value为我们使用 set方法设置的值。每个线程的本地变量存放在自己的本地内存变量 threadLocals中,如果当前线程一直不消亡,那么这些本地变量就会一直存在(可能会导致内存溢出),因此使用完毕需要将其 remove掉。

四、ThreadLocal 不支持继承性

同一个 ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的。(threadLocals 中为当前调用线程对应的本地变量,所以二者自然是不能共享的)

package test;public class ThreadLocalTest2 {//(1)创建ThreadLocal变量public static ThreadLocal<String> threadLocal = new ThreadLocal<>();public static void main(String[] args) {//在main线程中添加main线程的本地变量threadLocal.set("mainVal");//新创建一个子线程Thread thread = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("子线程中的本地变量值:"+threadLocal.get());}});thread.start();//输出main线程中的本地变量值System.out.println("mainx线程中的本地变量值:"+threadLocal.get());}
}

五、InheritableThreadLocal 类

在上面说到的 ThreadLocal类是不能提供子线程访问父线程的本地变量的,而 InheritableThreadLocal类则可以做到这个功能,下面是该类的源码

public class InheritableThreadLocal<T> extends ThreadLocal<T> {protected T childValue(T parentValue) {return parentValue;}ThreadLocalMap getMap(Thread t) {return t.inheritableThreadLocals;}void createMap(Thread t, T firstValue) {t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);}
}

从上面代码可以看出,InheritableThreadLocal类继承了 ThreadLocal类,并重写了childValue、getMap、createMap三个方法。其中 createMap方法在被调用(当前线程调用 set方法时得到的 map为 null的时候需要调用该方法)的时候,创建的是inheritableThreadLocal而不是 threadLocals。同理,getMap方法在当前调用者线程调用 get方法的时候返回的也不是threadLocals 而是 inheritableThreadLocal。下面我们看看重写的 childValue方法在什么时候执行,怎样让子线程访问父线程的本地变量值。我们首先从 Thread类开始说起

private void init(ThreadGroup g, Runnable target, String name,long stackSize) {init(g, target, name, stackSize, null, true);
}
private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {//判断名字的合法性if (name == null) {throw new NullPointerException("name cannot be null");}this.name = name;//(1)获取当前线程(父线程)Thread parent = currentThread();//安全校验SecurityManager security = System.getSecurityManager();if (g == null) { //g:当前线程组if (security != null) {g = security.getThreadGroup();}if (g == null) {g = parent.getThreadGroup();}}g.checkAccess();if (security != null) {if (isCCLOverridden(getClass())) {security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);}}g.addUnstarted();this.group = g; //设置为当前线程组this.daemon = parent.isDaemon();//守护线程与否(同父线程)this.priority = parent.getPriority();//优先级同父线程if (security == null || isCCLOverridden(parent.getClass()))this.contextClassLoader = parent.getContextClassLoader();elsethis.contextClassLoader = parent.contextClassLoader;this.inheritedAccessControlContext =acc != null ? acc : AccessController.getContext();this.target = target;setPriority(priority);//(2)如果父线程的inheritableThreadLocal不为nullif (inheritThreadLocals && parent.inheritableThreadLocals != null)//(3)设置子线程中的inheritableThreadLocals为父线程的inheritableThreadLocalsthis.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);this.stackSize = stackSize;tid = nextThreadID();
}

在 init方法中,首先(1)处获取了当前线程(父线程),然后(2)处判断当前父线程的 inheritableThreadLocals是否为null,然后调用createInheritedMap将父线程的 inheritableThreadLocals作为构造函数参数创建了一个新的 ThreadLocalMap变量,然后赋值给子线程。下面是 createInheritedMap方法和 ThreadLocalMap的构造方法

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {return new ThreadLocalMap(parentMap);
}private ThreadLocalMap(ThreadLocalMap parentMap) {Entry[] parentTable = parentMap.table;int len = parentTable.length;setThreshold(len);table = new Entry[len];for (int j = 0; j < len; j++) {Entry e = parentTable[j];if (e != null) {@SuppressWarnings("unchecked")ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();if (key != null) {//调用重写的方法Object value = key.childValue(e.value);Entry c = new Entry(key, value);int h = key.threadLocalHashCode & (len - 1);while (table[h] != null)h = nextIndex(h, len);table[h] = c;size++;}}}
}

在构造函数中将父线程的 inheritableThreadLocals成员变量的值赋值到新的 ThreadLocalMap对象中。返回之后赋值给子线程的inheritableThreadLocals。总之,InheritableThreadLocals类通过重写getMap和createMap两个方法将本地变量保存到了具体线程的inheritableThreadLocals变量中,当线程通过InheritableThreadLocals实例的set或者get方法设置变量的时候,就会创建当前线程的inheritableThreadLocals变量。而父线程创建子线程的时候,ThreadLocalMap中的构造函数会将父线程的inheritableThreadLocals中的变量复制一份到子线程的inheritableThreadLocals变量中。

六、从 ThreadLocalMap看 ThreadLocal使用不当的内存泄漏问题

【1】基础概念 : 首先我们先看看 ThreadLocalMap的类图,在前面的介绍中,我们知道 ThreadLocal只是一个工具类,他为用户提供get、set、remove接口操作实际存放本地变量的 threadLocals(调用线程的成员变量),也知道 threadLocals是一个ThreadLocalMap类型的变量,下面我们来看看 ThreadLocalMap这个类。在此之前,我们回忆一下 Java中的四种引用类型链接

【2】分析 ThreadLocalMap内部实现: 上面我们知道 ThreadLocalMap内部实际上是一个 Entry数组 private Entry[] table ​,我们先看看 Entry的这个内部类

/*** 是继承自WeakReference的一个类,该类中实际存放的key是指向ThreadLocal的弱引用和与之对应的value值(该value值* 就是通过ThreadLocal的set方法传递过来的值)由于是弱引用,当get方法返回null的时候意味着回收引用*/
static class Entry extends WeakReference<ThreadLocal<?>> {/** value就是和ThreadLocal绑定的 */Object value;//k:ThreadLocal的引用,被传递给WeakReference的构造方法Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}
}
//WeakReference构造方法(public class WeakReference<T> extends Reference<T> )
public WeakReference(T referent) {super(referent); //referent:ThreadLocal的引用
}//Reference构造方法
Reference(T referent) {this(referent, null);//referent:ThreadLocal的引用
}Reference(T referent, ReferenceQueue<? super T> queue) {this.referent = referent;this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}

在上面的代码中,我们可以看出,当前 ThreadLocal的引用 k被传递给 WeakReference的构造函数,所以 ThreadLocalMap中的key为 ThreadLocal的弱引用。当一个线程调用ThreadLocal的 set方法设置变量的时候,当前线程的 ThreadLocalMap就会存放一个记录,这个记录的 key值为 ThreadLocal的弱引用,value就是通过 set设置的值。如果当前线程一直存在且没有调用该ThreadLocal的 remove方法,如果这个时候别的地方还有对 ThreadLocal的引用,那么当前线程中的 ThreadLocalMap中会存在对ThreadLocal变量的引用和 value对象的引用,是不会释放的,就会造成内存泄漏。

考虑这个 ThreadLocal变量没有其他强依赖,如果当前线程还存在,由于线程的 ThreadLocalMap里面的 key是弱引用,所以当前线程的 ThreadLocalMap里面的 ThreadLocal变量的弱引用在 gc的时候就被回收,但是对应的 value还是存在的这就可能造成内存泄漏(因为这个时候 ThreadLocalMap会存在key为 null但是 value不为 null的 entry项 )。

ThreadLocalMap 中的 Entry的 key使用的是 ThreadLocal对象的弱引用,在没有其他地方对 ThreadLoca依赖,ThreadLocalMap中的 ThreadLocal对象就会被回收掉,但是对应的 value不会被回收,这个时候 Map中就可能存在 key为null但是 value不为null的项,这需要实际使用的时候使用完毕及时调用 remove方法避免内存泄漏。

如果使用线程池,由于线程可能并不是真正的关闭(比如newFixedThreadPool会保持线程一只存活)。因此,如果将一些大对象存放到 ThreadLocalMap中,可能会造成内存泄漏。因为线程没有关闭,无法回收,但是这些对象不会再被使用了。如果希望及时回收对象,则可以使用Thread.remove()方法将变量移除。

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

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

相关文章

MySQL--优化(索引--聚簇和非聚簇索引)

MySQL–优化&#xff08;索引–聚簇和非聚簇索引&#xff09; 定位慢查询SQL执行计划索引 存储引擎索引底层数据结构聚簇和非聚簇索引索引创建原则索引失效场景 SQL优化经验 一、聚簇索引 聚簇索引&#xff1a;将数据存储与索引放到了一块&#xff0c;索引结构的叶子节点保存…

01-环境搭建、SpringCloud微服务-黑马头条

环境搭建、SpringCloud微服务(注册发现、服务调用、网关) 1)课程对比 2)项目概述 2.1)能让你收获什么 2.2)项目课程大纲 2.3)项目概述 随着智能手机的普及&#xff0c;人们更加习惯于通过手机来看新闻。由于生活节奏的加快&#xff0c;很多人只能利用碎片时间来获取信息&…

数据开发 - 面经(已OC) - 北京中海通

投递流程&#xff1a; 2023.12.28 Boss 打招呼 2024.1.3 约面 2024.1.4 上午面试 &#xff08;手机端腾讯会议&#xff09; 2024.1.5 上午 通知面试通过 腾讯会议手机端无法和录影机同时运行&#xff0c;录音无效&#xff0c;之后注意使用电脑面试 面试流程&#xff1a;首…

搜维尔科技:3D Systems Geomagic Design X 逆向工程软件

产品概述 3D Systems Geomagic Design X 是全面的逆向工程软件 GeomagicoDesign XTM是全面的逆向工程软件&#xff0c;它结合了基于特征的CAD数模与三维扫描数据处理&#xff0c;使您能创建出可编辑、基于特征的CAD数模&#xff0c;并与您现有的CAD软件兼容。 拓展您的设计能…

两两交换链表中的节点+力扣

题目 题目链接 . - 力扣&#xff08;LeetCode&#xff09; 题目描述 代码实现 class Solution { public:ListNode* swapPairs(ListNode* head) {if(head nullptr || head->next nullptr) return head;ListNode *tmpHead swapPairs(head->next->next);ListNode …

Spring Boot异常处理和单元测试

1.SpringBoot异常处理 1.1.自定义错误页面 SpringBoot默认的处理异常的机制&#xff1a;SpringBoot 默认的已经提供了一套处理异常的机制。一旦程序中出现了异常 SpringBoot 会向/error 的 url 发送请求。在 springBoot 中提供了一个叫 BasicErrorController 来处理/error 请…

Qt/C++音视频开发68-检查是否含有B帧/转码推流/拉流显示/监控拉流推流/海康大华宇视监控

一、前言 为什么需要判断视频文件是否含有B帧&#xff0c;这个在推流的时候很容易遇到这个问题&#xff0c;一般来说&#xff0c;没有B帧的视频文件&#xff0c;解码后的数据帧pts和dts都是顺序递增的&#xff0c;而有B帧的则未必&#xff0c;可能有些需要先解码后面显示&…

让娃学习效率更高的“可视化”时间管理器

如果要问&#xff0c;老母亲在娃开学后&#xff0c;蕞着急孩子哪一种坏习惯&#xff0c;那时间管理肯定榜上有名&#xff01; 做作业的时候&#xff0c;才写了5分钟&#xff0c;已经没有耐心了&#xff0c;东摸摸西看看&#xff0c;一会说肚子疼想上厕所&#xff0c;一会又拿出…

【Java EE初阶三十】JVM的简单学习

1. JVM 内存区域划分 一个运行起来的 Java 进程&#xff0c;就是一个 JVM 虚拟机&#xff0c;需要从操作系统申请一大块内存&#xff0c;就会把这个内存&#xff0c;划分成不同的区域&#xff0c;每个区域都有不同的作用. JVM 申请了一大块内存之后,也会划分成不同的内…

Flink实时数仓之用户埋点系统(一)

需求分析及框架选型 需求分析数据采集用户行为采集业务数据采集 行为日志分析用户行为日志页面日志启动日志APP在线日志 业务数据分析用户Insert数据用户Update数据 技术选型Nginx配置Flume配置MaxWellHadoopFlink架构图 需求分析 数据采集 用户行为采集 行为数据&#xff1…

人类与智能体

1、人类与智能体 人类与智能体之间的关系在当今科技发展中变得日益紧密。智能体&#xff0c;作为人工智能领域的一个核心概念&#xff0c;通常指的是一种能够感知环境、做出决策并采取行动以实现特定目标的实体&#xff0c;它可以是软件系统、机器人或其他类型的自动化装置。 …

最小二乘法(带你 原理 实践)

文章目录 引言一、最小二乘法的基本原理二、最小二乘法的计算过程建立模型确定目标函数求解模型参数模型检验 三、最小二乘法的优缺点优点原理简单易懂统计特性优良适用范围广泛 缺点对异常值敏感假设条件较多 四、最小二乘法在实际应用中的案例五、如何克服最小二乘法的局限性…

vue-cli项目因为webpack版本不兼容运行后报错

vue-cli项目运行后报错&#xff1a; Error: Rule can only have one resource source (provided resource and test include exclude) in {"exclude": [null],"use": [{"loader": "G:\\CustomerDay\\customerday\\node_modules\\cache-l…

大语言模型的知识融合(ICLR2024)

一、写作动机&#xff1a; 虽然从头开始训练大型语言模型&#xff08;LLMs&#xff09;可以生成具有独特功能和优势的模型&#xff0c;但这种方法成本高昂&#xff0c;而且可能导致功能冗余。 二、主要贡献&#xff1a; 入了 LLMs 知识融合的概念&#xff0c;旨在结合现有 LL…

LiveNVR监控流媒体Onvif/RTSP功能-支持云端录像监控视频集中存储录像回看录像计划配置NVR硬件设备录像回看

LiveNVR支持云端录像监控视频集中存储录像回看录像计划配置NVR硬件设备录像回看 1、流媒体服务软件2、录像回看3、查看录像3.1、时间轴视图3.2、列表视图 4、如何分享时间轴录像回看&#xff1f;5、iframe集成示例7、录像计划7、相关问题7.1、录像存储位置如何配置&#xff1f;…

selenium也能过某数、5s盾..

文章转载于&#xff1a;selenium也能过某数、5s盾… 直接安装: pip install undetected_chromedriver运行代码&#xff1a; import undetected_chromedriver as uc import timedriver uc.Chrome(executable_pathrC:\Users\chromedriver.exe,version_main111) driver.get(网…

K线形态分析宝典:10种K线形态特征与应用场景详解,助您投资更有底气

在金融市场中&#xff0c;K线图是投资者们最常用的技术分析工具之一&#xff0c;通过观察K线形态可以揭示市场的走势和情绪。以下是10种常见的K线形态&#xff0c;包括详细的形态特征、作用以及应用场景&#xff0c;帮助您更好地理解市场走势&#xff0c;制定更精准的投资策略。…

JS实现chatgpt数据流式回复效果

最近高了一个简单chatgpt对话功功能&#xff0c;回复时希望流式回复&#xff0c;而不是直接显示结果&#xff0c;其实很简单&#xff0c;前端流式读取即可&#xff0c;后端SSE实现流式传输 前端用到fetch获取数据&#xff0c;然后利用reader读取 let requestId parseInt(Ma…

【Python】成功解决TypeError: ‘tuple‘ object does not support item assignment

【Python】成功解决TypeError: ‘tuple’ object does not support item assignment &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&am…

算法归纳【数组篇】

目录 二分查找1. 前提条件&#xff1a;2. 二分查找边界 2.移除元素有序数组的平方长度最小的子数组59.螺旋矩阵II54. 螺旋矩阵 二分查找 参考链接 https://programmercarl.com/0704.%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE.html#%E6%80%9D%E8%B7%AF 1. 前提条件&#xff1a; 数…