String、StringBuffer、StringBuilder有什么区别?

《Java核心技术面试精讲–杨晓峰》学习笔记目录

String

String的创建机理
  • 由于String在Java世界中使用过于频繁,Java为了避免在一个系统中产生大量的String对象,引入了字符串常量池
  • 字符串常量池运行机制:创建一个字符串时,首先检查池中是否有值相同的字符串对象,如果有则不需要创建直接从池中刚查找到的对象引用;如果没有则新建字符串对象,返回对象引用,并且将新创建的对象放入池中。但是,通过new方法创建的String对象是不检查字符串池的,而是直接在堆区或栈区创建一个新的对象,也不会把对象放入池中。上述原则只适用于通过直接量给String对象引用赋值的情况。

举例:String str1 = “123”; //通过直接量赋值方式,放入字符串常量池
String str2 = new String(“123”);//通过new方式赋值方式,不放入字符串常量池

注意:String提供了intern()方法。调用该方法时,如果常量池中包括了一个等于此String对象的字符串(由equals方法确定),则返回池中的字符串。否则,将此String对象添加到池中,并且返回此池中对象的引用。

代码示例

代码网址:https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html

jdk7 常量池存储的是对对象的引用
//在常量池中生成了一个对象“1”,堆中生成了一个对象s =“1”。
String s = new String("1");
//堆中s 指向常量池中的“1”
s.intern();
//生成对象s2指向常量池中的对象“1”
String s2 = "1";
//s 和 s2 地址不同
System.out.println(s == s2);

//常量池中生成一个对象“1”,并在堆中创建一个对象s3=“11”。常量池中不存在“11”
String s3 = new String("1") + new String("1");
//常量池中生成一个引用对象“11”,常量池对s3的引用
s3.intern();
//常量池中存在“11”,s4 生成对常量池中“11”的引用,也就是对s3的引用。
String s4 = "11";
//s3与s4地址相同。
System.out.println(s3 == s4);
String的特性
  • 不可变。它是典型的 Immutable 类,被声明成为 final class,所有属性也都是 final 的,由于不可变,Immutable 对象在拷贝时不需要额外复制数据。也由于它的不可变性,任何对字符串的修改,都会产生新的 String 对象。不可变的主要作用在于当一个对象需要被多线程共享,并且访问频繁时,可以省略同步和锁等待的时间,从而大幅度提高系统性能。不可变模式是一个可以提高多线程程序的性能,降低多线程程序复杂度的设计模式。
    应用场景
    在字符串内容不经常发生变化的业务场景优先使用String类。例如:常量声明、少量的字符串拼接操作等。如果有大量的字符串内容拼接,避免使用String与String之间的“+”操作,因为这样会产生大量无用的中间对象,耗费空间且执行效率低下(新建对象、回收对象花费大量时间)。

  • 针对常量池的优化。当2个String对象拥有相同的值时,他们只引用常量池中的同一个拷贝。当同一个字符串反复出现时,这个技术可以大幅度节省内存空间。

StringBuffer/StringBuilder

  • StringBuffer 和 StringBuilder 都实现了 AbstractStringBuilder 抽象类,拥有几乎一致对外提供的调用接口;
  • 其底层在内存中的存储方式与String相同,都是以一个有序的字符序列(char类型的数组)进行存储,不同点是StringBuffer/StringBuilder对象的值是可以改变的,并且值改变以后,对象引用不会发生改变;
  • 两者对象在构造过程中,首先按照默认大小申请一个字符数组,由于会不断加入新数据,当超过默认大小后,会创建一个先前两倍大的数组,并将原先的数组内容复制过来,再丢弃旧的数组。因此,对于较大对象的扩容会涉及大量的内存复制操作,如果能够预先评估大小,可提升性能。

注意:StringBuffer是线程安全的,但是StringBuilder是线程不安全的。可参看Java标准类库的源代码,StringBuffer类中方法定义前面都会有synchronize关键字。为此,StringBuffer的性能要远低于StringBuilder。
应用场景:

  1. 在频繁进行字符串的运算(如拼接、替换、删除等),并且运行在多线程环境下,建议使用StringBuffer,例如XML解析、HTTP参数解析与封装
  2. 在频繁进行字符串的运算(如拼接、替换、删除等),并且运行在单线程环境下,建议使用StringBuilder,例如SQL语句拼装、JSON封装等

补充

synchronized
  • synchronized是Java中的关键字,是一种同步锁。当一个线程访问被synchronized修饰的内容时,其他线程将不能访问。

它修饰的对象有以下几种:

  1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
  3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
  4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。

注意:
5. synchronized关键字不能继承。
6. 在定义接口方法时不能使用synchronized关键字。
7. 构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。
8. 实现同步是要很大的系统开销作为代价的,甚至可 能造成死锁,所以尽量避免无谓的同步控制。

==,equals,hashCode
  • 对于基本类型,==equals 方法完全一致
  • 对于引用类型,对象中如果不重写equals方法,eqluas比较的是栈中的地址值,重写后比较的是堆中的对象。
  • hashCode()方法返回的就是一个数值,其目的是生成一个hash码,Object类提供的默认实现确实保证每个对象的hash码不同。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值