关于string的一个不完全且不保证正确的总结

参考(或全部或部分转载:)https://www.cnblogs.com/xiaoxi/p/6036701.html
(详细内容参考以上链接,这里简单记些需要注意或易混的问题)

String str1="program";
String str2=new String("program");

碰到一点压力就把自己变成不堪重负的样子,碰到一点不确定性就把前途描摹成黯淡无光的样子,碰到一点不开心就把它搞得似乎是自己这辈子最黑暗的时候,大概都只是为了自己不去走而干脆放弃明天找的最拙劣的借口.

预备问题一:
字符串常量池为什么要存在?

我们知道字符串的分配和其他对象分配一样,是需要消耗高昂的时间和空间的,而且字符串我们使用的非常多。JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:使用字符串常量池。

预备问题二:
为什么要设置String类为不可变的,即immutable?

Java中的String被设计成不可变的,出于以下几点考虑:

  1. 字符串常量池的需要。字符串常量池的诞生是为了提升效率和减少内存分配。可以说我们编程有百分之八十的时间在处理字符串,而处理的字符串中有很大概率会出现重复的情况。正因为String的不可变性,常量池很容易被管理和优化。
  2. 安全性考虑。正因为使用字符串的场景如此之多,所以设计成不可变可以有效的防止字符串被有意或者无意的篡改。从java源码中String的设计中我们不难发现,该类被final修饰,同时所有的属性都被final修饰,在源码中也未暴露任何成员变量的修改方法。(当然如果我们想,通过反射或者Unsafe直接操作内存的手段也可以实现对所谓不可变String的修改)。
  3. 作为HashMap、HashTable等hash型数据key的必要。因为不可变的设计,jvm底层很容易在缓存String对象的时候缓存其hashcode,这样在执行效率上会大大提升。
    摘自:https://www.cnblogs.com/Kidezyq/p/8040338.html

预备问题三:
一大类问题:比较两个字符串是否相等
下面一一道来=。=
基础:

String str1="program";
String str2=new String("program");
String str3="program";
String str4=new String("program");
System.out.println(str1==str2);//false
System.out.println(str1==str3);//true
System.out.println(str2==str4);//false

First:“”,比较两个对象的地址空间是否相等
str1:创建时,首先check字符串常量池中是否有该字符串;发现没有,则在字符串常量池创建该字符串,并返回字符串常量池的引用;
str2:指向堆的一块空间,当然不相等。待检查
str3:检查字符串常量池,发现已有该字符串,则直接返回该字符串的引用,所以str1
str3。
str4:???可以说更疑惑了;封印解除!(emm)

进阶:

String str1="programming";
String str3="programming";
String str2="program"+"ming";
System.out.println(str1==str2);//true
System.out.println(str1==str3);//true

分析:因为例子中的s0和s1中的"helloworld”都是字符串常量,它们在编译期就被确定了,所以s0s1为true;而"hello”和"world”也都是字符串常量,当一个字符串由多个字符串常量连接而成时,它自己肯定也是字符串常量,所以s2也同样在编译期就被解析为一个字符串常量,所以s2也是常量池中"helloworld”的一个引用。所以我们得出s0s1==s2。

问题:emm
编译期到底做了些什么?
如果你坚持的话,编译器做了如图工作:算了 图太长,截不下
经过以上三步,常量池中只有programming,还是有programming,program,ming?——只有programming

String s0="helloworld"; 
String s1=new String("helloworld"); 
String s2="hello" + new String("world"); 
System.out.println("===========test4============");
System.out.println( s0==s1 ); //false  
System.out.println( s0==s2 ); //false 
System.out.println( s1==s2 ); //false

分析:用new String() 创建的字符串不是常量,不能在编译期就确定,所以new String() 创建的字符串不放入常量池中,它们有自己的地址空间。
s0还是常量池中"helloworld”的引用,s1因为无法在编译期确定,所以是运行时创建的新对象"helloworld”的引用,s2因为有后半部分new String(”world”)所以也无法在编译期确定,所以也是一个新创建对象"helloworld”的引用。

问题:
啥叫编译器无法确定?
回答:根据字节码文件啊,(就是编译后得到的那个,我发现!)
还是不懂,再议吧

String str1="abc";   
String str2="def";   
String str3=str1+str2;
System.out.println("===========test5============");
System.out.println(str3=="abcdef"); //false

分析:因为str3指向堆中的"abcdef"对象,而"abcdef"是字符串池中的对象,所以结果为false。JVM对String str=“abc"对象放在常量池中是在编译时做的,而String str3=str1+str2是在运行时刻才能知道的。new对象也是在运行时才做的。而这段代码总共创建了5个对象,字符串池中两个、堆中三个。+运算符会在堆中建立来两个String对象,这两个对象的值分别是"abc"和"def”,也就是说从字符串池中复制这两个值,然后在堆中创建两个对象,然后再建立对象str3,然后将"abcdef"的堆地址赋给str3。

问题:
问题太多有点懒得提了。…… 萎靡不振
根据上述分析,str3=str1+str2时,都做了哪些操作?在堆中又开辟两个空间,分别存字符串abc和def么?
最后一行进行str3和abcdef的比较时,没有在字符串常量池开辟一个空间存abcdef么?
重点:
str3的计算过程,会先在堆中开辟两个空间存储abc和def,然后计算得到abcedf,赋给str3。

一个总结:
字符串常量的+与字符串引用的+:
字符串常量的+:会在编译期进行优化(用人话来讲就是,编译器在编译这段代码时,将字符串常量的+操作后的结果放到常量池中;)
字符串引用的+:要到程序运行的时候才知道该值的结果(WHY?
如:

String str1="abc";   
String str2="def";
String str3=str1+str2;
String str4="abc"+"def";

我刚刚灵光一闪;
str3的计算是利用了str1和str2的引用;而引用的值在编译期间是无法确定的(也就是说,编译的时候没有分配内存,编译时只能存储str3的计算过程,也就是加法,但不能确定其值)(就这么个意思大概理解一下= =)

超难:
final修饰的string:

String s0 = "ab"; 
final String s1 = "b"; 
String s2 = "a" + s1;  
System.out.println("===========test9============");
System.out.println((s0 == s2)); //result = true

总结:
1)字符串的创建:
直接声明字符串:
检查常量池中是否存在,如果存在,则返回这个常量的引用;如果不存在,将该字符串放到常量池,返回其引用;
通过new关键词:
检查常量池中是否存在;如果存在,则在堆中开辟一个空间,将这个常量池中这个字符串的引用存到堆中;返回堆中的引用给字符串;如果不存在,则将该字符串放到常量池中,同样返回这个字符串常量的引用给堆中内存;返回堆的引用给字符串。???堆中存的是子串,还是常量池中的引用?

2)编译时确定与运行时确定:
通过声明字符串的方式定义字符串,在程序编译时即确定,并放到了静态常量池中(class文件的常量池中)。
通过new声明的字符串,要到运行时才在堆中分配空间;

3)字符串常量的+和字符串引用的+:
字符串常量的+:编译时完成拼接;
字符串引用的+:运行时完成拼接。
所以:

String s1="abc";
String s2="def";
String s3="abc"+"def";
String s4="abc"+s2;
String s5="abc"+new String("def");
System.out.println(s3==s4);//false
System.out.println(s3==s5);//false
System.out.println(s4==s5);//false

4)加final的string:
就相当于字符串常量,在编译时完成拼接:

final String s1="abc";
String s2=s1+"def";
String s3="abc"+"def";
System.out.println(s2==s3);//true

5)String.intern()方法:
intern方法完成的任务:

String s1=new String("abc");
s1=s1.intern();
String s2="abc";
System.out.println(s1==s2);//true

1.7之前:
s1.intern(),判断常量池中是否有字符串常量"abc",如果有,返回这个字符串常量的引用;如果没有,则在常量池中新建一个字符串常量,返回这个常量的引用;
NOTE:
s1.intern()是有返回值的,但是如果不通过:s1=s1.intern()接收,那s1的值(栈中的值),是不会改变的,还是指向堆;

String s3=new String("abc");
s3.intern();
String s4="abc";
System.out.println(s3==s4);//false

1.7及之后:
s1.intern(),判断常量池中是否有字符串常量"abc",如果有,返回这个字符串常量的引用;如果没有,则常量池中存储第一次出现这个常量的堆中的地址,并返回这个常量池的引用。

不懂的地方:
编译器做了什么事?
为什么字符串引用的+不能编译器确定(很傻叉的问题吧=3=)
如果通过new新建的对象,在创建时会在常量池中创建这个字符串常量,那要intern干吗?

自我安慰:
做做选择题应该没问题了;要让我说明原理,我就emmmm……

猜你喜欢

转载自blog.csdn.net/shelly_Chestnut/article/details/85108208