在java虚拟机中大致分为了栈、堆、方法区、常量池以及本地方法区等,由于String的存储涉及一二,我们便来看一下一些简单的存储机理。
首先栈的存储特点:先进后出,用来存放顺序控制指令的基本数据类型,还有一些引用。值得我们注意的是(类里面的基本类型放在堆中,方法里面的基本类型在栈中),陆续我们都会讲到。
那么今天们要讲的String就主要存储在常量池中(现在的java认为常量池是堆的一部分)。
比如我们现在定义了两个String a和b。
即String a = “qwe”;
String b = “qw”;
那么我们就在栈中 有了a和b两个字符串的引用变量,在堆中的常量池中根据String的length给它分配足够的大空间(不可变性)。此时 “=”操作就会分别得到 “qwe”和“qw”的地址。也就是a指向了“qwe”,b指向了“qw”。
现在我们就可以清楚地看到我们的字符串的赋值操作了,那么问题来了,是不是我们每声明一个String变量在堆中都会有一个内存空间被申请呢?
String c = “qwe”;
现在 我们的java虚拟机得到这段代码后,会迅速在常量池中扫描搜索“qwe”,此前的 a 已经申请了这部分空间,所以我们的 c 会直接指向“qwe”,这就实现了字符串的共享。
如果我们重新又给了 c 一次赋值操作呢?
c = “qwer”
此时也就是c的指向发生了改变,它便指向了新的内存空间区域,它从“qwe”的指向转到了“qwer”的指向。也就是它获得了新的地址。值得我们注意的是,这次仅仅只是改变了c的指向,与a无关(即便它两是同一指向)。这也是java的值传递的一个样例。
String d = a+b;
这个同样是根据a+b两个字符串拼接的length先分配足够大的空间,在常量池中有了一个“qweqw”的字符串,此时d指向了该字符串。
c = c + “s”;
涉及到自身的拼接呢?并不是在c的指向那个字符串中直接+“s”,我们已经反复强调了不变性,一个字符串的地址确定了后就不可以修改了。这个我们会拿到原来c的地址和length来重新申请一个空间得到这个字符串“qwers”,c的指向也就发生了改变。而原来的“qwer”因为没有指向被清除(如果还有别的引用指向不会被清除)。
下面我们看一下String的比较方法
String s1 = “abc”;
String s2 = “abc”;
System.out.println(s1==s2);
System.out.println(s1.equals(s2));
两个的输出结果都是“true”。但是两个的本质不同。“==”涉及到了指向问题,也就是两者如果是同一指向(记录了同一个地址),那么便是“true”,相反就是“false”。“equal()”是String类封装好的一个方法,用于比较字符串是否相等,不涉及指向问题。很简单相等就是相等。
String s3 = “abcd”;
String s4 = s3.substring(0,3);
System.out.println(s1==s4);
System.out.println(s1.equals(s4));
前者的输出结果为“false”,s4是调用了s3的一个方法 截取了s3的部分字段,而这部分字符串由new一个新空间,s4指向了新空间(堆中并非常量池)。s1与s3的指向便不相同,所以为“false”。
后者为“true”,s1与s4代表的字段相同。
陆续会更新String的另一种构造方法,情况会有所不同。谢谢大家。