【c/c++】深入探秘:C++内存管理的机制

Alt

🔥个人主页Quitecoder

🔥专栏c++笔记仓

Alt

朋友们大家好,本篇文章我们详细讲解c++中的动态内存管理

目录

  • 1.C/C++内存分布
  • 2.C语言中动态内存管理方式:malloc/calloc/realloc/free
  • 3.c++内存管理方式
    • 3.1new/delete对内置类型的操作
      • 3.1.1抛异常
    • 3.2new/delete对自定义类型的操作
  • 4.operator new与operator delete函数
  • 5.new和delete的实现原理
  • 6.简单了解定位new表达式(placement-new)
  • 7.概念辨析
    • 7.1 malloc/free和new/delete的区别
    • 7.2 内存泄漏

1.C/C++内存分布

我们来看内存区域划分

在这里插入图片描述

数据段就是我们所说的全局变量,代码段是我们所说的常量区,我们需要重点关注的是堆区,这部分是由我们自己控制的

int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
  static int staticVar = 1;
  int localVar = 1;
  int num1[10] = { 1, 2, 3, 4 };
  char char2[] = "abcd";
  const char* pChar3 = "abcd";
  int* ptr1 = (int*)malloc(sizeof(int) * 4);
  int* ptr2 = (int*)calloc(4, sizeof(int));
  int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
  free(ptr1);
  free(ptr3);
}
 选择题:
   选项: A.栈  B.堆  C.数据段(静态区)  D.代码段(常量区)
   globalVar在哪里?__1__   staticGlobalVar在哪里?__2__
   staticVar在哪里?__3__   localVar在哪里?__4__
   num1 在哪里?__5__
   
   char2在哪里?__6__   *char2在哪里?_7__
   pChar3在哪里?__8__      *pChar3在哪里?__9__
   ptr1在哪里?_10___        *ptr1在哪里?__11__

我们来依次讨论:

  1. globalVar 是全局变量,不是静态的,所以它存储在数据段(静态区)
  2. staticGlobalVar 也是全局变量,但它是静态的,因此它同样存储在数据段(静态区)
  3. staticVar 是函数内的静态变量,所以它存储在数据段(静态区),因为它的生命周期贯穿程序的整个执行期
  4. localVar 是局部变量,存储在栈上
  5. num1 是局部变量,它是数组,存储在栈上
  6. char2 是局部变量,它是数组首元素的地址,存储在栈上
  7. *char2(即char2数组的内容)存储在栈上,因为char2本身就在栈上
  8. pChar3 是局部指针变量,存储在
  9. *pChar3 指向的内容(即字符串"abcd")存储在代码段(常量区)
  10. ptr1 是局部指针变量,存储在
  11. *ptr1 指向的内容(即通过malloc分配的内存)存储在
  • *char2(局部字符数组)
    当你声明一个局部字符数组并用一个字符串字面量初始化它,如char char2[] = "abcd";时,编译器在栈上为数组分配内存,然后将字符串字面量的内容(包括结尾的\0字符)复制到这块内存中。因此,char2和它的内容(*char2指向的内容)都存储在栈上

  • *pChar3(字符串字面量指针)
    另一方面,当你使用指针指向一个字符串字面量,如const char* pChar3 = "abcd";时,这个字符串字面量存储在程序的只读数据段(或称为代码段、常量区)中。pChar3本身作为一个局部指针变量存储在栈上,但它指向的字符串(“abcd”)实际上存储在常量区。这是因为字符串字面量被视为常量数据,编译器会将它们放在程序的常量区域内,这个区域通常是只读的,以防止程序意外修改它的内容。因此,尽管pChar3是一个指针,存储在栈上,但它指向的字符串内容存储在常量区

总结

  • *char2不在常量区,因为char2是局部字符数组,其内容直接存储在栈上。
  • *pChar3在常量区,因为它指向的是一个字符串字面量,字符串字面量被存储在程序的常量区域,这部分内存是只读的。

当我们讨论变量存储在哪里时,通常涉及到几个关键区域:栈(Stack)、堆(Heap)、数据段(Data Segment,又称静态区)、和代码段(Code Segment,又称常量区)。每种类型的变量根据其特性和声明周期被存储在这些区域中的相应位置

  • 是用于存储局部变量、函数参数等的内存区域。当一个函数被调用时,其局部变量和一些书keeping信息被推入栈中;当函数执行完成,这些信息被从栈上弹出。栈是自动管理的,开发者无需手动分配或释放内存。

  • 是用于动态内存分配的内存区域。不同于栈,开发者需要显式地从堆上分配内存(如使用mallocnew),并在不再需要时释放这些内存(如使用freedelete)。

  • 数据段,又称为静态区,用于存储全局变量、静态变量等。这些变量的生命周期贯穿整个程序执行期,因此它们被存储在一个特定的、持久的内存区域中。

  • 代码段,又称为常量区,用于存储程序的执行代码和常量数据,如字符串字面量。这部分内存是只读的,用来保证程序代码的安全性

2.C语言中动态内存管理方式:malloc/calloc/realloc/free

在C语言中,动态内存管理是通过一组标准库函数完成的,包括malloc, calloc, realloc, 和 free。这些函数允许程序在运行时动态地分配、调整和释放堆内存,这是对于管理变化的数据量和大小特别有用的能力。下面是这些函数的基本用法和它们之间的区别:

malloc

  • 用法void* malloc(size_t size);
  • 功能:分配指定字节数的未初始化内存。它返回一个指向分配的内存的指针。如果分配失败,返回NULL
  • 示例int* ptr = (int*)malloc(sizeof(int) * 4); 这行代码为4个整数分配了内存

calloc

  • 用法void* calloc(size_t num, size_t size);
  • 功能:为指定数量的元素分配内存,每个元素的大小也在参数中指定,并自动初始化所有位为0。如果分配失败,返回NULL
  • 示例int* ptr = (int*)calloc(4, sizeof(int)); 这行代码为4个整数分配了内存,并将它们初始化为0。

realloc

  • 用法void* realloc(void* ptr, size_t size);
  • 功能调整之前调用malloccalloc分配的内存块的大小。如果新的大小大于原始大小,可能会移动内存块到新的位置以提供足够的连续空间。如果realloc的第一个参数是NULL,它的行为就像malloc
  • 示例ptr = (int*)realloc(ptr, sizeof(int) * 8); 这行代码将之前分配的内存大小调整为8个整数的大小。

free

  • 用法void free(void* ptr);
  • 功能:释放之前通过malloc, calloc, 或 realloc分配的内存。一旦内存被释放,那块内存就不能再被访问了。
  • 注意:尝试释放未经分配的内存块或多次释放同一个内存块是不安全的,可能导致未定义行为

注意

  • 在使用这些函数时,确保正确处理内存分配失败的情况,并在内存不再需要时使用free来避免内存泄露。
  • 当使用realloc时,如果分配失败,原始内存不会被释放。因此,建议先将realloc的返回值赋给一个临时指针,以检查是否分配成功,再重新赋值给原始指针,以避免内存泄漏。
  • 始终确保只对通过malloc, calloc, 或 realloc分配的指针使用free,并且每个分配的内存块只被free一次

3.c++内存管理方式

C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过newdelete操作符进行动态内存管理

3.1new/delete对内置类型的操作

new的基本用法

Type* variable = new Type(arguments);
  • Type:要分配的对象类型
  • variable:指向分配的内存的指针
  • arguments:传递给构造函数的参数(如果需要的话)

示例

int* ptr1 = new int; 

在堆上分配了一个int大小的内存

int* ptr2 = new int[10]; 

加上方括号[ ]表示分配了十个int大小的内存

释放:

对于ptr,我们直接delete

delete ptr1;

释放数组对象的内存ptr2,我们需要加上方括号:

delete [] ptr2;

我们也可以分配内存的同时直接初始化:

int* ptr5 = new int(5);

动态申请一个int类型的空间并初始化为5

在这里插入图片描述
我们也可以同时开辟多个空间完成初始化:

int* ptr6 = new int[10] {1,2,3,4,5};

在这里插入图片描述
后面的空间默认初始化为零

  • 尽管newdelete提供了对象构造和析构的自动管理,但程序员仍然需要负责确保每个用new分配的内存都被对应的delete释放,以避免内存泄露
  • mallocfree一样,试图delete一个未经new分配的指针,或者对同一个指针执行多次delete,都是未定义行为,并且可能导致程序崩溃
  • 当使用new[]分配数组时,必须使用对应的delete[]来释放内存。使用错误的delete形式也是未定义行为

来看下面的代码:

struct ListNode
{
	ListNode* _next;
	int _val;

	ListNode(int val)
		:_next(nullptr)
		,_val(val)
	{}
};

struct ListNode* CreateListNode(int val)
{
	struct ListNode* newnode = (struct ListNode*)malloc(sizeof(struct ListNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}

	newnode->_next = NULL;
	newnode->_val = val;
	return newnode;
}

这是c语言构造一个节点并完成初始化的过程,我们来看c++的实现:

int main()
{
	ListNode* node1 = new ListNode(1);
	return 0;
}

这行代码自动为ListNode对象分配了内存,并调用了其构造函数进行初始化。这种方式更简洁,也更安全,因为它保证了对象在使用前被正确初始化,注意这里ListNode是自定义类型,除了开空间还会调用构造函数

在这里插入图片描述
在这里插入图片描述

只要我们写好构造函数,我们发现new的使用是十分方便的

我们来构建一个链表:

ListNode* CreateList(int n)
{
	ListNode head(-1);  // 哨兵位
	ListNode* tail = &head;
	int val;
	printf("请依次输入%d个节点的值:>", n);
	for (size_t i = 0; i < n; i++)
	{
		cin >> val;
		tail->_next = new ListNode(val);
		tail = tail->_next;
	}
	return head._next;
}

我们输入五个值,1 2 3 4 5
在这里插入图片描述

哨兵节点:ListNode head(-1);这行代码创建了一个局部的哨兵节点,它的值被设为-1(这个值通常是任意的,因为哨兵节点本身不存储任何有意义的数据)。哨兵节点的主要目的是简化在链表头部的插入和删除操作,因为你总是有一个非空的节点作为链表的起始点,从而避免了处理空链表的特殊情况

最后,函数通过return head._next;返回新构建链表的头节点。由于head是一个哨兵节点,它的_next成员实际上指向链表的第一个真实节点(如果有的话)或者是nullptr(如果n为0或用户没有输入任何有效数据)

3.1.1抛异常

我们不用手动检查new是否开辟成功new失败了会抛出异常

void func()
{
	int n = 1;
	while (1)
	{
		int* p = new int[1024 * 1024*100];
		cout <<n<<"->"<< p << endl;
		++n;
	}
}

我们一次申请400M的空间大小
在这里插入图片描述
再看c语言版本

void func()
{
	int n = 1;
	while (1)
	{
		//int* p = new int[1024 * 1024 * 100];
		int* p = (int*)malloc(1024 * 1024 * 400);
		cout << n << "->" << p << endl;
		++n;
	}
}

在这里插入图片描述
开辟失败,程序无限循环并返回空

c++中的抛异常:

try
	{
		func();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}

这段代码是C++中的一个示例,展示了如何使用try-catch语句来处理异常。这里的重点是捕获并处理func()函数中可能抛出的异常。如果func()函数执行中出现了问题,它将抛出一个异常,这个异常会被catch块捕获。捕获到的异常类型为const std::exception&,这是C++标准异常类型的一个基类。在catch块中,通过e.what()调用来获取并打印出异常的具体信息

  • try块:在try块中的代码执行时,如果发生了异常(即代码抛出了异常),那么try块中的剩余代码将不会继续执行,而是跳转到相应的catch块中处理异常

  • catch块:此代码段用于捕获类型为const std::exception&的异常。这意味着它能够捕获任何是std::exception实例或其派生类的异常。通过常量引用捕获异常是一种最佳实践,因为这样可以避免异常对象的切片问题,并且可以最小化性能开销

  • const exception& e:这里声明了一个名为e的引用,它引用了被捕获的异常。const限定符表明在catch块中,e是不会被修改的

  • e.what()std::exception及其派生类有一个名为what()的成员函数,它返回一个描述异常的空终止字符序列(C风格字符串)。cout << e.what() << endl;语句将这个消息打印到标准输出中

后续我们还会遇到这个函数,再详细讲解

来看抛异常的结果:
在这里插入图片描述

3.2new/delete对自定义类型的操作

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
int main()
{
	A* p1 = new A(1);
	delete p1;
	return 0;
}

new/deletemalloc/free最大区别是 new/delete对于【自定义类型】除了开空间,还会调用构造函数和析构函数

	A* p1 = new A(1);
00007FF798AA260B  mov         ecx,4  
00007FF798AA2610  call        operator new (07FF798AA104Bh)  
00007FF798AA2615  mov         qword ptr [rbp+108h],rax  
00007FF798AA261C  cmp         qword ptr [rbp+108h],0  
00007FF798AA2624  je          main+50h (07FF798AA2640h)  
00007FF798AA2626  mov         edx,1  
00007FF798AA262B  mov         rcx,qword ptr [rbp+108h]  
00007FF798AA2632  call        A::A (07FF798AA1343h)  
00007FF798AA2637  mov         qword ptr [rbp+138h],rax  
00007FF798AA263E  jmp         main+5Bh (07FF798AA264Bh)  
00007FF798AA2640  mov         qword ptr [rbp+138h],0  
00007FF798AA264B  mov         rax,qword ptr [rbp+138h]  
00007FF798AA2652  mov         qword ptr [rbp+0E8h],rax  
00007FF798AA2659  mov         rax,qword ptr [rbp+0E8h]  
00007FF798AA2660  mov         qword ptr [p1],rax

在这里插入图片描述
new过程跳转到构造函数
在这里插入图片描述
delete调用析构函数

打印结果如下:

A():000001DB79796B50
~A():000001DB79796B50

我们发现,汇编代码中有这一步:

00007FF798AA2610  call        operator new (07FF798AA104Bh)

operator new,接下来我们来讲解这一部分

4.operator new与operator delete函数

new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator
delete是系统提供的全局函数new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间

void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
	void* p;
	while ((p = malloc(size)) == 0)
		if (_callnewh(size) == 0)
		{
			// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
			static const std::bad_alloc nomem;
			_RAISE(nomem);
		}
	return (p);
}

operator new该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。

 static const std::bad_alloc nomem;

申请失败则会抛异常

void operator delete(void* pUserData)
{
	_CrtMemBlockHeader* pHead;
	RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
	if (pUserData == NULL)
		return;
	_mlock(_HEAP_LOCK);  /* block other threads */
	__TRY
		        /* get a pointer to memory block header */
		pHead = pHdr(pUserData);
	         /* verify block type */
	_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
	_free_dbg(pUserData, pHead->nBlockUse);
	__FINALLY
		_munlock(_HEAP_LOCK);  /* release other threads */
	__END_TRY_FINALLY
		return;
}
_free_dbg(pUserData, pHead->nBlockUse);

operator delete: 该函数最终是通过free来释放空间的

通过上述两个全局函数的实现知道,operator new 实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的

我们只需要简单了解一下,并不需要深入理解

5.new和delete的实现原理

如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL

自定义类型:

  • new的原理
    1. 调用operator new函数申请空间
    2. 在申请的空间上执行构造函数,完成对象的构造
class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
int main()
{
	A* p1 = new A(1);
	delete p1;
	return 0;
}

在这里插入图片描述

  • delete的原理
    1. 在空间上执行析构函数,完成对象中资源的清理工作
    2. 调用operator delete函数释放对象的空间
class Stack
{
public:
	Stack()
	{
		_a = (int*)malloc(sizeof(int) * 4);
		_top = 0;
		_capacity = 4;
	}

	~Stack()
	{
		free(_a);
		_top = _capacity = 0;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};
int main()
{
	Stack* pst = new Stack;
	delete pst;
	return 0;
}

这里进行了双层嵌套:
在这里插入图片描述
我们就很清楚的能看到,现需要调用析构函数再进行释放

  • new T[N]的原理

    1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请
    2. 在申请的空间上执行N次构造函数
  • delete[]的原理

    1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
    2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间
class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
int main()
{
	A* p1 = new A;
	A* p2 = new A[10];
	delete p1;
	delete[]p2;
	return 0;
}

在这里插入图片描述
在这段代码中,p2 是指向由 new A[10] 分配的对象数组的指针。虽然你可能会认为 p2 只需要分配足够存储 10 个 A 类型对象的空间,即 10 * sizeof(A)实际上编译器通常会分配额外的空间来存储有关数组本身的信息,比如数组的大小。这是因为在执行 delete[] p2; 时,系统需要知道要调用多少次析构函数
在这里插入图片描述

让我们具体看一下为什么会这样:

  1. 对象数组的内存分配:当你创建一个对象数组时,例如 new A[10],C++ 需要知道在稍后释放数组时应该调用多少次析构函数。为此,它可能在分配给数组的内存块中存储一些额外的元数据,通常是数组的长度

  2. 析构函数调用:在使用 delete[] p2; 释放内存时,这个额外存储的信息就被用来确保为数组中的每个元素正确调用析构函数

  3. 内存布局:因此,分配给 p2 的内存实际上包含了更多比简单的 10 * sizeof(A) 字节。首先是数组长度的元数据(大小取决于系统和编译器),紧接着是 10 个 A 类型对象的存储空间

  4. 字节大小:如果 sizeof(A) 是 4(假设 int 类型是 4 字节,并且没有类对齐导致的额外空间),那么仅对象部分就占用了 40 字节。加上存储数组大小的额外空间,总大小就会超过 40 字节

在这里插入图片描述

我们再来看内置类型:

int* p1=new int[10];
00007FF7F031206B  mov         ecx,28h  

刚好开辟了四十个字节的空间,因为它不需要调用析构函数

6.简单了解定位new表达式(placement-new)

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象

使用格式:
new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
int main()
{
	int* p1 = new int[10];
	return 0;
}
A* p1 = (A*)malloc(sizeof(A));

p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行

new(p1)A;

显示调用构造函数对一块已经有的空间的初始化

定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化

7.概念辨析

7.1 malloc/free和new/delete的区别

malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:

  1. malloc和free是函数,new和delete是操作符
  2. malloc申请的空间不会初始化,new可以初始化
  3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可
  4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
  5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
  6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理

7.2 内存泄漏

  • 什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费
  • 内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死

分类:

  • 堆内存泄漏(Heap leak):
    堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak

  • 系统资源泄漏
    指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定

本节内容到此结束!!求大家三连啊!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode-199. 二叉树的右视图【树 深度优先搜索 广度优先搜索 二叉树】

LeetCode-199. 二叉树的右视图【树 深度优先搜索 广度优先搜索 二叉树】 题目描述&#xff1a;解题思路一&#xff1a;广度优先搜索解题思路二&#xff1a;深度优先搜索解题思路三&#xff1a;0 题目描述&#xff1a; 给定一个二叉树的 根节点 root&#xff0c;想象自己站在它…

股权激励和期权激励对比辨析

文章目录 概念定义 收益方式 风险评估 应用和分析 股权激励和期权激励&#xff0c;两者的区别是什么&#xff0c;本文就来梳理对比一下。 概念定义 股权激励&#xff0c;是指上市公司以本公司股票为标的&#xff0c;对其董事、高级管理人员及其他员工进行的长期性激励。取得…

微服务(基础篇-008-es、kibana安装)

目录 05-初识ES-安装es_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1LQ4y127n4?p81&vd_source60a35a11f813c6dff0b76089e5e138cc 1.部署单点es 1.1.创建网络 1.2.加载镜像 1.3.运行 2.部署kibana 2.1.部署 2.2.DevTools 3.安装IK分词器 3.1.在线安装ik…

程序员们应注意的行业特有的法律问题

大家好&#xff0c;我是不会魔法的兔子&#xff0c;是一枚执业律师&#xff0c;持续分享技术类行业项目风险及预防的问题。 一直以来兔子都在以大家做项目时候会遇到的风险问题做分享&#xff0c;最近有个念头一直挥之不去&#xff0c;就是要不要给我们广大的程序员们也分享一…

一文彻底搞懂ZooKeeper选举机制

文章目录 1. ZooKeeper 集群2. ZooKeeper 启动3. ZooKeeper 选举机制4. Follower&#xff08;跟随者&#xff09;和Candidate&#xff08;候选者&#xff09;节点区别5. Leader节点挂掉期间写操作是否会丢失 1. ZooKeeper 集群 ZooKeeper 是一个分布式的开源协调服务&#xff…

Node.js------模块化

◆ 能够说出模块化的好处◆ 能够知道 CommonJS 规定了哪些内容◆ 能够说出 Node.js 中模块的三大分类各自是什么◆ 能够使用 npm 管理包◆ 能够了解什么是规范的包结构◆ 能够了解模块的加载机制 一.模块化的基本概念 1.模块化 模块化是指解决一个复杂问题时&#xff0c…

基于SpringBoot+Thymeleaf的学生会管理系统

在这里插入图片描述 在这里插入图片描述

MYSQL——索引概念索引结构

索引 索引是帮助数据库高效获取数据的排好序的数据结构。 有无索引时&#xff0c;查询的区别 主要区别在于查询速度和系统资源的消耗。 查询速度&#xff1a; 在没有索引的情况下&#xff0c;数据库需要对表中的所有记录进行扫描&#xff0c;以找到符合查询条件的记录&#…

现在优秀企业都用SaaS知识库工具,原因就在这里

在这个信息化、知识化时代&#xff0c;企业的竞争力往往取决于能否有效管理和利用内部的知识资源。而如何实现这一任务呢&#xff1f;答案就在SaaS知识库工具。现在&#xff0c;很多优秀的企业已经使用了SaaS知识库工具进行知识管理&#xff0c;那么&#xff0c;他们为什么要这…

【洛谷 P8695】[蓝桥杯 2019 国 AC] 轨道炮 题解(映射+模拟+暴力枚举+桶排序)

[蓝桥杯 2019 国 AC] 轨道炮 题目描述 小明在玩一款战争游戏。地图上一共有 N N N 个敌方单位&#xff0c;可以看作 2D 平面上的点。其中第 i i i 个单位在 0 0 0 时刻的位置是 ( X i , Y i ) (X_i, Y_i) (Xi​,Yi​)&#xff0c;方向是 D i D_i Di​ (上下左右之一, 用…

kubadm部署kubernetes

什么是kubernetes Kubernetes是一款应用于集群的&#xff0c;容器自动部署、扩展和管理的开源平台&#xff0c;提供了一种以容器为中心的基础架构。利用kubernetes&#xff0c;你可以快速高效地响应客户如下请求&#xff1a; 应用程序的动态、精准部署应用程序的动态扩展无缝推…

【机器学习】K-近邻算法(KNN)介绍、应用及文本分类实现

一、引言 1.1 K-近邻算法&#xff08;KNN&#xff09;的基本概念 K-近邻算法&#xff08;K-Nearest Neighbors&#xff0c;简称KNN&#xff09;是一种基于实例的学习算法&#xff0c;它利用训练数据集中与待分类样本最相似的K个样本的类别来判断待分类样本所属的类别。KNN算法…

2024福建三支一扶报名流程,超全超详细!

2024年福建三支一扶报名已经开始&#xff0c;请注意时间&#xff01; ⚠2024年福建省省级“三支一扶”计划招募岗位1070个 报名时间&#xff1a;4月1日8:00至4月17日17:00 审查考核&#xff1a;4月18日至5月10日 确定派遣人员&#xff1a;5月11日至5月31日 报名入口&#xff1…

数据质量决定大模型能力,景联文科技提供高质量大模型数据

随着大模型的深入发展&#xff0c;各类资源要素的配置状态已悄然变化。其中&#xff0c;数据的价值已被提升到一个新高度。 大模型往往拥有庞大的参数和复杂的网络结构&#xff0c;需要大量的数据来学习和优化。数据的质量和数量直接决定了模型的训练效果。若数据不足或质量不佳…

【JavaScript 漫游】【051】Set 和 Map 数据结构

文章简介 本篇文章为【JavaScript 漫游】专栏的第 051 篇文章&#xff0c;记录了 ES6 规范新增的 Set 和 Map 数据结构的相关知识点。 SetWeakSetMapWeakMap Set 基本用法 类似于数组&#xff0c;但是成员的值都是唯一的&#xff0c;没有重复的值。 Set 本身是一个构造函…

IT外包行业未来发展趋势

随着企业对高可用性系统和分布式系统需求的增加&#xff0c;IT人才外包行业迎来了前所未有的发展机遇。未来几年&#xff0c; IT外包行业将呈现出一系列发展趋势 首先&#xff0c;IT外包人才队伍将不断壮大。随着企业对人效的需求日益增长&#xff0c;以及为规避用工风险和降低…

StarRocks实战——携程火车票指标平台建设

目录 前言 一、早期OLAP架构与痛点 二、指标平台重构整体设计 2.1 指标查询过程 2.1.1 明细类子查询 2.1.2 汇总类子查询 2.1.3 “缓存” 2.2 数据同步 三、Starrocks使用经验分享 3.1 建表经验 3.2 数据查询 3.3 函数问题 四、查询性能大幅提升 五、 后续优化方…

LeetCode575——分糖果

题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 这道题比较简单&#xff0c;但我还是花费了将近四个小时的时间去解答&#xff0c;AC的那一刻&#xff0c;终于全身舒畅&#xff0c;这道题的思路就是先求出糖果的种数&#xff0c;然后我们从题中可以得出&#x…

PMP备考需要多长时间?

PMP备考需要多久&#xff1f;50天就能顺利学完 PMP考试备考时间需要看自己的工作安排了&#xff0c;学习周期要恰到好处&#xff0c;太长的话可能导致边学边忘&#xff0c;根本来不及总结冲刺&#xff1b;太短的话又会造成学习内容掌握不稳定&#xff0c;导致考试的时候发挥失…

JavaScript(一)基础

文章目录 一、JS介绍JavaScript是什么JavaScript书写位置JavaScript的注释输入输出语法字面量 二、变量变量是什么变量基本使用变量的本质变量命名规则与规范变量拓展-数组var与let的区别 三、常量四、数据类型数据类型检测数据类型数据类型转换隐式转换显式转换 简单运算符断点…