一、==运算符和equals之间的区别:
- 对于基本数据类型,==比较的是值是否相同
- 对于引用数据类型,==比较的是内存地址是否相同
- equals仅比较引用指向的内容(值)是否相同
二、字符串的不可变性
String的对象一旦被创建,则不能修改,是不可变的。所谓的修改其实是创建了新的对象,引用指向新的对象。
例:
String s = "abc";// (1) 创建新的对象"abc" s为指向对象abc的引用
System.out.println("s = " + s);//abc
s = "123"; (2) //创建新的对象"123" 引用s指向新的对象123
System.out.println("s = " + s);//123
执行(1)处代码后,首先会在方法区的运行时常量池创建一个新的对象"abc",其次在Java栈中创建一个对象的引用s,并让s指向"abc";
执行(2)处代码后,首先会在方法区的运行时常量池创建一个新的对象"123",然后让Java栈中创建的的引用s重新指向“123”,而原来的对象"abc"还在内存中,并没有改变。
1. String为什么不可变
- String类用final关键字修饰,不可继承
- 但是String对象的引用本身是可以改变指向其他的对象的
- final修饰的char[]数组保证了char数组的引用不可变,内容可变。但String内部并不提供方法来完成这一操作,所以String的不可变是基于代码封装和访问控制的。
例:java.lang.String类代码前三行
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
/** String本质是个char数组. 而且用final关键字修饰.*/
private final char value[];
}
String不可变是为了线程的安全!!!
2. String, StringBuffer and StringBuilder的区别:
可变性:
- String 不可变
- StringBuffer 和 StringBuilder 可变
线程安全:
- String 不可变,因此是线程安全的
- StringBuffer 是线程安全的,内部使用 synchronized 进行同步
- StringBuilder 不是线程安全的
适用场景:
- 少量的字符串数据,可以考虑String
- 单线程场景下操作大量字符串数据,考虑使用StringBuilder
- 多线程场景下操作大量字符串数据,考虑使用StringBuffer
3. StringBuffer
StringBuffer提供append和add方法,可以将字符串添加到已有序列的末尾或指定位置
- append方法
String s = "abc";
String s1 = s + s;
System.out.println("s1 = " + s1);
String类型在使用 “+” 运算符时,首先把“abc”封装成stringbuilder,其次调用append方法,最后再用tostring返回。所以当大量使用字符串加法时,会大量地生成stringbuilder实例,这是十分浪费的,这种时候应该用stringbuilder来代替string。
三、String.intern()
例1:
String s = new String("1");
/* JDK1.6及以下:首先在字符串常量池和堆中创建相应对象"1",s为指向堆中的引用
JDK1.7及以上:首先在字符串常量池和堆中创建相应对象"1",s为指向堆中的引用
/*
s.intern();
/* JVM判断字符串常量池中是否存在对象"1",若存在,引用s依旧指向堆中;
若不存在
JDK1.6及以下:将堆中相应对象复制到字符串常量池中,引用s.intern()指向常量池
JDK1.7及以上:将堆相应对象的地址放到字符串常量池中,引用s.intern()指向常量池
/*
String s2 = "1";
/* JVM判断字符串常量池是否存在对象"1",
若存在,引用s2直接指向常量池(JDK1.6及以下:引用s2指向常量池中的对象"1";
JDK1.7及以上:引用s2指向常量池中对象"1"在堆中的地址,相当于引用s2指向堆);
若不存在,在常量池中创建相应对象"1",引用s指向相应常量池
/*
System.out.println(s == s2); // 无论JDK1.6及以下还是JDK1.7及以上输出结果为false
例2:
String s3 = new String("1") + new String("1");
/* JDK1.6及以下:首先在字符串常量池中创建对象"1",其次在堆中分别创建两个String对象"1",最后在堆中通过.append方法创建对象"11",引用s3指向对象"11"
JDK1.7及以上:首先在字符串常量池中创建对象"1",其次在堆中分别创建两个String对象"1",最后在堆中通过.append方法创建对象"11",引用s3指向对象"11"
/*
s3.intern();
/* JVM判断字符串常量池中是否存在对象"11",若存在,引用s3依旧指向堆中;
若不存在
JDK1.6及以下:将堆中相应对象复制到字符串常量池中,引用s3.intern()指向常量池
JDK1.7及以上:将堆相应对象的地址放到字符串常量池中,引用s3.intern()指向常量池(这时的引用s3相当于指向堆)
/*
String s4 = "11";
/* JVM判断字符串常量池是否存在对象"11",
若存在,引用s2直接指向常量池(JDK1.6及以下:引用s4指向常量池中的对象"11";
JDK1.7及以上:引用s4指向常量池中对象"11"在堆中的地址,相当于引用s4指向堆);
若不存在,在常量池中创建相应对象"11",引用s4指向相应常量池
/*
System.out.println(s3 == s4);
/* JDK1.6及以下 false
JDK1.7及以上 true
/*
图1:JDK1.6和1.7的例1都为图1所示
图2:JDK1.6为黑色线条 所示;JDK1.7为红色线条所示
搞懂String.intern()的关键就是对于new String(),其会分别在常量池和堆中分别建立新的对象引用,然后将引用指向堆中;然后s.intern()会将堆中的内容或者地址放置在常量池中(若常量池中不存在该对象),反之引用不变;而常量字符串的赋值如String s2 = "1"直接放入常量池中;
对于如new String(“1”)+new String(“1”)的字符串,其首先在常量池中创建对象“1”,其次在堆中创建两个new String(“1”),最后通过.append方法在堆中创建“11”(此时常量池中无对象“11”)。