1. 看看源码

大家都知道, 被声明为 final,因此它不可被继承。( 等包装类也不能被继承)。我们先来看看 的源码。

在 Java 8 中, 内部使用 char 数组存储数据。

final class

java.io., , {

/** The value is used for . */

final char value[];

在 Java 9 之后, 类的实现改用 byte 数组存储字符串,同时使用 coder 来标识使用了哪种编码。

final class

java.io., , {

/** The value is used for . */

final byte[] value;

/** The of the used to the bytes in {@code value}. */

final byte coder;

value 数组被声明为 final,这意味着 value 数组初始化之后就不能再引用其它数组。并且 内部没有改变 value 数组的方法,因此可以保证 不可变。

2. 不可变有什么好处呢

2.1 可以缓存 hash 值

因为 的 hash 值经常被使用,例如 用做 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。

2.2 Pool 的使用

如果一个 对象已经被创建过了,那么就会从 Pool 中取得引用。只有 是不可变的,才可能使用 Pool。

2.3 安全性

经常作为参数, 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 是可变的,那么在网络连接过程中, 被改变,改变 的那一方以为现在连接的是其它主机,而实际情况却不一定是。

2.4 线程安全

不可变性天生具备线程安全,可以在多个线程中安全地使用。

3. 再来深入了解一下

3.1 “+” 连接符

字符串对象可以使用“+”连接其他对象,其中字符串连接是通过 (或 )类及其 方法实现的,对象转换为字符串是通过 方法实现的。可以通过反编译验证一下:

/**

* 测试代码

*/

class Test {

void main([] args) {

int i = 10;

s = “abc”;

.out.(s + i);

/**

* 反编译后

*/

class Test {

void main( args[]) { //删除了默认构造函数和字节码

byte byte0 = 10;

s = “abc”;

.out.((new ()).(s).(byte0).());

由上可以看出,Java中使用”+”连接字符串对象时,会创建一个()对象,并调用()方法将数据拼接,最后调用()方法返回拼接好的字符串。那这个 “+” 的效率怎么样呢?

3.2 “+”连接符的效率

使用“+”连接符时,JVM会隐式创建对象,这种方式在大部分情况下并不会造成效率的损失,不过在进行大量循环拼接字符串时则需要注意。比如:

s = “abc”;

for (int i=0; i

s += “abc”;

这样由于大量创建在堆内存中,肯定会造成效率的损失,所以在这种情况下建议在循环体外创建一个对象调用()方法手动拼接(如上面例子如果使用手动拼接运行时间将缩小到1/200左右)。

与此之外还有一种特殊情况,也就是当”+”两端均为编译期确定的字符串常量时,编译器会进行相应的优化,直接将两个字符串常量拼接好,例如:

.out.(“Hello” + “World”);

/**

* 反编译后

*/

.out.(“”);

4. 字符串常量

4.1 为什么使用字符串常量?

JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:使用字符串常量池。每当创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于字符串的不可变性,常量池中一定不存在两个相同的字符串。

4.2 实现字符串常量池的基础

实现该优化的基础是因为字符串是不可变的,可以不用担心数据冲突进行共享。

运行时实例创建的全局字符串常量池中有一个表,总是为池中每个唯一的字符串对象维护一个引用,这就意味着它们一直引用着字符串常量池中的对象,所以,在常量池中的这些字符串不会被垃圾收集器回收。

我们来看个小例子,了解下不同的方式创建的字符串在内存中的位置:

= “abc”; // 常量池

= “abc”; // 常量池

= new (“abc”); // 堆内存

string类型_string[]_string[]

5. 类常见的面试题

5.1 判断字符串s1和s2是否相等

void main([] args) {

s1 = “123”;

s2 = “123”;

s3 = “1234”;

s4 = “12” + “34”;

s5 = s1 + “4”;

s6 = new (“1234”);

.out.(s1 == s2); // true

.out.(s1.(s2)); //true

.out.(s3 == s4); //true

.out.(s3 == s5); // false

.out.(s3.(s5)); //true

.out.(s3 == s6); // false

解析:

s1 = “123”;先是在字符串常量池创建了一个字符串常量“123”,“123”常量是有地址值,地址值赋值给s1。接着声明 s2=“123”,由于s1已经在方法区的常量池创建字符串常量”123″,进入常量池规则:如果常量池中没有这个常量,就创建一个,如果有就不再创建了,故直接把常量”123″的地址值赋值给s2,所以s1==s2为true。

由于类重写了方法,s1.(s2)比较的是字符串的内容,s1和s2的内容都是”123″,故s1.(s2)为true。

s3创建了一个新的字符串”1234″,s4是两个新的字符串”12″和”34″通过”+“符号连接所得,根据Java中常量优化机制, “12” 和”34″两个字符串常量在编译期就连接创建了字符串”1234”,由于字符串”1234″在常量池中存在,故直接把”1234″在常量池的地址赋值给s4,所以s3==s4为true。

s5是由一个变量s1连接一个新的字符串”4″,首先会在常量池创建字符串”4″,然后进行”+“操作,根据字符串的串联规则,s5会在堆内存中创建(或)对象,通过方法拼接s1和字符串常量”4”,此时拼接成的字符串”1234″是(或)类型的对象,通过调用方法转成对象”1234″,所以s5此时实际指向的是堆内存中的”1234″对象,堆内存中对象的地址和常量池中对象的地址不一致,故s3==s5为false。

看下JDK8的API文档里的解释:

Java语言为字符串连接运算符(+)提供特殊支持,并为其他对象转换为字符串。字符串连接是通过 (或 )类及其方法实现的。字符串转换是通过方法来实现,由下式定义和继承由在Java中的所有类。有关字符串连接和转换的其他信息,请参阅,Joy 和,Java 语言规范。

不管是常量池还是堆,只要是使用比较字符串,都是比较字符串的内容,所以s3.(s5)为true。

Java常量优化机制:给一个变量赋值,如果等于号的右边是常量,并且没有一个变量,那么就会在编译阶段计算该表达式的结果,然后判断该表达式的结果是否在左边类型所表示范围内,如果在,那么就赋值成功,如果不在,那么就赋值失败。但是注意如果一旦有变量参与表达式,那么就不会有编译期间的常量优化机制。

s6 = new (“1234”);在堆内存创建一个字符串对象,s6指向这个堆内存的对象地址,而s3指向的是字符串常量池的”1234″对象的地址,故s3==s6为false。

string[]_string类型_string[]

5.2 创建多少个字符串对象?

s0 = “123”;

s1 = new (“123”);

s2 = new (“1” + “2”);

s3 = new (“12”) + “3”;

解析:

字符串常量池对象:“123”,1个;

共1个。

字符串常量池对象:“123”,1个;

堆对象:new (“123”),1个;

共2个。

字符串常量池对象:“12”,1个(Jvm在编译期做了优化,“1” + “2”合并成了 “12”);

堆对象:new (“12”),1个

共2个。

由于s2涉及字符串合并,我们通过命令看下字节码信息:

javac .java //编译源文件得到class文件

javap -c .class // 查看编译结果

得到字节码信息如下:

string[]_string类型_string[]

可以看到,包括在内,共创建了4个对象,字符串”12″和字符串”3″是分开创建的,所以共创建了3个字符串对象。

总结:

new ()是在堆内存创建新的字符串对象,其构造参数中可传入字符串,此字符串一般会在常量池中先创建出来,new ()创建的字符串是参数字符串的副本,看下API中关于构造器的解释:

( )

初始化新创建的对象,使其表示与参数相同的字符序列;换句话说,新创建的字符串是参数字符串的副本。

所以new ()的方式创建字符串百分百会产生一个新的字符串对象,而类似于”123″这样的字符串对象则需要在创建之前看常量池中有没有,有的话就不创建,没有则创建新的对象。 “+”操作符连接字符串常量的时候会在编译期直接生成连接后的字符串,若该字符串在常量池已经存在,则不会创建新的字符串;连接变量的话则涉及等字符串构建器的创建,会在堆内存生成新的字符串对象。

———END———
限 时 特 惠: 本站每日持续更新海量各大内部创业教程,永久会员只需109元,全站资源免费下载 点击查看详情
站 长 微 信: nanadh666

声明:1、本内容转载于网络,版权归原作者所有!2、本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。3、本内容若侵犯到你的版权利益,请联系我们,会尽快给予删除处理!