1.内存分区
(1)栈区:由编译器自动分配释放 ,存放值类型的对象本身,引用类型的引用地址(指针),静态区对象的引用地址(指针),代码中必须就栈的大小有明确的定义。栈区内存无需我们管理,也不受GC管理,栈顶元素使用完毕弹出就会立即释放(由操作系统管理)。
(2)堆区(托管堆):用于存放引用类型对象本身。在c#中由.net平台的垃圾回收机制(GC)管理。栈,堆都属于动态存储区,可以实现动态分配。
(3)静态区及常量区:用于存放静态类,静态成员(静态变量,静态方法),常量的对象本身。由于存在栈内的引用地址都在程序运行开始最先入栈,因此静态区和常量区内的对象的生命周期会持续到程序运行结束时,届时静态区内和常量区内对象才会被释放和回收(编译器自动释放)。所以应限制使用静态类,静态成员(静态变量,静态方法),常量,否则程序负荷高。
(4)代码区:存放函数体内的二进制代码。
3.什么时候用String?什么时候用StringBuilder?
String是引用类型。
(1)String的存储方式很特殊,在c#的内存中,在常量区里会分配一块空间叫做String暂存池(常量池),在某些时候,我们的字符串数据是存储在这个常量池中的,而地址依然是存放在栈中。
字符串一旦创建就不可修改大小,每次使用System.String类中的方法之一时,都要在内存中创建一个新的字符串对象,这就需要为该新对象分配新的空间。在需要对字符串执行重复修改的情况下,与创建新的String对象相关的系统开销可能会非常昂贵(销毁前面的创建新的字符串,新的地址和值)。如果要修改字符串而不创建新的对象,则可以使用System.Text.StringBuilder类。例如当在一个循环中将许多字符串连接在一起时,使用StringBuilder类可以提升性能。
所以对字符串添加或删除操作不频繁的话,就几个固定的string累加的时候就不需要StringBuilder了,毕竟StringBuilder的初始化也是需要时间的。对字符串添加或删除操作比较频繁的话那就用StringBuilder。
(2)微软在处理字符串的时候用到散列表:它是什么呢?简单理解就是当你创建了字符串"china"这个字符串的时候,当你再创建这个字符串的时候,编译器是不会再去开辟新的内存来存储的。它会直接指向第一次创建的地址
4.引用和值比较
1) ==
——对于值类型,比较的是两个对象的代数值是否相等,比较时会自动进行类型转换(自定义值类型需要重载运算符后才可以使用==比较)
——对于引用类型,比较的是两个对象的引用(string类型比较的是值)
int a = 1;
long b = 1;
Debug.Log(a == b);//true
string s1 = "string";
string s2 = "string";
Debug.Log(s1 == s2);//true
StringBuilder sb1 = new StringBuilder("StringBuilder"); StringBuilder sb2 = new StringBuilder("StringBuilder");
Debug.Log(sb1 == sb2);//false
2) Equals
——对于值类型,比较的是两个对象的代数值是否相等,比较时不会自动进行类型转换(自定义值类型需要重载运算符后才可以使用==比较)
——对于引用类型,比较的是两个对象的地址(string类型比较的是值)
int a = 1;
long b = 1;
Debug.Log(a.Equals(b));//false
string s1 = "string";
string s2 = "string";
Debug.Log(s1.Equals(s2));//true
StringBuilder sb1 = new StringBuilder("StringBuilder");
StringBuilder sb2 = new StringBuilder("StringBuilder");
Debug.Log(sb1.Equals(sb2));//false
3) Object.ReferenceEquals(obj1,obj2)
—对于值类型,返回值总是为false,因为装箱后的引用总是不同的
——对于引用类型,比较的是两个对象的引用(string类型比较的是值)
int a = 1;
long b = 1;
Debug.Log(ReferenceEquals(a, b));//false
string s1 = "string";
string s2 = "string";
Debug.Log(ReferenceEquals(s1, s2));//true
StringBuilder sb1 = new StringBuilder("StringBuilder");
StringBuilder sb2 = new StringBuilder("StringBuilder");
Debug.Log(ReferenceEquals(sb1, sb2));//false
总结
我们要知道不是所有字符串都放在常量池当中:
存放暂存池:
(1)用字面量值创建String对象,例:String str = "ABCD";
(2)用String.Intern(),例:StringBuilder sb = new StringBuilder("ABCD");string str1 = "ABCD";string str2=string.Intern(sb.ToString);
(3)字符串拼接,例:str1 = "ABCD";str2 = "EFG";str1+str2。
不存放暂存池(存放在堆中):
(1)使用char[].Tostring(),例:str1=ABCD”;char[]charArray = str1.ToArray(); str2 = charArray.ToString();
(2)使用new String(),例:str1 =new String()。
编译其实只是一个扫描过程,进行词法语法检查,代码优化而已。我想你说的"编译时分配内存"很不严谨,只是指“编译时赋初值”,它只是形成一个文本,检查无错误,并没有分配内存空间。编译时是不分配内存的,此时只是根据声明时的类型进行占位,到以后程序执行时分配内存才会正确。
参考:C# 编译&运行原理_wangyihero8的博客-CSDN博客