C++智能指针万字详细讲解(包含智能指针的模拟实现)

        在笔试,面试中智能指针经常出现,如果你对智能指针的作用,原理,用法不了解,那么可以看看这篇博客讲解,此外本博客还简单模拟实现了各种指针,在本篇的最后还应对面试题对智能指针的知识点进行了拓展。希望能加深你对智能指针的理解。那么开始学习吧!

一.智能指针作用

        C++的智能指针主要作用是为了防止内存泄漏。在代码中我们new出来的对象都需要delete,但是当我们我们忘记或者代码出现异常导致没有delete对象,就会产生内存泄漏。长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

        下面我们看看一个常见的因为异常导致内存泄漏的例子:

#include<iostream>
using namespace std;static int sa = 1;
int div_func(int a, int b)
{if (b == 0){throw invalid_argument("除0错误");}return a / b;
}
int Func1()
{int* a1 = new int{1};int* a2 = new int{sa};int n=div_func(*a1,*a2);sa--;delete a1;delete a2;return n;
}int main()
{try{while (1){int n = Func1();cout << n;}}catch (exception& e){cout << e.what() << endl;}return 0;
}

运行结果:

        main函数调用func1函数,func中new出了a1,和a2,然后调用div_func,当b=0,此时就会抛出异常,异常被mian函数捕获直接跳转,此时new出来的a1和a2就不会被delete,导致内存泄漏。这种代码的内存泄漏,还是比较难防备,此时就需要使用智能指针。

二.智能指针原理 

        我们首先介绍一下什么是RAII。

        RAII(Resource Acquisition Is Initialization)(资源获取即初始化)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。

        这种做法有两大好处:

  • 1.不需要显式地释放资源。
  • 2.对象所需的资源在其生命期内始终保持有效。

        智能指针也就是利用RAII的原理实现的,把管理一份资源的责任托管给了一个对象,通过构造函数获取资源,通过析构函数释放资源,看代码:

template<class T>
class SmartPtr {
public:SmartPtr(T* ptr = nullptr):_ptr(ptr){}~SmartPtr(){if (_ptr)delete _ptr;}
private:T* _ptr;
};

        这样我们就能通过类的生命周期来对资源进行管理和释放。对于最开始的代码我们只需要把 int* a1 = new int{1};int* a2 = new int{sa}代码写成SmartPtr<int > sp1=new{1};SmartPtr<int >sp2=new{sa},这样即使因为抛异常跳转到mian()函数也会因为生命周期的结束自动释放资源。上面的代码就是智能指针的基本原理。

三.智能指针介绍和使用 

C++常见的智能指针有三种,这里我们只做基本介绍和使用,详细特点我们后面实现再介绍。

1.std::unique_ptr

特点:独占资源所有权,不可复制(不能进行拷贝构造和赋值运算符重载)但支持移动语义,生命周期结束时自动释放资源,保证只有一个对象只有一个unique_ptr指针,避免重复析构。

class A
{
public:int a;A(int n){a = n;}~A(){std::cout << "调用析构" << std::endl;}};
int main()
{std::unique_ptr<A> ptr = std::make_unique<A>(1);//c++高版本写法。std::unique_ptr<A> ptr( new A(1));//第二种写法//std::unique_ptr<A> ptr1=ptr;//禁止了拷贝构造会报错std::unique_ptr<A> ptr2 = std::move(ptr);  // 所有权转移
}

2.std::shared_ptr

特点:共享资源所有权,通过引用计数管理生命周期,线程安全的引用计数更新。允许复制。

每复制一个shared_ptr,计数+1,析构一个计数-1,计数为零才调用析构。

class A
{
public:int a;A(int n){a = n;}~A(){std::cout << "调用析构" << std::endl;}};
int main()
{std::shared_ptr<A> ptr = std::make_shared<A>(1);std::shared_ptr<A> ptr1 = ptr;}

3.std::weak_ptr

  • 特点:弱引用,不影响 shared_ptr 的引用计数,需通过 lock() 提升为 shared_ptr 访问资源(后面详细讲解)。

四.简单模拟实现std::auto_ptr

        auto_ptr主要是在早期版本的C++使用,现在基本不会使用,特点是转移管理权,即当指针复制时,让新指针指向旧指针,再将旧指针指向空,我们主要做个了解。

namespace bit
{template<class T>class auto_ptr{public:auto_ptr(T* ptr = nullptr):_ptr(ptr){}//移交管理权auto_ptr(auto_ptr<T>& ap){_ptr = ap.get();ap._ptr = nullptr;}//释放原来的,接收管理权auto_ptr<T>& operator=(auto_ptr<T>& ap){if (this != &ap){// 释放当前对象中资源if (_ptr)delete _ptr;}_ptr=ap.get();ap.ptr=null;return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}~auto_ptr(){delete _ptr;}T* get(){return _ptr;}private:T* _ptr;};}class Date
{
public:Date(int year,int month,int day){_year = year;_month = month;_day = day;cout << "调用构造函数" << endl;}~Date(){cout << "调用析构" << endl;}int _year;int _month;int _day;
};
int main()
{bit::auto_ptr<int> ap1 = new int{ 1 };bit::auto_ptr<Date> ap2 = new Date{ 1,1,1 };cout << *ap1 << endl;cout << ap2->_year << endl;bit::auto_ptr<Date> ap3 = ap2;}

运行结果:

最后一行时的监视窗口:

        auto_ptr作为智能指针,当调用拷贝构造或赋值运算符重载,不允许多个智能指针指向同一个对象,而是将一个智能指针的资源管理权移交给另外一个智能指针,这种做法是不太好的,这意味着,赋值后的原auto_ptr对象将不再拥有指针的所有权,其内部指针会被置为NULL。这种行为可能导致一些潜在的错误,因为程序员可能期望原对象仍然拥有指针的所有权。很多公司明确要求不能使用auto_ptr。

五.简单模拟实现std::unique_ptr

        unique_ptr的实现原理:简单粗暴的防拷贝,下面简化模拟实现了一份UniquePtr来了解它的原理。

	template<class T>class unique_ptr{public:unique_ptr(T* ptr)	:_ptr(ptr){}unique_ptr (unique_ptr& up) = delete;unique_ptr<T>& operator=(unique_ptr& up) = delete;T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get(){return _ptr;}private:T* _ptr;};

        通过删除拷贝构造函数和赋值运算符重载来确保指向该资源的只有该智能指针。也就是说是一个资源只能有一个智能指针。

六.简单模拟实现std::shared_ptr

       上面的俩种指针之所以只能做到一个资源只能有一个智能指针,是因为没有解决多个智能指针指向一份资源从而导致重复析构的问题而shared_ptr可以解决这个问题

        shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源所有智能指针都指向同一个内存和引用计数。

        当有智能指针指向内存资源时,同时让共享的引用计数++,当智能指针析构时,只是让引用计数--,只有当引用计数为0时再调用指向资源的析构函数。此外为了多线程访问,对计数需要加锁保护。

        具体看代码:

namespace bit
{template<class T>class shared_ptr{public:shared_ptr(T* ptr = nullptr):_ptr(ptr), _pRefCount(new int(1)), _pmtx(new mutex){}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _pRefCount(sp._pRefCount), _pmtx(sp._pmtx){AddRef();}void Release(){_pmtx->lock();bool flag = false;if (--(*_pRefCount) == 0 && _ptr){cout << "delete:" << _ptr << endl;delete _ptr;delete _pRefCount;flag = true;}_pmtx->unlock();if (flag == true){delete _pmtx;}}void AddRef(){_pmtx->lock();++(*_pRefCount);_pmtx->unlock();}shared_ptr<T>& operator=(const shared_ptr<T>& sp){//if (this != &sp)if (_ptr != sp._ptr){Release();_ptr = sp._ptr;_pRefCount = sp._pRefCount;_pmtx = sp._pmtx;AddRef();}return *this;}int use_count(){return *_pRefCount;}~shared_ptr(){Release();}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get() const{return _ptr;}private:T* _ptr;int* _pRefCount;mutex* _pmtx;};

         必须注意的是多线程的话,因为所有指针共享一个引用计数,对引用计数必须加锁访问,这里我们只做简单模拟。

七.weak_ptr的模拟实现

        weak_ptr有2个作用:

  • 打破循环引用:通过将类成员声明为 weak_ptr,避免 shared_ptr 的循环引用导致内存泄漏
  • 安全访问资源:通过 lock() 方法原子性地获取 shared_ptr,若对象已释放则返回空指针,避免悬垂指针

        下面我们引入第一个作用: 打破循环引用

        在shard_ptr中看似很安全,但是可能会出现循环引用的问题,下面让我们看看

class B;
class A {
public:std::shared_ptr<B> b_ptr;  // 强引用int a;~A() { std::cout << "A destroyed\n"; }
};
class B {
public:std::shared_ptr<A> a_ptr;  // 强引用int b;~B() { std::cout << "B destroyed\n"; }
};int main(){auto ptr_A = std::make_shared<A>();//c++新版本创建智能指针的新方法auto ptr_B = std::make_shared<B>();ptr_A->b_ptr = ptr_B;   //ptr_A的引用计数++;ptr_B->a_ptr = ptr_A;  //ptr_B的引用计数++  循环引用,引用计数均为2Bstd::cout << "运行完毕" << std::endl;
}  // mian结束,智能指针ptr_A,ptr_B引用计数只能减为1,对象未销毁

运行结果:

         可以看到我们并未成功调用对象A和B的析构函数,造成了内存泄漏。我们来分析一下原因。

        首先ptr_A指向A对象(假设为a),ptr_A的计数为1,ptr_B指向B对象(假设为b),ptr_B的计数也为1,然后 ptr_A->b_ptr = ptr_B; ptr_B->a_ptr = ptr_A; 此时ptr_A和ptr_B的计数增加为2.

        我们来画图理解。

        这里我们用控制块A和控制B代表指向A 和B的计数 ,当程序运行结束ptr_A,ptr_B调用析构时,计数都减少1,如下:

         此时指向A和B对象的计数都为1,无法自动调用析构,造成内存泄漏(new 出来的对象也是不会自动调用析构的)。

        我们要知道shared_ptr智能指针计数为0时才能调用指向对象的析构函数。

        为了解决上面的问题,我们创键了weak_ptr.

        weak_ptr一般和shard_ptr搭配使用,weak_ptr可以接受shard_prt类型的指针,但是不会影响计数。也就是说weak_ptr的构造和析构都不会增加和减少计数,同时weak_ptr也不会计数为0也不会调用指向对象的析构函数,只是充当指向作用

        我们使用weak_ptr对上面的代码进行修改。

class B;
class A {
public:std::weak_ptr<B> b_ptr;  // 强引用int a;~A() { std::cout << "A destroyed\n"; }
};
class B {
public:std::shared_ptr<A> a_ptr;  // 强引用int b;~B() { std::cout << "B destroyed\n"; }
};int main()
{auto ptr_A = std::make_shared<A>();auto ptr_B = std::make_shared<B>();ptr_A->b_ptr = ptr_B;ptr_B->a_ptr = ptr_A;std::cout << "运行完毕";
}

运行结果如下:

         我们将对象A的智能指针替换为weak_ptr,其他不变。再来分析析构过程

         首先ptr_A指向A对象,ptr_B指向B对象,计数都为1。ptr_A->b_ptr = ptr_B,此时A中的是weak_ptr<B>指针,不会增加ptr_B的计数,而 ptr_B->a_ptr = ptr_A,会增加ptr_A的计数(对哪个指针进行拷贝就是增加哪个指针的计数)。ptr_A计数为2,ptr_B计数为1。此时情况如图:

        当main结束时ptr_A调用自己的析构函数计数减少为1, 同时ptr_B自动调用自己的析构函数,计数减少为0。

        由于ptr_B计数为0需要调用b的析构函数调用b的析构函数释放资源后要调用成员变量a_ptr的析构函数(析构函数的顺序是先调用自己的,再调用成员变量的),此时控制块A计数为1(a_ptr是拷贝ptr_A的,俩者计数相同),减一后为0,计数为零需要调用a的析构函数,a的成员变量b_ptr计数为0,A可以直接析构,到此对象全部成功析构。图示如下:

        

        因此析构函数的调用顺序是先B后A,但是B对象是后于A对象被销毁的。 

        上面这么多就是为了论证weak_ptr的一个作用:​打破循环引用,避免内存泄漏。

        对于weak_ptr的第二个作用就好理解多了。 

  • 安全访问资源:通过 lock() 方法原子性地获取 shared_ptr,若对象已释放则返回空指针,避免悬垂指针。
std::weak_ptr<int> wp;
if (auto sp = wp.lock()) {  // 检查对象是否存在// 安全使用 sp
}

        wp.lock()是看计数是否为0,为0返回空,不为零返回一个shared_ptr(计数也会++)。

        以上这些讲讲的都是weak_ptr的作用和原理,下面我们给出weak_ptr的模拟实现。

template <typename T>
class WeakPtr {
public:// 默认构造函数(空指针)WeakPtr() : ptr_(nullptr), ref_count_(nullptr) {}// 从 SharedPtr 构造template <typename U>WeakPtr(const SharedPtr<U>& shared) : ptr_(shared.ptr), ref_count_(shared.ref_count) {}// 拷贝构造函数WeakPtr(const WeakPtr& other) : ptr_(other.ptr_), ref_count_(other.ref_count) {}shared_ptr<T> lock() const {if (*ptr<=0) return shared_ptr<T>();return shared_ptr<T>(ptr, ref_count_);}// 析构函数~WeakPtr() {}T& operator*(){return *ptr;}T* operator->(){return ptr;}T* get() const{return ptr;}private:T* ptr;                        // 指向对象的指针int* ref_count_;           // 指向控制块的指针// 允许 SharedPtr 访问私有成员template <typename U>friend class SharedPtr;
};

            要注意的这里只是简单实现,并不详细。

八.面试题拓展

        上面的讲解基本就能解决绝大多数的面试题了,但是面试的知识点也越来越细了,因此我们再根据常见的面试题进行拓展。

weak_ptr真的不计数?是否有计数方式?在哪分配的空间?

        

对于1,2小问,这里我们需要介绍一下share_ptr和weak_ptr中控制块的概念。

        控制块是智能指针实现引用计数机制的核心数据结构,包含以下信息

  1. 强引用计数(use_count)​:记录当前有多少个shared_ptr持有对象。
  2. 弱引用计数(weak_count)​:记录当前有多少个weak_ptr观察对象。
  3. 对象指针:指向实际管理的对象(可能为空,若对象已被销毁)。
  4. 自定义删除器(可选)。

     也就是说shared_ptr和weak_ptr中有2个强弱俩个计数,其中强引用计数作用就是当计数为0时调用指向对象的析构函数,但控制块仍存在,弱引用作用是当​弱引用归零时控制块本身被释放。

那么控制块的作用是什么:

1. 支持weak_ptr的安全操作

  • 感知对象状态:即使对象已被销毁(强引用归零),weak_ptr仍需通过控制块判断对象是否有效(如lock()要通过控制块判断);
  • 避免悬空控制块:若控制块随对象一起释放,weak_ptr将无法判断对象是否存在,导致未定义行为

2. 避免控制块内存泄漏

  • 生命周期分离:控制块的存活由弱引用计数决定。即使对象已销毁,只要存在weak_ptr观察,控制块就必须保留以记录弱引用信息
  • 最终释放机制:当所有weak_ptr销毁(弱引用归零),控制块才会被释放,避免内存残留

这里我们在总结一下智能指针的释放流程。

释放流程

  1. 对象销毁:当最后一个shared_ptr析构时,强引用计数归零,对象被释放。
  2. 控制块保留:若仍有weak_ptr观察(弱引用计数>0),控制块继续存在。
  3. 控制块释放:当所有weak_ptr析构(弱引用归零),控制块被销毁

 对于最后1个小问,我们还需要了解控制块的分配方式

  • new分配:直接使用new时,对象和控制块分两次分配,控制块独立存在
  • make_shared优化:通过make_shared创建shared_ptr时,对象和控制块分配在同一块连续内存中,减少内存碎片和分配次数。因此第二种方法更好一点。

因此上面面试题的答案是:

weak_ptr真的不计数?是否有计数方式,在哪分配的空间。

计数,控制块中有强弱引用计数,如果是使用make_shared初始化的函数则它所在的控制块空间是在所引用的shared_ptr中同一块的空间,若是new则控制器所分配的内存与shared_ptr本身所在的空间不在同一块内存。

  好了,智能指针就讲解到这了,感觉有帮助的话,请点点赞吧,这真的很重要。

                                

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

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

相关文章

【Go】Go语言结构体笔记

整体介绍 虽然 Go 语言不是传统意义上的面向对象语言&#xff0c;但它提供了结构体&#xff08;struct&#xff09;来组织数据&#xff0c;并且可以为结构体绑定方法&#xff0c;从而达到面向对象的部分效果。 关键知识点包括&#xff1a; 结构体定义与实例化 定义结构体时使用…

Three.js 快速入门教程【十八】射线拾取模型——鼠标点击屏幕选中模型或物体

系列文章目录 Three.js 快速入门教程【一】开启你的 3D Web 开发之旅 Three.js 快速入门教程【二】透视投影相机 Three.js 快速入门教程【三】渲染器 Three.js 快速入门教程【四】三维坐标系 Three.js 快速入门教程【五】动画渲染循环 Three.js 快速入门教程【六】相机控件 Or…

Object.defineProperty()Proxy详解(Vue23数据劫持实现)

底层原理&#x1f447;&#x1f3ff; 总结一下&#xff0c;结构应该包括&#xff1a; 1. 方法的基本作用和参数。 2. 数据描述符和存取描述符的区别。 3. 属性定义的内部处理流程。 4. 在Vue中的应用实例。 5. 常见错误和正确实践。 每个部分都要结合搜索结果的信息&…

MySQL 进阶语法:函数、约束、多表查询、事务

目录 一、MySQL 常用函数 1. 字符串函数 1.1 基本字符串操作 1.2 字符串截取与处理 1.3 字符串搜索与替换 2. 数值函数 2.1 基本数学运算 2.2 数学计算 2.3 随机数与符号 3. 日期时间函数 3.1 获取当前时间 3.2 日期时间计算 3.3 日期时间提取 3.4 日期时间格式化…

第 12 章(番外)| Solidity 安全前沿趋势 × 审计生态 × 职业路径规划

&#x1f310; 第 12 章&#xff08;番外&#xff09;| Solidity 安全前沿趋势 审计生态 职业路径规划 ——做得了审计&#xff0c;也接得了项目&#xff0c;走进 Web3 安全工程师的职业实战地图 ✅ 本章导读 Solidity 安全&#xff0c;不只是代码安全、业务安全、审计安全…

1、pytest基本用法

目录 先给大家分享下学习资源 1. 安装pytest 2. 编写用例规则 3. 执行用例 最近在学习pytest的用法 并且用这套框架替换了原来的unittest&#xff0c; 同是测试框架 确实感觉到pytest更加便捷 这边分享给大家我得学习心得 先给大家分享下学习资源 1 官方文档 pytest 官方…

【sylar-webserver】5 协程调度模块

文章目录 设计思路三种协程的切换 协程调度模块&#xff0c;需要把前面的线程模块和协程模块结合使用 ~ 设计思路 构造函数定义 线程池 基本信息。start()&#xff0c;创建线程池&#xff0c;每个线程创建都执行 run()。每个线程在 run() 里&#xff0c;查找任务队列 m_tasks…

Go 语言规范学习(1)

文章目录 IntroductionNotation示例&#xff08;Go 语言的 if 语句&#xff09;&#xff1a; Source code representationCharacters例子&#xff1a;变量名可以是中文 Letters and digits Lexical elementsCommentsTokensSemicolons例子&#xff1a;查看程序所有的token Ident…

探索抓包利器ProxyPin,实现手机APP请求抓包,支持https请求

以下是ProxyPin的简单介绍&#xff1a; - ProxyPin是一个开源免费HTTP(S)流量捕获神器&#xff0c;支持 Windows、Mac、Android、IOS、Linux 全平台系统- 可以使用它来拦截、检查并重写HTTP(S)流量&#xff0c;支持捕获各种应用的网络请求。ProxyPin基于Flutter开发&#xff0…

深度学习3-pytorch学习

深度学习3-pytorch学习 Tensor 定义与 PyTorch 操作 1. Tensor 定义&#xff1a; Tensor 是 PyTorch 中的数据结构&#xff0c;类似于 NumPy 数组。可以通过不同方式创建 tensor 对象&#xff1a; import torch# 定义一个 1D Tensor x1 torch.Tensor([3, 4])# 定义一个 Fl…

深入浅出Spring-Boot-3.x.pdf

通过网盘分享的文件&#xff1a;深入浅出Spring-Boot-3.x.pdf 链接: https://pan.baidu.com/s/10ZkhmeIXphEwND9Rv4EBlg?pwduatm 提取码: uatm

springboot启动事件CommandLineRunner使用

什么是CommandRunner CommandRunner是springboot启动完成时会调用的一个runner 启动参数会传递到这个runner 我们能用来做一些初始化工作和缓存预热等工作 ApplicationRunner VS CommandRunner? 这两个Runner作用一样 只是得到的启动参数格式不一样 前者是一个Argument对象…

数据可视化TensorboardX和tensorBoard安装及使用

tensorBoard 和TensorboardX 安装及使用指南 tensorBoard 和 TensorBoardX 是用于可视化机器学习实验和模型训练过程的工具。TensorBoard 是 TensorFlow 官方提供的可视化工具&#xff0c;而 TensorBoardX 是其社区驱动的替代品&#xff0c;支持 PyTorch 等其他框架。以下是它…

蓝桥杯C++基础算法-多重背包

这段代码实现了一个多重背包问题的动态规划解法。多重背包问题与完全背包问题类似&#xff0c;但每个物品有其数量限制。以下是代码的详细思路解析&#xff1a; 1. 问题背景 给定 n 个物品&#xff0c;每个物品有其体积 v[i]、价值 w[i] 和数量 s[i]&#xff0c;以及一个容量为…

【SUNO】【AI作词】【提示词】

仿写歌词提示词模板&#xff08;升级版&#xff09; 一、仿写目标 风格定位 音乐风格&#xff1a; [填写目标风格&#xff0c;如&#xff1a;民谣/流行/古风/电子/爵士等]参考案例&#xff1a;如《成都》的叙事民谣&#xff0c;《孤勇者》的励志流行。 情感基调&#xff1a; […

26考研——树与二叉树_树与二叉树的应用(5)

408答疑 文章目录 三、树与二叉树的应用哈夫曼树和哈夫曼编码哈夫曼树的定义概念带权路径长度&#xff08;WPL&#xff09;计算示例分析 哈夫曼树的构造算法描述哈夫曼树的性质示例 哈夫曼编码Huffman树的编码规则Huffman树的构建过程前缀编码前缀编码的分析及应用 Huffman树的…

【VUE】day06 动态组件 插槽 自定义指令 ESlint

【VUE】day06 动态组件 & 插槽 & 自定义指令 1. 动态组件1.1 通过不同的按钮展示不同的组件1.1.1回顾click 1.2 keep-alive的使用1.3 keep-alive对应的生命周期函数1.3.1 keep-alive的include属性1.3.2 exclude 1.4 组件注册名称和组件声明时name的区别1.4.1 组件声明时…

nodejs-原型污染链

还是老规矩&#xff0c;边写边学&#xff0c;先分享两篇文章 深入理解 JavaScript Prototype 污染攻击 | 离别歌 《JavaScript百炼成仙》 全书知识点整理-CSDN博客 Ctfshow web入门 nodejs篇 web334-web344_web334 ctfshow-CSDN博客 334-js审计 var express require(expr…

Oracle 数据库通过exp/imp工具迁移指定数据表

项目需求&#xff1a;从prod数据库迁移和复制2个表(BANK_STATE&#xff0c;HBS)的数据到uat数据库环境。 数据库版本&#xff1a;Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 迁移工具&#xff1a;客户端exp/imp工具 -- 执行命令 从Prod数据库导出数据exp us…

企业级基于SpringBoot的MQTT的构建和使用

基于SpringBoot的MQTT配置及使用 首先要使用EMQX搭建一个MQTT服务器&#xff0c;参考文档&#xff1a;EMQX快速开始 本着开源分享的观点&#xff0c;闲话不多说&#xff0c;直接上代码 导入Maven <dependency><groupId>org.springframework.integration</gro…