对String字符串类型的重新思考

相信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()让字符串在编译时就加入到常量池中发挥常量池 的缓存功能

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值