Java共享字符串_JAVA-字符串的构建

本文所有内容基于以下几个要点

String不可变

String重载了"+"

对象进行字符串相加会调用toString方法

StringBuilder 和 StringBuffer

JVM 常量池

阅读完本文,你将对java字符串创建、字符串相加、jvm常量区(内存区域)有一个新的认识,提前感谢你的阅读。

String注意事项

不可变String

String类中每一个特看起来会修改String值的方法,实际上都是创建了一个权限的String对象,而最初的String对象丝毫未动

这意味着String是只读的,指向String的任何引用都不会改变它的值

不可变性会带来效率问题(字符串相加的中间对象)

String中值的保存的源码,final字符数组意味着value的不可变

/** The value is used for character storage. */

private final char value[];

字符串常量池

每个字符序列如"123"会生成一个实例放在常量池里,这个实例是不在堆里的,也不会被GC

我们在使用诸如String str = "abc"的格式定义类时,总是想当然地认为,创建了String类的对象str。但是由于字符串常量池的存在,担心陷阱!对象可能并没有被创建!而可能只是指向一个先前已经创建的对象。 只有通过new()方法才能保证每次都创建一个新的对象,因此在使用 "==" 进行对象比较时需要注意这个问题

JVM中的常量池在内存当中是以表(hashtable)的形式存在的, 对于String类型,有一张固定长度的CONSTANT_String_info表用来存储文字字符串值(注意:该表只存储文字字符串值,不存储符号引用。

常量池中保存着很多String对象,并且可以被共享使用,因此它提高了效率)。

String类有一个intern()方法,它可以访问字符串池。是扩充常量池的 一个方法。当一个String实例str调用intern()方法时,Java 查找常量池中 是否有相同的字符串常量,如果有,则返回其的引用,如果没有,则在常量池中增加一个等于str的字符串并返回它的引用;

String重载了“+”

String的“+”和“+=”是Java中仅有的两个重载过的操作符,java不允许程序员重载任何操作符

当有字符串常量进行相加时,会产生大量的中间对象,所有的这些字符串常量都会保存到 JVM 常量区,此方式产生了大量需要垃圾回收的中间对象

“abc”和mango生成一个中间变量x,x和“def”生成一个新的中间变量,以此类推

String mango = "mango";

String s = "abc" + mango + "def" + 47;

System.out.println(s);

编译器会对字符串相加自动引入StringBuilder类,因为他更加高效。(详细的分析可以在这篇文章找到 String concatenation with Java 8)

但是编译器并不知道需要创建几个StringBuilder,在循环中进行字符串相加会创建大量的StringBuilder对象,这同样需要大量的GC

String result = "";

for(int i=0;i

result += fields[i]; // 每一次循环会创建一个新的StringBuilder

}

(此处需要依次javap反编译查看JVM字节码)

但是手动创建StringBuilder却只需要一个对象就可以完成操作

StringBuilder result = new StringBuilder();

for(int i=0;i

result.append(fields[i]); // 仅有一个StringBuilder对象

}

result.toString() // get target string

(此处需要依次javap反编译查看JVM字节码)

toString & 无意识递归

无意识递归问题

发生示例如下。this在这里会被进行类型转换,由A类转换成String,但转换正是通过toString来进行的,于是发生递归调用。

public class A{

...

public String toString(){

return "toString memory address: "+ this;

}

...

}

如果期望打印对象地址应当使调用Object.toString()用 super.toString()而不是this.

StringBuilder & StringBuffer

StringBuilder允许预先指定大小,如果知道最终字符串大小有多长,那么预先指定StringBuilder的大小会避免多次重新分配缓冲

StringBuilder常用的方法:append()、toString()、delete()

StringBuffer是线程安全的,但是开销大

StringBuilder和StringBuffer均继承自AbstractStringBuilder类

AbstractStringBuilder类值的保存的源码,可见对象是可变的/**

* The value is used for character storage.

*/

char[] value;

END

何时使用"+"和StringBuilder

使用 “+” 连接字符串

少量数据

没有循环

使用StringBuilder

单线程

大量数据相加

大量循环

使用StringBuffer

多线程

同StringBuilder

更加深入的了解

需要了解JVM内存模型

需要查看String、StringBuilder、StringBuffer源码

可能需要做一些习题

可供练习的习题

public static void main(String[] args) {

/**

* 情景一:字符串池

* JAVA虚拟机(JVM)中存在着一个字符串池,其中保存着很多String对象;

* 并且可以被共享使用,因此它提高了效率。

* 由于String类是final的,它的值一经创建就不可改变。

* 字符串池由String类维护,我们可以调用intern()方法来访问字符串池。

*/

String s1 = "abc";

//↑ 在字符串池创建了一个对象

String s2 = "abc";

//↑ 字符串pool已经存在对象“abc”(共享),所以创建0个对象,累计创建一个对象

System.out.println("s1 == s2 : "+(s1==s2));

//↑ true 指向同一个对象,

System.out.println("s1.equals(s2) : " + (s1.equals(s2)));

//↑ true 值相等

//↑------------------------------------------------------over

/**

* 情景二:关于new String("")

*

*/

String s3 = new String("abc");

//↑ 创建了两个对象,一个存放在字符串池中,一个存在与堆区中;

//↑ 还有一个对象引用s3存放在栈中

String s4 = new String("abc");

//↑ 字符串池中已经存在“abc”对象,所以只在堆中创建了一个对象

System.out.println("s3 == s4 : "+(s3==s4));

//↑false s3和s4栈区的地址不同,指向堆区的不同地址;

System.out.println("s3.equals(s4) : "+(s3.equals(s4)));

//↑true s3和s4的值相同

System.out.println("s1 == s3 : "+(s1==s3));

//↑false 存放的地区多不同,一个栈区,一个堆区

System.out.println("s1.equals(s3) : "+(s1.equals(s3)));

//↑true 值相同

//↑------------------------------------------------------over

/**

* 情景三:

* 由于常量的值在编译的时候就被确定(优化)了。

* 在这里,"ab"和"cd"都是常量,因此变量str3的值在编译时就可以确定。

* 这行代码编译后的效果等同于: String str3 = "abcd";

*/

String str1 = "ab" + "cd"; //1个对象

String str11 = "abcd";

System.out.println("str1 = str11 : "+ (str1 == str11));

//↑------------------------------------------------------over

/**

* 情景四:

* 局部变量str2,str3存储的是存储两个拘留字符串对象(intern字符串对象)的地址。

*

* 第三行代码原理(str2+str3):

* 运行期JVM首先会在堆中创建一个StringBuilder类,

* 同时用str2指向的拘留字符串对象完成初始化,

* 然后调用append方法完成对str3所指向的拘留字符串的合并,

* 接着调用StringBuilder的toString()方法在堆中创建一个String对象,

* 最后将刚生成的String对象的堆地址存放在局部变量str3中。

*

* 而str5存储的是字符串池中"abcd"所对应的拘留字符串对象的地址。

* str4与str5地址当然不一样了。

*

* 内存中实际上有五个字符串对象:

* 三个拘留字符串对象、一个String对象和一个StringBuilder对象。

*/

String str2 = "ab"; //1个对象

String str3 = "cd"; //1个对象

String str4 = str2+str3;

String str5 = "abcd";

System.out.println("str4 = str5 : " + (str4==str5)); // false

//↑------------------------------------------------------over

/**

* 情景五:

* JAVA编译器对string + 基本类型/常量 是当成常量表达式直接求值来优化的。

* 运行期的两个string相加,会产生新的对象的,存储在堆(heap)中

*/

String str6 = "b";

String str7 = "a" + str6;

String str67 = "ab";

System.out.println("str7 = str67 : "+ (str7 == str67));

//↑str6为变量,在运行期才会被解析。

final String str8 = "b";

String str9 = "a" + str8;

String str89 = "ab";

System.out.println("str9 = str89 : "+ (str9 == str89));

//↑str8为常量变量,编译期会被优化

//↑------------------------------------------------------over

}

参考

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值