相信String类型应该是大家开发的时候使用的最多的一种类型,在初学Java的时候总是认为字符串也是一种基本数据类型,到后来才知道String是对象类型
让我们来看看文档中是如何定义字符串类型的数据的
文档中很清楚的表明字符串在创建后值无法修改,也就是说
String s1="abc";
System.out.println(s1.hashCode());//`6354
s1=s1+"aa";
System.out.println(s1.hashCode());//92599298
我们可以发现在s1改变后就会生成一个新的对象,也进一步证明了字符串在创建就不能修改了,那么为什么Java要把字符串定义为不可修改呢?
首先我们知道在开发中我们对字符串的使用是很频繁的,因为字符串是对象,那么每创建一次字符串就要创建一个对象,就要在堆上分配内存,这种操作频繁进行会影响性能(所以我们也可以理解为什么Java中要有基本数据类型的存在,基本数据类型只在栈中分配内存,开销很小,而在堆中创建对象会有较大的开销)所以在jvm中存在字符串池,创建的字符串对象会放在池中,如果创建一个相同字面量的字符串,那么会直接在池中返回,而不用去堆中创建(可以理解为字符串池就像一个缓存)
String s1=“abc”;
String s2=“abc”
System.out.println(s1==s2); true
但上述的是使用声明的方式创建字符串,我们知道还可以通过String的有参构造创建一个字符串 String s1=new String(“abc”)
String s1="abc";
String s2= new String("abc")
System.out.println(s1==s2); //false
因为new了一个字符串对象所以会直接去堆中创建对象,而不去看池,s1,s2保存的是字符串的引用,两个不同的对象,地址引用一定不会相同 但是若使用equals()方法会返回true,因为String类重写了equals()方法
查看源码我们可以发现如果传入是String类型,会把它转化为数组,逐一比较
回到问题,为什么要将字符串设置为不可变呢?
不可变对象天生就是线程安全的,所有不需要进行线程同步处理
最后再来看看String类一个不怎么常用的方法:
intern()
这个方法平时用的很少,但在面试中经常出现
intern()有两个作用,
第一个是将字符串字面量放入常量池(如果池没有的话)
第二个是返回这个常量的引用。
首先看以下的代码
String s1 = "aa";
String s2 = "bb";
String s3 = s1 + s2;
String s4 = "aa" + "bb";
下面是反编译的结果
String s1 = "aa";
String s2 = "bb";
String s3 = (new StringBuilder())
.append(s1)
.append(s2)
.toString();
String s4 = "aabb";
我们可以发现s4显示直接拼接,而s3则调用了StringBuilder的append()方法
为什么呢?
仔细对比上面的式子我们可以看出s4由字符串直接相加而s3由两个变量相加,究其原因,是因为常量池要保存的是已确定的字面量值。也就是说,对于字符串的拼接,纯字面量和字面量的拼接,会把拼接结果作为常量保存到字符串,如果在字符串拼接中,有一个参数是非字面量,而是一个变量的话,整个拼接操作会被编译成StringBuilder.append,这种情况编译器是无法知道其确定值的。只有在运行期才能确定。那么,有了这个特性了,intern就有用武之地了。那就是很多时候,我们在程序中用到的字符串是只有在运行期才能确定的,在编译期是无法确定的,那么也就没办法在编译期被加入到常量池中。
这时候,对于那种可能经常使用的字符串,使用intern进行定义,每次JVM运行到这段代码的时候,就会直接把常量池中该字面值的引用返回,这样就可以减少大量字符串对象的创建了。
引用大神的代码
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();
}
for (int i = 0; i < MAX; i++) {
arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length])).intern();
}
}
这里会有大量的重复字符串出现,但在编译时无法得知,所以不能加入到常量池的缓存中,可以使用intern()让字符串在编译时就加入到常量池中发挥常量池 的缓存功能