C# /JAVA: 字符串构建利器StringBuilder区别
前言
本章笔记直接记录的string、StringBuilder内存存储原理,并没有大幅度、重点的去介绍堆、栈、常量池的相关底层实现原理。
所以,为了帮助大家更好的去理解,可以在阅读本文章前去了解堆、栈、常量池之间的基础关系,对以下的阅读有很大的帮助。
名词解释
栈:存放变量(值类型)
堆:存放对象(引用类型)
常量池:它是一个Hash表。
为了提升性能和减少内存开销,避免字符串的重复创建,所以开辟出来一个单独的内存空间,就是字符串池。
字符串常量池是由String类私有的维护。
八大基本类型
: byte、short、int、long、boolean、float、double、char
四大引用类型
:数组、class、interface、字符串(string
一、String
1.1 示例
案例一: 不同变量赋值( = )
String str1 = "Hello" ;
String str2 = "Hello" ;
System.out.println(str1 ); //结果: Hello
System.out.println(str1 ); //结果: Hello
- 情况1:不存在
变量
str1 会存放
在栈
中,- 首先在
常量池
中进行查找“Hello”是否存在。 - 不存在时,会在常量池中以
键值对
格式创建<key,value>,value则指向堆中的“Hello”对象指针。 - 从而,str1的
引用地址
就是堆中0x0001对应的“Hello”指针
。
- 情况2:存在
变量
str2 会存放在栈
中,- 首先在
常量池
中进行查找“Hello”是否存在。 存在
时,会继续使用常量池中已存在的key,不会再新建。也就是 str2使用常量池中hello指向堆中的0x0001对应的“Hello”指针
。- 与str1的引用地址是一个。
案例二:相同变量赋值( = )
我们都知道String
属于类,它是不可变
的。即一旦一个String对象被创建以后便不能被更改、变长
、修改;直至这个对象被销毁。
不可变 : 文章下方会有专门的讲解
下面写了一个小例子,如下方所示:
String str1 = "Hello" ;
str1 = "Word" ;
//打印出来的str1为: Word
System.out.println(str1);
看到这里,可能就会有疑问:不是不能被修改吗?怎么会对他进行了修改?
针对这个问题,我画了一张底层实现原理图,希望能够帮助到大家。如图所示:
前面我们说到了第一次str1赋值“Hello”,在常量池中创建后,其value指向对象指针
;
从图中可以看出,再次给str1赋值“Word”时,并不是对原来堆中的实例对象进行重新赋值,而是生成一个新的实例对象,并且str1的引用地址变指向了这个新的“Word”这个字符串。
之前的实例对象“Hello”依然存在,只是不再被引用了而已;如果没有被再次引用,则会被垃圾回收。
但是吧,在这里暂时不回被回收(垃圾回收不能释放被Hash表中引用的字符串,因为Hash表中正在容纳对他们的引用。除非进程终止)
案例三:变量追加赋值( += )
前面提到了String
属于类,它是不可变
的。即一旦一个String对象被创建以后便不能被更改、变长
、修改;直至这个对象被销毁。
小案例:
String str1 = "Hello" ;
str1 + = " Word" ;
//打印出来的str1为:Hello Word
System.out.println(str1)
看到这里,可能就会有疑问:不是长度不可以变吗?为什么变量str1的长度会增加?会被修改?
针对这个问题,我又画了一张底层实现原理图,希望能够帮助到大家。如图所示:
公式变化:
str1在追加赋值+=“Word”
时,(图中为了好看,堆地址0x0003对应的字符串对象我添加了空格,不要被误导了哈~)实际上是str1+“Word”
(str1指向Hello) → Hello+Word
→ " HelloWord
"。
底层变化:
- str1 中的“Hello”、“Word” 在常量池中没有,所以需要在常量池中创建对应key,其value(0x0001、0x0002)指向堆中的对象指针(Hello、Word)。
- 前面也提到了,实际上str1 = str1+“Word”,而这一步操作是
隐式操作
,不走字符串常量池的。 - 也就是说:Hello+Word 是在
堆中相加
的,生成了新的对象
。 - 其新生成堆地址0x0003(对象在堆中地址)会赋给str1。
- str1 根据这个地址去找到对象 “ HelloWord ”。(之前的实例对象“Hello”“Word”依然存在,只是不再被引用了而已)。
1.2 常量池扩展(IsInterned、ReferenceEquals)
有关常量池动态机制在此处查看: string常量池/驻留池——动态机制
这两个都是在C#中提供的方法,java的我还没有使用过。之后的java练习中找到了平替将会及时补充本文哈。
Object.ReferenceEquals() 确定实例是否为同一实例
ReferenceEquals 方法是 Object类的静态方法
,不能被改写。该方法可以比较两个引用类型的引用是否指向用一个实例。
案例一:
> string aa = "Hello";
> string bb = "Hello";
> aa == bb
//判断变量是否相等。返回结果为:true
true
案例二:
> object.ReferenceEquals(aa,bb)
//判断两个变量引用地址是否相同
t