String类
String类不会在原有的数据上修改(添加或删除),而是重新开辟一块堆内存放入修改的内容
修改String类不会在原有的数据上修改(添加或删除),因为其内部的char[]是被final修饰的,而是重新开辟一块堆内存放入修改的内容,原有引用重新指向这一块堆内存。应该使用StringBuilder类的append()方法,它会在堆存的原有的数据上修改,不会开辟新的堆内存空间。
+号连接符连接两个s = " A" + “B”,会在编译阶段优化成s = “AB”,连接其他的会创建新的StringBuilder对象append拼接。
那如果用+号连接符修改呢?+号连接符连接两个" "
(如:s = " A" + “B”),则会在编译阶段:java源文件编译成.class文件时,会被优化成s = “AB”,放入常量池,这个可以,但这种灵活度太差,都是写死的。
若连接其他情况(如:int i = 10; s = “abc” + i ), 程序运行时,会创建一个新的StringBuilder对象,append(“abc”).append(String.valueOf(i))之后,在toString()转换为String类返回
/**
* 测试代码
*/
public class Test {
public static void main(String[] args) {
int i = 10;
String s = "abc";
System.out.println(s + i);
}
}
/**
* 反编译后
*/
public class Test {
public static void main(String args[]) { //删除了默认构造函数和字节码
byte byte0 = 10;
String s = "abc";
System.out.println((new StringBuilder()).append(s).append(byte0).toString());
}
}
String s1 = “A”;
String s2 = “B”
s = s1 + s2
也会创建一个StringBuilder对象,编译阶段不会优化。
String类常量池
为了减少内存开销,在创建字符串常量时,会先去字符串常量池中检查是否存有这个字符串常量,如果有,直接返回字符串常量池中的字符串引用(即地址),如果没有,则在字符串常量池中创建一个字符串常量,并返回字符串引用。
JDK7后,常量池被放在了堆内存中,
/**
* 字符串常量池中的字符串只存在一份!
* 运行结果为true
*/
String s1 = "hello world!";
String s2 = "hello world!";
System.out.println(s1 == s2);
/**
* 运行结果为true false
*/
String s1 = "AB";
String s2 = "AB";
String s3 = new String("AB");
System.out.println(s1 == s2);
System.out.println(s1 == s3);
String对象的intern()方法会首先在字符串常量中找有没有该字符串常量,如果有不在创建新的字符串常量,直接返回字符串常量在字符串常量池中的地址,没有则创建一份,并返回地址值。
static final int MAX = 1000 * 10000;
static final String[] arr = new String[MAX];
public static void main(String[] args) throws Exception {
Integer[] DB_DATA = new Integer[10];
Random random = new Random(10 * 10000);
for (int i = 0; i < DB_DATA.length; i++) {
DB_DATA[i] = random.nextInt();
}
long t = System.currentTimeMillis();
for (int i = 0; i < MAX; i++) {
//arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length]));
arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length])).intern();
}
System.out.println((System.currentTimeMillis() - t) + "ms");
System.gc();
}
arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length])).intern();使用intern()会减少很多内存,它不会创建1000 * 10000个字符串常量,因为有相同的。
如果不使用arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length])),则会创建1000 * 10000个String对象,但注意:Sting()对象也会指向字符串常量池中的字符串常量。
String s = new String(“hello”),创建了几个对象?两个或者一个,若字符串常量"hello"已存在于常量池中,则创建new String()对象;若字符串常量"hello"不存在于常量池中,则会先在常量池中创建字符串常量"hello",再创建new String()对象。
测试题:
public static void main(String[] args) {
String s1 = "AB";
String s2 = new String("AB");
String s3 = "A";
String s4 = "B";
String s5 = "A" + "B";
String s6 = s3 + s4;
System.out.println(s1 == s2);
System.out.println(s1 == s5);
System.out.println(s1 == s6);
System.out.println(s1 == s6.intern());
System.out.println(s2 == s2.intern());
System.out.println(s2.intern() == s6.intern());
}
false
true
false
true
false
true
解析:真正理解此题目需要清楚以下三点
1)直接使用双引号声明出来的String对象会直接存储在常量池中;
2)String对象的intern方法会得到字符串对象在常量池中对应的引用,如果常量池中没有对应的字符串,则该字符串将被添加到常量池中,然后返回常量池中字符串的引用;
3) 字符串的+操作其本质是创建了StringBuilder对象进行append操作,然后将拼接后的StringBuilder对象用toString方法处理成String对象,这一点可以用javap -c命令获得class文件对应的JVM字节码指令就可以看出来。
https://blog.csdn.net/ifwinds/article/details/80849184