【C++笔记】STL详解:String类的实现

【C++笔记】STL详解:String类的实现

前言:

                在前面的学习中,我们已经初步掌握了string类接口函数的使用方法,本文将带领大家从零开始,逐步实现一个完整的string类。

        

一、string类总览

        

       温馨提示: 为了避免与标准库中的string产生命名冲突,我们使用mystd命名空间进行封装。

namespace mystd { class string { public: //迭代器 typedef char* iterator; typedef const char* const_iterator; //默认成员函数 string(); string(const char* str); //构造函数 string(const string& s); //拷贝构造函数 string& operator=(const string& s); //赋值运算符重载函数 ~string(); //析构函数 //迭代器相关函数 iterator begin(); iterator end(); const_iterator begin()const; const_iterator end()const; //容量和大小相关函数 size_t size(); size_t capacity(); void reserve(size_t n); void resize(size_t n, char ch = '\0'); bool empty()const; //修改字符串相关函数 void push_back(char ch); void append(const char* str); string& operator+=(char ch); string& operator+=(const char* str); string& insert(size_t pos, char ch); string& insert(size_t pos, const char* str); string& erase(size_t pos, size_t len); void clear(); void swap(string& s); const char* c_str()const; //访问字符串相关函数 char& operator[](size_t i); const char& operator[](size_t i)const; size_t find(char ch, size_t pos = 0)const; size_t find(const char* str, size_t pos = 0)const; size_t rfind(char ch, size_t pos = npos)const; size_t rfind(const char* str, size_t pos = 0)const; private: char* _str; //存储字符串 size_t _size; //记录字符串当前的有效长度 size_t _capacity; //记录字符串当前的容量 static const size_t npos; //静态成员变量(整型最大值) }; const size_t string::npos = -1; //关系运算符重载函数 bool operator<(const string& s1, const string& s2); bool operator<=(const string& s1, const string& s2); bool operator>(const string& s1, const string& s2); bool operator>=(const string& s1, const string& s2); bool operator==(const string& s1, const string& s2); bool operator!=(const string& s1, const string& s2); //<<和>>运算符重载函数 ostream& operator<<(ostream& out, const string& s); istream& operator>>(istream& in, string& s); }

        

成员变量分析:

        

① char * _str  :用来存储字符串。

        

② size_t size : 记录当前字符串的长度(不包含‘\0’)。

        

③ size_t _capacity:记录字符串当前的容量(不包含‘\0’)。

        
④ static const size_t npos:这是一个静态成员变量,其值为-1(即整型的最大值),通常用作函数的异常返回值。

        

迭代器分析:

       

①typedef char* iterator :通过重命名char* 指针作为迭代器

        
②typedef const char* const_iterator : 通过重命名 const char * 指针作为常量迭代器

        

二、默认成员函数

        

2.1 无参构造函数

        

 //1. string的无参构造 string() :_str(new char[1] {'\0'}) , _size(0) , _capacity(0) {}

        

代码分析:

        

① 默认string类预留一个 ‘\0’ 作为空字符的表示

        

例如: string s;    -> 调用string的无参构造,表示s为空字符

        

② 初始化 _size 为0

        

③ 初始化 _capacity 为0       

         

注:空字符的字符个数_size 和 空间 _capacity 均为0 , 即'\0'额外单独开空间。

        

2.2 带参构造函数

        

//2. string的带参构造,不推荐使用初始化列表进行构造 string(const char* str) { //计算字符串的字符个数 _size = strlen(str); //温馨提示:_capacity所开辟的空间也不包含\0 _capacity = _size; //单独为'\0'开空间,所以开辟空间_capacity+1 _str = new char[_capacity + 1]; //拷贝C类型字符 strcpy(_str, str); }

        

温馨提示:

        

①使用初始化列表进行构造时,初始化顺序是按照成员变量声明顺序执行的。

        

②为了复用strlen计算结果,不建议在此处使用初始化列表方式。

        

2.3 拷贝构造函数

        

在模拟实现拷贝构造函数前,我们应该首先了解深浅拷贝:

        

①浅拷贝:拷贝出来的目标对象的指针和源对象的指针指向的内存空间是同一块空间,其中一个对象的改动会对另一个对象造成影响。

        

②深拷贝:深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动不会对另外一个对象造成影响。

        

 分析:string类需要深拷贝的原因     

        

场景演示:用字符串str1拷贝str2 ,即string str2(str1)

       

①每个string 类对象需要独立的空间进行存储字符串,要求任何一个对象的改动不会对另外一个对象造成影响,因此str1拷贝str2需要进行深拷贝。

        

②深拷贝给每个对象独立分配资源,保证多个对象之间不会因共享资源而造成多次释放造成程序崩溃问题

        

代码一:传统写法

        

string(const string& s) : _size(s._size) , _capacity(s._size) // 容量按需分配,节省内存 { _str = new char[_capacity + 1]; strcpy(_str, s._str); }

        

例如:string str2(str1)

        

①首先为拷贝对象str2分配足够容纳源对象str1的内存空间。

        

②然后将源对象str1的字符串内容完整复制到str2中。

        

③最后将源对象str1的所有成员变量值赋给str2。

                

注意:由于str2的_str指针和str1的_str指针指向不同的内存空间,因此这是一个深拷贝操作,两个对象完全独立。

        

        

代码二:现代写法

        

//封装swap函数:用来交换成员变量 void swap(string& tmp) { //调用库函数中的swap进行交换两数 std::swap(_str, tmp._str); std::swap(_size, tmp._size); std::swap(_capacity, tmp._capacity); } // 现代写法 cpoy-and-swap 写法 string(const string& s) { string tmp(s._str); swap(tmp); }

        

代码解析:

        
场景演示:假设执行 string s2(s1);

   

① s1传引用传参给s,即s为s1的别名
    
② 调用构造函数,构造出一个C字符串为s._str的对象tmp
    
③ tmp由带参构造函数而来,字符串指向的地址和s不同,但字符串个数_size 和容量 _capacity 与s1完全相同。

   

④ 再将s2和tmp进行交换,此时s2的值就与s1的值相同,于此时同时tmp的值为原来的s2,函数结束时进行析构原来的s2指向的空间。

        

        

温馨提示:

        

如果待拷贝对象s2的成员变量: char* _str、size_t _size、size_t _capacity



A.没有在类内给缺省值(比如 = nullptr)

        

B. 也没有在拷贝构造函数的初始化列表中被初始


        

在执行 swap(tmp, s2) 时,由于 s2 指向的是随机内存空间,交换后当 tmp 调用析构函数时会导致严重错误。

        

        

代码演示图如下所示:

        

        

2.4 赋值运算符重载

        

赋值运算符重载与拷贝构造函数类似,同样需要考虑深浅拷贝问题。

        

为了实现深拷贝,我们提供以下两种实现方式:

        

① 传统写法

//例如: s2 = s1 //返回类型为string& 实现了连续的赋值,并且减少了拷贝 string& operator =(const string& other) { if (this == &other) { return *this; } delete[] _str; _str = new char[other._capacity + 1]; strcpy(_str, other._str); _size = other._size; _capacity = other._capacity; return *this; }

        

赋值运算符的几个关键步骤:

        

①防范自我赋值(if (this == &other)):防止 s1 = s1 时,先把自己的内存释放了,导致后面的拷贝出现段错误。

        

②清理旧资源(delete[] _str):防止内存泄漏。

        

③深拷贝新资源(new 和 strcpy)。

        

④返回自身引用(return *this):支持 s1 = s2 = s3 这样的链式操作。

        

        

② 现代写法

//[补充] 实现赋值重载:cpoy-and-swap 写法 /* 假设执行 s2=s1; s1传值传参给tmp,此时tmp执行深拷贝构造 再将s2和tmp进行交换,此时s2的值就与s1的值相同,于此时同时tmp的值为原来的s2,函数结束时进行析构原来的s2指向的空间 */ //封装swap函数 void swap(string& tmp) { std::swap(_str, tmp._str); std::swap(_size, tmp._size); std::swap(_capacity, tmp._capacity); } string& operator=(string tmp) { swap(tmp); return *this; }

        

场景示例:假设你写了这句代码:s1 = s2;

        

        

①第 1 步:利用参数“按值传递”,外包【深拷贝】

        

发生了什么: 当进入 operator= 函数时,因为参数是按值传递的(string tmp),C++ 编译器会自动调用“拷贝构造函数”,用 s2 克隆出一个局部的临时对象 tmp。

        

为什么高明: 这个临时对象 tmp 已经是一份完美的深拷贝了!

        

此时 s1 毫发无损,完美解决了我们之前担心的“旧家拆了,新家没建好”的致命隐患。


        

        

②第 2 步:偷天换日,外包

        

发生了什么: 进入函数体后,s1(即 this)看着临时对象 tmp 手里拿着新鲜出炉的数据,于是执行了 std::swap,直接把两者的底牌(指针、容量、大小)互换了。

        

结果: * s1 拿到了 tmp 申请好的新内存和新数据(更新完成!)。


        

tmp 成了一个“背锅侠”,手里拿着 s1 淘汰下来的旧内存和旧数据。

        

        

③第 3 步:借刀杀人,外包【旧内存释放】

                

发生了什么: 随着 return *this; 执行完毕,函数结束。

                

为什么高明: 局部对象 tmp的生命周期走到了尽头。

        

C++ 编译器会自动调用它的“析构函数” , 因为此时 tmp手里捏着的是 s1 的旧内存,析构函数手起刀落,顺道就把 s1 之前的旧垃圾清理得干干净净!完全不需要你手动写 delete[]。

        

代码演示图如下所示:

        

        

2.5 析构函数

        

//析构函数 ~string() { delete[] _str; _size = 0; _capacity = 0; }

        

        

三、迭代器

        

string类中的迭代器本质上是字符指针,iterator只是其类型别名

typedef char* iterator; typedef const char* const_iterator; 

        

3.1 begin

                

string类中的begin函数:返回字符串首字符的地址,即迭代器初始的位置。

//A. 普通“读写版” iterator begin() { return _str; } //B. 常量“只读版” const_iterator begin() const { return _str; }

        

3.2 end

        

string类中的end函数:返回字符串中最后一个字符的后一个字符的地址(即’\0’的地址),即迭代器的末位置。        

//A. 普通“读写版” iterator end() { return _str + _size; } //B. 常量“只读版” const_iterator end() const { return _str + _size; }

        

四、容量和大小相关函数

        

4.1  size

        

 由于 string 类的成员变量是私有的,无法直接访问,因此它提供了 size()这个成员函数,用于获取字符串的字符个数。

//返回字符串的长度 size_t size() const { return _size; } 

        

4.2 capacity

        

 由于 string 类的成员变量是私有的,无法直接访问,因此它提供了 capcity()这个成员函数,用于获取字符串的容量个数。

//返回字符串容量的函数 size_t capacity() const { return _capacity; } 

       

4.3 reserve

        

reserve函数:

        

函数原型为:void reserve(size_t n); 

        

函数功能为:


        

①当n超过对象当前capacity时,将capacity扩展至不小于n的大小 

        

②当n小于当前capacity时,维持现有capacity不变

        

代码示例:

//reverse函数的实现:对字符串进行扩容操作,参数size_t n 表示新容量的大小 //温馨提示:一般只扩容不缩容 void string::reserve(size_t n) { if (n > _capacity) { //注意:预留一个位置给'\0',capacity的大小不包含'\0' char* tmp = new char[n + 1]; //拷贝旧空间的值到新空间 strcpy(tmp, _str); //释放旧空间 delete[]_str; //指向新空间 _str = tmp; //更新当前空间 _capacity = n; } }

        

4.4 empty

        

代码示例:判断字符串是否为空,调用strcmp库函数进行实现

bool string::empty() { return strcmp(_str, "") == 0; }

        

五、字符串修改函数

        

5.1 push_back

        

push_back函数

        

函数原型为:void push_back(char c); 

        

函数功能为:在当前字符串的后面尾插上一个字符

        

代码实现:

// push_back函数的实现:尾插一个字符 void string::push_back(char c) { //判定是否能够进行尾插,检查容量是否足够 if (_size == _capacity) { //使用二倍扩容法 reserve(_capacity == 0 ? 4 : _capacity * 2); } _str[_size++] = c; //注意:_str[_size] 的'\0' 被插入的字符 c 所覆盖,所以需要再补充一个'\0' _str[_size] = '\0'; }

        

代码讲解:

        

①在尾插操作前,首先需要检查当前容量是否足够。如果空间不足,则调用 reserve 函数进行扩容。

        

②完成扩容检查后,执行字符的尾插操作。

        

③特别需要注意的是,尾插字符后必须在其后添加'\0'终止符。否则在打印字符串时可能出现非法访问问题,因为新插入字符的后方位置不一定存在字符串终止符。

        

5.2 append

        

append函数

        

函数原型:void append(const char* str)

        

函数功能:在当前字符串的后面尾插一个字符串

        

代码示例:

// append函数的实现:尾插一个字符串 void string::append(const char* s) { //判断是否能够进行尾插入一个字符串 size_t len = strlen(s); if (len + _size > _capacity) { //对字符串进行扩容操作 //扩容规则:大于当前字符串长度的二倍,按照所需长度扩容,反之按照当前容量的二倍扩容,进而减少扩容的次数 size_t newcapacity = (_size + len > _capacity * 2) ? _size + len : _capacity * 2; reserve(newcapacity); } //通过strcpy , 从源字符串'\0'的位置即:_str+_size,尾插字符串s strcpy(_str + _size, s); //更新当前size的长度 _size += len; }

        

代码详解:

                

①在执行尾插操作前,需先检查当前字符串缓冲区是否具备足够空间。

        

②若空间不足,则需先进行扩容操作。

        

③扩容完成后,将待插入字符串追加至目标字符串末尾

        

注意:由于待插入字符串本身已包含终止符'\0',故无需额外添加。

        

5.3 operator+=

        

operator+=函数重载:

        

A.函数原型:

        

①string& operator+=(const char* s);     
        

②string& operator+=(const char c);   

        

B.函数功能:

        

①重载运算符+= 使其能够通过运算符尾插一个字符串

        

②重载运算符+= 使其能够通过运算符尾插一个字符        

        

C.函数实现思想:

        

①对于字符串与字符的尾插,直接调用push_back函数即可实现。

        

②对于字符串与字符串的尾插,通过调用append函数进行实现

     

//运算符重载 +=的实现: 尾插入一个字符 string& string::operator+=(char c) { //复用push_back函数 this->push_back(c); return *this; } //运算符重载 +=的实现: 尾插入一个字符串 string& string::operator+=(const char* s) { //复用append函数 this->append(s); return *this; }

        

5.4 insert

        

insert函数:        

        

函数原型:

        

①void insert(size_t pos, char c); 

        

②void insert(size_t pos, const char* s);     

        

函数功能:

        

①在pos位置插入一个字符

        

② 在pos位置插入一个字符串

        

代码实现1:在pos位置插入一个字符

// insert函数的实现:在pos位置插入一个字符 void string::insert(size_t pos, char c) { //保证pos位置 assert(pos <= _size); //判断是否需要进行扩容操作 if (_size == _capacity) { size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2; reserve(newcapacity); } size_t end = _size + 1; while (end > pos) { _str[end] = _str[end - 1]; --end; } _str[pos] = c; _size++; }

        

代码注意事项:

        

化解size_t 死循环危机: 把 end 的初始值设为 _size + 1,并用 end > pos 作为条件。

        

保证了即使在 pos = 0(头部插入)的最极端情况下,end 减到 0 循环就会立刻停止,绝不会发生 0 - 1 变成巨大正数(整型下溢)导致的内存越界崩溃。

        

错误代码示例:

 

// 典型的错误写法(容易引发灾难) size_t end = _size; while (end >= pos) { _str[end + 1] = _str[end]; --end; }

        

错误原因分析:

        

①假设你要在头部插入字符,也就是 pos = 0。

        

②当 end 减到 0 时,把第 0 个字符往后挪完,接着执行 --end,因为 size_t 不能是负数,0 - 1 会直接发生整型下溢,变成一个极其巨大的正数(比如 4294967295)。

        

③此时循环条件 end >= 0 依然成立!程序就会陷入死循环,并疯狂篡改非法内存,最终导致程序崩溃。       

        

        

代码实现2:在pos位置插入一个字符串

//insert函数的实现:在pos位置插入一个字符串 void string::insert(size_t pos, const char* s) { assert(pos <= _size); //计算当前字符串的长度,判断是否需要进行扩容处理 size_t len = strlen(s); //特殊处理,当len等于0的时候 if (len == 0) return; if (len + _size > _capacity) { size_t newcapacity = len + _size > _capacity * 2 ? len + _size : _capacity * 2; reserve(newcapacity); } //进行移动字符保证字符串能够插入 size_t end = _size + len; while (end > pos + len - 1) { _str[end] = _str[end - len]; --end; } //插入字符串到指定pos的位置处 for (size_t i = 0; i < len; i++) { _str[pos + i] = s[i]; } _size += len; }

        

这段代码的具体执行步骤是:

        

①先搬家 \0: end 初始等于 _size + len。_str[end] = _str[_size],把原本在末尾的结束符 \0 挪到了新的队尾。

        

②依次搬家后续字符: 随着 --end,依次把原来队伍最后的字符,挪到加上 len 之后的新位置。

                

③停止: 循环条件是 end > pos + len - 1(等价于 end >= pos + len),当 end 等于 pos + len 时,刚好执行 _str[pos + len] = _str[pos];。

        

④这意味着,原本在 pos 位置的那个字符,被挪到了它该去的新位置,搬家正式结束。

        

此时,从 _str[pos] 到 _str[pos + len - 1] 这段空间就被彻底腾空了,正好用来执行你代码最后的那段 for 循环:把新的字符串 s 塞进这个空位里。

        

        

5.5 erase

        

erase函数

        

函数原型:void erase(size_t pos, size_t len = npos);

        

函数功能:删除字符串从pos位置开始的len个字符,len的默认长度为 size_t npos=-1 (其值为4294967295)

        

一般分为两种情况:

        

①pos位置及其之后的有效字符都需要被删除。

        

②pos位置及其之后的有效字符只需删除一部分。

        

代码实现:

//erase函数的实现:从pos位置删除len个字符 void string::erase(size_t pos, size_t len) { //判断pos位置是否合理 assert(pos < _size); //判断删除的长度len的情况 if (len >= _size - pos) { _str[pos] = '\0'; _size = pos; } else { size_t begin = pos + len; while ( begin<= size() ) { _str[begin - len] = _str[begin]; ++begin; } //更新size的个数 _size -= len; } }

        

        

5.6 substr

        

substr函数

        

函数原型:string substr(size_t pos, size_t len)        

        

函数功能:从pos位置开始(包括pos),截取len个大小的字符。

        

代码实现:

 string string::substr(size_t pos, size_t len) { assert(pos < _size); if (len >= _size - pos) { //更新一下当前的len len = _size - pos; } string; //预留空间,避免多次扩容 tmp.reserve(len); for (size_t i = 0; i < len; i++) { tmp += _str[pos + i]; } return tmp; }

        

5.7 clear

        

clear函数

        

函数原型:void clear();

        

函数功能:用于将对象中存储的字符串置空   

        

代码实现:

//clear函数的实现:清除字符串的内容 void string::clear() { _str[0] = '\0'; _size = 0; }

六、访问字符串相关函数     

        

6.1 operator[]

        

operator函数重载

        

函数原型:

        

①char& operator[] (size_t pos)

        

②const char& operator[] (size_t pos) const


        

函数功能:[]运算符的重载使string对象能够像C字符串一样,通过下标访问特定位置的字符。

        

代码实现:

//A. 普通“读写版” char& operator[] (size_t pos) { //断言:保证下标位置在有效的范围区间内 assert(pos < _size); return _str[pos]; } //B. 常量“只读版” const char& operator[] (size_t pos) const { //断言:保证下标位置在有效的范围区间内 assert(pos < _size); return _str[pos]; }

        

6.2 find

        

find函数

        

函数原型:

        

①size_t find(char c, size_t pos = 0);

        

②size_t find(const char* s, size_t pos = 0);

        

函数功能:

        

① 从pos位置(默认为0)查找一个字符,查找失败失败返回npos,查找成功返回第一个字符的位置下标

        

② 从pos位置(默认为0)查找一个字符串,查找失败失败返回npos,查找成功返回字符串中第一个字符的位置        

        

代码实现1:从pos位置向后查找一个字符,返回查找到的字符下标

//find函数的实现:从pos位置向后查找一个字符,返回查找到的字符下标 size_t string::find(char c, size_t pos) { assert(pos < _size); for (size_t i = pos; i < _size; i++) { if (_str[i] == c) { return i; } } return npos; }

        

代码实现2:从pos位置向后查找一个字符串,返回查找

//find函数的实现:从pos位置向后查找一个字符串,返回查找 size_t string::find(const char * s, size_t pos) { assert(pos < _size); //for (size_t i = pos; i + s.size() <= _size; i++) //{ // size_t j = 0; // while (j < s.size()) // { // if (_str[pos + j] != _str[j]) // { // break; // } // j++; // } // if (j == s.size()) return i; //} const char* ptr = strstr(_str, s); //在母串str中,能够查找到字串s if (ptr != nullptr) { return ptr - _str; } else { return npos; } }

        

        

七、比较运算符重载

        

C++提供了六个关系运算符:>、>=、<、<=、==和!=。

在实际应用中,我们只需为类重载其中两个运算符,其余四个可以通过已重载的运算符来实现。

      

例如:对于string类,我们可以选择只重载 < 和 == 这两个关系运算符。

//重载运算符< bool operator<(const string& s1, const string& s2) { return strcmp(s1.c_str(), s2.c_str()) < 0; } //重载运算符== bool operator==(const string& s1, const string& s2) { return strcmp(s1.c_str(), s2.c_str()) == 0; }

        

剩余运算符: >、>=、<=和!= ,通过复用上述关系运算符

//重载运算符<= bool operator<=(const string& s1, const string& s2) { return s1 < s2 || s1 == s2; } //重载运算符> bool operator>(const string& s1, const string& s2) { return !(s1 <= s2); } //重载运算符>= bool operator>=(const string& s1, const string& s2) { return !(s1 < s2); } //重载运算符!= bool operator!=(const string& s1, const string& s2) { return !(s1 == s2); }

        

        

八、字符串输入与输出

        

8.1 >>运算符的重载

        

operator>> 函数重载

        

函数原型:istream& operator>>(istream& in, string& s)

        

函数功能: 重载 >> 运算符旨在使 string 对象能够像内置类型一样直接使用输入操作。

        

代码实现:

//流提取运算符重载的实现:输入字符串 istream& operator>>(istream& in, string& s) { //清除原有的字符 s.clear(); //预留缓冲数组,避免扩容 const int N = 256; char buff[N] = { 0 }; int i = 0; //清除前导空白区域 char ch = in.get(); while (ch == ' ' || ch == '\n' || ch == '\t') { ch = in.get(); // 只要是空白字符,就继续往下读,丢弃掉 } //遇到换行 和 空格停止 while (ch != ' ' && ch != '\n' && ch != '\t') { buff[i++] = ch; if (i == N - 1) { buff[i] = '\0'; s += buff; i = 0; } ch = in.get(); } //预留数组未满,就提前结束 if (i > 0) { buff[i] = '\0'; s += buff; } return in; }

        

 代码详解:

        

①重置状态 (s.clear()):确保 cin >> s 是覆盖写入,而不是追加。

        

②准备“运货小推车” (buff[256]):通过局部数组攒字符,避免频繁调用 += 导致底层频繁的内存扩容。

        

③精准打击(跳过前导空白):这是你新加的逻辑。无论用户在正式输入前敲了多少个空格或回车,全被这个 while 循环吞掉,直到碰见真正的有效字符。

        

④高效搬运(核心循环):遇到非空白字符开始装车。车装满了(i == N - 1)就卸货到 s 里,然后继续装。遇到下一个空格或换行,就意味着“这个单词读完了”,立即停车。

        

⑤打扫战场(扫尾工作):如果最后车里还有剩的货(i > 0),一次性全卸给 s。

        

8.2 <<运算符的重载

        

operator<< 函数重载

        

函数原型:ostream& operator<<(ostream& out, const string& s);

        

函数功能:重载<<运算符是为了让string对象能够像内置类型一样使用<<运算符直接输出打印

        

代码实现:

//流插入运算符重载的实现:输出字符串 ostream& operator<<(ostream& out, const string& s) { for (auto ch : s) { out << ch; } return out; }

        

既然看到这里了,不妨关注+点赞+收藏,感谢大家,若有问题请指正。

        

Read more

JavaSE基础-Java String不可变性深度解析

JavaSE基础-Java String不可变性深度解析

目录 Java String 不可变性(Immutability)深度解析 一、核心原因详解 1. 字符串常量池(String Pool)—— 内存共享的基础 精简版 详细版 2. 安全性(Security)—— 防止被恶意篡改 精简版 详细版 3. 线程安全(Thread Safety)—— 天然的不可变对象 精简版 详细版 4. 适合作为 HashMap 的 Key —— hashCode 缓存 精简版 详细版 5. 缓存 hashCode —— 提升性能 二、不可变对象的一般性好处(扩展) 三、一句话总结 高频修改场景的核心矛盾:不可变性带来的 GC 压力

By Ne0inhk
别再乱用 ArrayList 了!这 4 个隐藏坑,90% 的 Java 开发者都踩过

别再乱用 ArrayList 了!这 4 个隐藏坑,90% 的 Java 开发者都踩过

🎁个人主页:User_芊芊君子 🎉欢迎大家点赞👍评论📝收藏⭐文章 🔍系列专栏:AI 文章目录: * 【前言】 * 坑 1:遍历删除元素,触发 ConcurrentModificationException * 坑的表现 * 踩坑场景 * 底层原因(通俗解释) * 错误/正确代码对比 * 错误代码 * 正确代码(3 种方案) * 坑 2:初始容量设置不当,导致频繁扩容,性能损耗 * 坑的表现 * 踩坑场景 * 底层原因(通俗解释) * 错误/正确代码对比 * 错误代码 * 正确代码 * 扩展建议 * 坑 3:空指针/索引越界,忽略索引范围或元素为空 * 坑的表现 * 踩坑场景 * 底层原因(通俗解释) * 错误/

By Ne0inhk
【Java 开发日记】我们来说一说 Redis 主从复制的原理及作用

【Java 开发日记】我们来说一说 Redis 主从复制的原理及作用

目录 概述 一、核心作用 二、详细工作原理 阶段 1:连接建立与配置 阶段 2:数据同步(全量/部分同步) 阶段 3:命令传播(增量同步) 三、重要特性与配置 四、总结与形象比喻 面试回答 概述 Redis 主从复制是一种数据同步机制,它允许一个 Redis 服务器(称为 主服务器/Master)将其数据复制到一个或多个 Redis 服务器(称为 从服务器/Slave/Replica)。这是 Redis 实现高可用性、可扩展性和数据冗余的核心技术之一。 一、核心作用 1. 数据冗余与备份: * 核心作用:从服务器是主服务器数据的实时热备份。当主服务器数据丢失或损坏时,

By Ne0inhk
【JAVA 进阶】SpringMVC全面解析:从入门到实战的核心知识点梳理

【JAVA 进阶】SpringMVC全面解析:从入门到实战的核心知识点梳理

文章目录 * 前言 * 一、SpringMVC概述 * 1.1 MVC设计模式简介 * 1.2 SpringMVC的定义与核心优势 * 1.3 SpringMVC的应用场景 * 二、SpringMVC核心原理与执行流程 * 2.1 SpringMVC核心组件 * 2.1.1 前端控制器(DispatcherServlet) * 2.1.2 处理器映射器(HandlerMapping) * 2.1.3 处理器适配器(HandlerAdapter) * 2.1.4 处理器(Handler) * 2.1.5 视图解析器(ViewResolver) * 2.1.6 视图(View) * 2.1.

By Ne0inhk