最近突然想研究下string类型的内存管理。查了下网上的资料,发现有些文章说法不一,更有甚者误人子弟。所以我结合网上的文章和自己的实验写下这篇文章。
字符串常量池不在堆中也不在栈中,是独立的内存空间管理,在内存的常量区。
先看如下代码
string s1 = "1";
string s2 = "2";
s2 = "123";
Console.WriteLine(s1);
2022/12/3
这里有一处错误,本人在此修改下
string s2 = “2”; 修改为 string s2 = s1;
目的是让s1和s2指向同一块区域。
之后所有的图也有一小部分错误,s2初始应该指向"1"。
如果是引用类型,输出的应该是123,因为s1和s2指向了同一块区域,s2对这一块区域的值进行修改了,那么输出s1的值应该是被修改之后的值。
但是输出的是1,也就是说输出的是s1一开始赋的值。
所以上面代码的内存分配是这样的
当我们让s1 = “123”;的时候
当我们定义了s1和s2的字符串,然后CLR内部机制去字符串常量池中找,如果存在相同内容的字符串对象的引用,则将这个引用返回。否则新的字符串对象被创建,然后将这个引用放入字符串常量池,并返回该引用。当然如果是new出来的对象,则放在托管堆中。
测试环境:Unity
Tips:先看代码和结果,结论我放在后面
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ToStringTest : MonoBehaviour
{
private string str1 = "1";
private string str2 = "2";
private string str12 = "12";
private void Start()
{
string s = "1" + "2";
print($"s的值为{s}");
print($"s与str12值是否相等:{s.Equals(str12)}");
print($"s与str12地址是否相等:{ReferenceEquals(str12, s)}");
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ToStringTest : MonoBehaviour
{
private string str1 = "1";
private string str2 = "2";
private string str12 = "12";
private void Start()
{
string s = str1 + str2;
print($"s的值为{s}");
print($"s与str12值是否相等:{s.Equals(str12)}");
print($"s与str12地址是否相等:{ReferenceEquals(str12, s)}");
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ToStringTest : MonoBehaviour
{
private string str1 = "1";
private string str2 = "2";
private string str12 = "12";
private void Start()
{
string s = string.Intern(str1 + str2);
print($"s的值为{s}");
print($"s与str12值是否相等:{s.Equals(str12)}");
print($"s与str12地址是否相等:{ReferenceEquals(str12, s)}");
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ToStringTest : MonoBehaviour
{
private string str1 = "1";
private string str2 = "2";
private string str12 = "12";
private char[] chs = new char[] { '1', '2' };
private void Start()
{
string s = new string(chs);
print($"s的值为{s}");
print($"s与str12值是否相等:{s.Equals(str12)}");
print($"s与str12地址是否相等:{ReferenceEquals(str12, s)}");
}
}
我们可以使用System.String.IsInterned判断,如果 str 在公共语言运行时的暂存池中,则返回对它的引用;否则返回 null。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ToStringTest : MonoBehaviour
{
private char[] chs = new char[] { '1', '2' };
private void Start()
{
string s = new string(chs);
print(string.IsInterned(s));
}
}
这说明了字符串常量池中并没有"12",这个字符串常量。
注意:有些同学可能会这么说
为什么不用string.IsInterned(“12”),而用string.IsInterned(s)呢?
好,那么我们看看下面这部分代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ToStringTest : MonoBehaviour
{
private char[] chs = new char[] { '1', '2' };
private void Start()
{
string s = new string(chs);
print(string.IsInterned("121212121212"));
}
}
看到了吗,这里有一个需要注意的点,因为我们传参传了"121212121212"。所以就已经在字符串常量池中创建了这个对象,所以这并不能说明什么。
如果不信的话,再看下面这串代码
string s = new string(chs);
string s1 = "1", s2 = "2";
print(string.IsInterned(s1 + s2));
以下三种情况会查询暂存池(若查询不到就将其存入暂存池)
注意:不是所有的字符串都放在暂存池中,运行时期动态创建的字符串不会被加入到暂存池中。
暂存池由CLR来维护,其中的所有字符串对象的值都不相同。
只有编译阶段的文本字符常量会被自动添加到暂存池。
运行时期动态创建的字符串不会被加入到暂存池中,而是托管堆。
string.Intern()可以把动态创建的字符串加入到暂存池中。