浅谈java中的String

Java中的String类型不属于八大基本数据类型,而是一个引用数据类型,所以在定义一个String对象的时候如果不直接赋值给这个对象,它的默认值就是null。我们要怎么理解String类型的不可变,在JDK源码中String这个类的value方法被final关键字修饰,导致String里的值不可以被修改。

在这里插入图片描述

也就是说双引号括起来的String对象,从出生到死亡,都无法发生变化。
对于任意一个String对象,都由value[]和hash组成,例如:String str = “hello”;
在这里插入图片描述

当我们直接使用双引号括起来的字符串初始化String对象时,该字符串会被存储在“方法区”的“字符串常量池”当中。而上图中的hash值就是用于查找字符串在常量池中的位置。

字符串常量池主要用于存储字符串常量,本质是一个哈希表(StringTable)。从JDK1.8开始,这个哈希表就存放在了堆中

为什么会将字符串放在常量区?
因为字符串在实际的开发中使用太频繁。为了执行效率,所以把字符串放到了方法区的字符串常量池当中。


从这里开始,将从内存的角度分析问题

例1:

在这里插入图片描述
这里输出false的原因大家应该都知道,因为str1和str2的引用指向不,那底层是如何实现的呢?
通过内存图可以清晰明了看起问题本质:

在这里插入图片描述

String str1 = “hello”,首先会创建一个字符数组,用来存放"hello",然后再开辟一块空间(假如为ptr1),指向这个数组,最后会在栈上用一个引用去指向ptr1。

String str2 = new String(“hello”),首先会查看StringTable是否存在"hello",这里已经存在,会开辟一块空间(假如为ptr2),将ptr1中的内容拷贝到ptr2中,那么ptr2就指向了"hello"数组,最后在栈上用一个引用去指向ptr2

JDK源码:
在这里插入图片描述
在这里插入图片描述

JDK源码的大致含义:当我们用一个字符串去实例化一个String对象时,会将这个字符串通过hash函数得到一个hash值,然后在StringTable中找,如果存在就不需要重新再创建,如果不存在就需要创建这个字符串


例2:

public class Test6 {
    public static void main(String[] args) {
        String str1 = "hello";
        String str2 = "hello";
        System.out.println(str1 == str2);
    }
}

在这里插入图片描述

在这里插入图片描述
因为hello在SringTable中已经存在,所以在栈上引用str2直接指向了0x3344。而例1中str2是new出来的,所以需要在堆上开辟空间,然后这块空间再指向0x3344


例3:

public class Test6 {
    public static void main(String[] args) {
        String str1 = "hello";
        String str2 = "he" + "llo";
        System.out.println(str1 == str2);
    }
}

在这里插入图片描述
这里的结果依然是true
因为“he”和“llo”都是常量,代码在编译的时候就已经确定了最终结果是常量字符串“hello”

如果“he”或者“llo”有一个是变量,最终结果为false,例如:
在这里插入图片描述


例4:

public class Test6 {
    public static void main(String[] args) {
        String str1 = "11";
        String str2 = new String("1") + new String("1");
        System.out.println(str1 == str2);
    }
}

在这里插入图片描述

还是画图分析:

在这里插入图片描述

当new string(“1”)时,产生的匿名对象,也会将"1"放入到字符串常量池中,当两个"1"相加,就会形成一个StringBuilder对象, 里面存放"11",当这个StringBuilder对象要转换成String对象时,需要调用to_string()方法,这个方法并不会判断"11"是否在字符串常量池中,而是直接创建新的字符串。也就是说to_string并不会入池,所以最终的结果为false

例5:

public class Test6 {
    public static void main(String[] args) {
        String str2 = new String("1") + new String("1");
        String str1 = "11";
        System.out.println(str1 == str2);
    }
}

在这里插入图片描述

这里的结果也为false,跟例4的原因是一样的。这里就不画图了。

但是只要对这个代码稍作修改,结果就为true

在这里插入图片描述

intern()这个方法叫做手动入池(将String放在字符串常量池中)

本来str2(“11”)是没有入池,但是调用了intern()方法后,“11"就被放进了常量池中。当定义str1时,发现常量池中有"11”,就不需要再创建字符串"11",直接赋值,就和例2一样,最终str1和str2引用的是同一块空间,所以为true

注意:使用intern()方法时,如果常量池中没有才会入池,有的话就不会入池


equals()方法进行比较String

在这里插入图片描述

以上是equals的源码,是按照逐一比较字符的方式。

如果String没有重新equals方法的话,默认是比较地址
在这里插入图片描述

任何一个引用去调用方法,都要预防空指针异常,equals方法也不例外,例如:
在这里插入图片描述

除此之外,下面两种字符串引用有着本质的区别

String str1 = null;
String str2 = "";

str1这个引用,不指向任何对象
str2这个引用,指向的字符串是空的

可以使用length方法验证一下:
在这里插入图片描述

String作为参数,在使用之前也需要进行判断:

public static void func(String str) {
    //正确写法
    if (str == null || str.length() == 0) {
        return;
    }
    //错误写法
    if(str.length() == 0 || str == null) {
        return;
    }
}

第二种写法,先判断str的长度可能会存在空指针异常


尽量少用String去拼接字符串

public class Test7 {
    public static void main(String[] args) {
        String str = "abcde";
        for(int i = 0; i < 10; ++i) {
            str += i;
        }
        System.out.println(str);
    }
}

这段代码,大家可能认为平平无奇,就是一个单纯的字符串拼接。但是每一次拼接都会产生临时对象,可以看一下java的汇编代码:

在这里插入图片描述
从汇编代码可以看出,每一次的for循环,都会创建一个StringBuilder对象,最后再调用String方法。这样是非常浪费内存和时间的,虽说循环10次影响比较小,但是循环10w次呢?那开销就很大了,所以,由于String是不可变的,所以应当少用String去拼接字符串。


StringBuffer和StringBuilder

首先来回顾下String类的特点:
任何的字符串常量都是String对象,而且String的常量一旦声明不可改变,如果改变对象内容,改变的是其引用的指向而已。
通常来讲String的操作比较简单,但是由于String的不可更改特性,为了方便字符串的修改,提供StringBuffer和StringBuilder类。
StringBuffer 和 StringBuilder 大部分功能是相同的,主要介绍 StringBuffer

public class Test8 {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        sb.append("fff");
        System.out.println(sb);
        sb.append("lll");
        System.out.println(sb);
    }
}

在这里插入图片描述

String是无法更改的,而StringBuilder对象时可以更改的,这里调用了两次append方法,都是在原有对象进行添加操作,并且有重新创建新的对象。

在这里插入图片描述

前面也了解了,在循环中用String去拼接字符串,会产生临时对象StringBuilder,而StringBuilder对象又是可以更改的,所以优先使用StringBuilder对象去拼接字符串


StringBuffer和StringBuilder的区别

StringBuilder中的部分append:
在这里插入图片描述

StringBuffer中的部分append:
在这里插入图片描述

通过对比,发现StringBuffer中的append方法是多了synchronized,也就是说StringBuffer是线程安全的,而StringBuilder不是线程安全的。
因此StringBuilder用于单线程,而StringBuffer用于多线程。

String、StringBuffer、StringBuilder的区别

  • String的内容不可修改,StringBuffer与StringBuilder的内容可以修改.
  • StringBuffer与StringBuilder大部分功能是相似的
  • StringBuffer采用同步处理,属于线程安全操作;而StringBuilder未采用同步处理,属于线程不安全操作
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值