目录
二、StringBuilder类和StringBuffer类
2.StringBuilder类和StringBuffer类的区别
三、String类、StringBuilder类和StringBuffer类异同
一、String类
1.String类底层代码
//String底层代码 public final class String implements java.io.Serializable, Comparable<String>, CharSequence, Constable, ConstantDesc { private final byte[] value; }
由上述代码可以得出:
- String类是由final修饰的类,因而不能被其他类继承;
- String类底层是通过byte类型的数组实现的,也被final修饰,所以字符串的值在创建之后就不可以被修改。
2.String类的构造方法
public String() | 创建一个空白字符串对象,不含有任何内容 |
public String(char[] chs) | 根据字符数组的内容,来创建字符串对象 |
public String(String original) | 根据传入的字符串内容,来创建字符串对象 |
String s = “abc” | 直接赋值的方式创建字符串对象,内容就是abc |
3.String类字符串特点
- java 程序中所有的双引号字符串,都是String类型的对象
- 字符串不可变,他们的值在创建后不能被更改
- 虽然String的值是不可变的,但是他们可以共享
注意:所有字符串常量都会存在方法区的字符串常量池中
使用字符串字面量创建对象时,系统会检查该字符串是否在字符串常量池(方法区)存在,如果不存在,会创建,如果存在,不会重新创建而是直接使用。
4.理解String类值不可变性
class Demo { public static void main(String[] args) { String str1 = "hello"; System.out.println(str1); String str2 = "hello"; str1 = "world"; System.out.println(str1); System.out.println(str2); } }
内存图:
解释:字符串str1和str2内容相同,都指向字符串常量池中的同一个地址001,它们共享字符串“hello”;str1值发生改变,系统会重新分配内存地址进行赋值,而原先的内存地址里面的值并没有发生变化,这就是string字符串的不变性。
5.创建字符串对象的区别对比
- 构造方法创建
通过 new 创建的字符串对象,每一次 new 都会申请一个内存空间,虽然内容相同,但是地址值不同。
- 直接赋值方式创建
以“ ”方式给出的字符串,只要字符序列相同(顺序和大小写),无论在程序代码中出现几次,JVM 都只会建立一个 String 对象,并在字符串常量池中维护。
面试题:使用String str = new String("hello")方式创建对象,一共创建几个对象?
答:一共会创建两个对象,一个是使用new创建的对象,存储在堆中;另一个是常量对象“hello”,存储在字符串常量池中。
6.String类不同拼接方式区别
class Demo { public static void main(String[] args) { String str1 = "hello"; String str2 = "world"; String str4 = "helloworld"; String str3 = "hello"+"world";//常量优化机制 String str5 = "hello"+str2; String str6 = str1+"world"; String str7 = str1+str2; System.out.println(str4==str3);//true System.out.println(str4==str5);//false System.out.println(str4==str6);//false System.out.println(str4==str7);//false } }
总结:
- 常量优化机制,"hello"和"world"是字符串常量,在编译时,会直接拼接成字串"helloworld" ,因而和常量池中已创建的"helloworld"是同一个地址;
- 当变量与字面量或变量与变量进行拼接时系统会在堆中创建一个StringBuilder对象,调用append()方法进行拼接,最后调用toString()方法转换成String对象,因str5、str6和str7都指向的是堆内存中String对象的地址值。
面试题:String s = new String(“1”)+ new String(“1”)创建了几个字符串对象?
答:创建了5个字符串对象。字符串常量池中创建字符串"1"对象,堆内存中开辟两个内存地址创建String对象,之后进行字符串拼接,系统会在堆中创建一个StringBuilder对象,调用append()方法进行拼接,最后调用toString()方法转换成String对象。
二、StringBuilder类和StringBuffer类
1.底层代码
public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, Comparable<StringBuilder>, CharSequence { @HotSpotIntrinsicCandidate public StringBuilder() { super(16); } @HotSpotIntrinsicCandidate public StringBuilder(int capacity) { super(capacity); } } private int newCapacity(int minCapacity) { // overflow-conscious code int oldCapacity = value.length >> coder; int newCapacity = (oldCapacity << 1) + 2; if (newCapacity - minCapacity < 0) { newCapacity = minCapacity; } int SAFE_BOUND = MAX_ARRAY_SIZE >> coder; return (newCapacity <= 0 || SAFE_BOUND - newCapacity < 0) ? hugeCapacity(minCapacity) : newCapacity; }
- 底层是用一个数组来存储字符串的值,默认长度为16,即一个空的StringBuiler和StringBuffer对象数组长度为16;
- 若采用有参构造函数创建StringBuilder和StringBuffer对象,数组的长度不再是16,则根据当前对象的值来决定数组的长度,即长度为当前对象的值的长度+16。如果数组中的字符串的个数超出数组的长度,则会对数组进行扩容;
- 扩容也就是创建一个新的byte数组,将现有的容量扩大一倍再加上2,扩容完成之后,将原数组的内容复制到新数组,最后引用指向新的byte数组。
2.StringBuilder类和StringBuffer类的区别
线程安全是指在多线程环境下,多个线程同时访问同一资源,不会产生意外结果或导致数据出错的状态。
常用的线程安全方法:加锁。通过加锁机制控制对共享资源的访问,使得每次只有一个线程能够访问共享资源。Java中提供了synchronized关键字来实现加锁功能。
StringBuilder类:线程不安全,单线程。
StringBuffer类:线程安全,多线程。
StringBuffer类几乎所有公开的方法都是用synchronized修饰的,而StringBuilder没有。
三、String类、StringBuilder类和StringBuffer类异同
相同点:底层都是通过byte数组实现的
不同点:
1.String类对象创建之后,它的值不能修改,若要修改需要开辟新的内存空间来存储修改之后的对象;而StringBuilder类和StringBuffer类对象的值可以修改,不需要开辟新的内存空间;
2.StringBuffer类几乎所有公开的方法都是用synchronized修饰的,线程比较安全,在多线程系统中可以保证数据同步,只是效率低,而StringBuilder没有synchronized修饰,线程不安全,适合单线程。
总结:在实际开发中需要对字符串进行频繁的修改的,不使用String类;若要考虑线程安全的时候使用StringBuffer类;若不需要考虑线程安全,选择使用StringBuilder类。