开局两张图,内容全靠刷。
马甲哥看到这样的现象,一开始还是有点懵逼。
这个例子,string是纯粹的引用类型,但是在函数传值时类似于值传递;我之前给前后示例的内存变化图吧:
根因就是大多数高级语言都把String设计成不可变的: 由一个字符串池管理字符串面值。
为什么被设计成不可变。
这个我还真的搜索了一下[1]:
总结起来:
- string 被设计为不可变, 是因为 string在现代任何语言中,使用很频繁:多个对象可能都是这个字符面值, 然后就设计一个Pool来存储string。既然pool里面共享字符面值,修改的时候又不能影响到别人,那就只好重新拷贝产生新的字符面值。
- 不可变资源消除了多线程中的资源竞争:对于文本的修改都会导致创建新空间,因此在多个线程同时访问文本无需设置锁,这对高频使用的String很友好。
- 字符串不变性对于[在哈希表中使用字符串作为键]很友好,需要计算哈希值的对象必须是不可变的,以确保哈希值不变。
- 一个有意思的现象是:String虽然是引用类型,字符串对比时却表现的像值类型
string str1="FooFoo"; string strFoo="Foo"; string str2= strFoo + strFoo; return str1 == str2; // 返回true
正因为String不可变性 & Pool的机制,频繁变更字符串,会在池中产生很多临时的不用的字符串,所以我们有了优化的套路:
StringBuilder
代表可变的字符串,一旦修改不会尝试创建新对象,而是动态扩展内存
var ss = new StringBuilder("Hello ", 100); // 初次字符容量100 ss.Append("www.cnblogs.com"); Console.WriteLine(ss.ToString()); // ss打印结果为:222
Span
Span该出圈了,
Span提供对内存连续区域的类型安全访问,该内存可以位于堆、堆栈、甚至是非托管内存;
与String不可变性相关的是ReadOnlySpan
(值类型), 提供内存数据的只读视图,每次切片不会产生新对象,而是在已存在的连续空间上创造新的视图。
var text = "https://www.cnblogs.com/JulianHuang/p/14817621.html"; ReadOnlySpan<char> nameSpan = text.AsSpan(8, 15); nameSpan = nameSpan.Slice(4,7); Console.WriteLine(nameSpan.ToString());
总结输出
今天从两张诡异的编程图聊到了String的不可变性、内存分布, 延伸谈到了
- String不可变性的设计设计考量(有先射箭再画靶的嫌疑☺️)
- 针对频繁修改的String如何做内存优化