String类型详解(下),让你StringBuilder和StringBuffer不再迷路

目录

一,字符串的不可变性

二,字符串修改

三, StringBuilder和StringBuffer

StringBuilder的介绍

四,StringBuider与StringBuffer的区别

String、StringBuffffer、StringBuilder的区别


续上


一,字符串的不可变性

在上回String详解中我们知道了所有的截取,替换等都不是在它原本的String字符串上进行的,那为什么呢?接下来让我们继续进行探究

String是一种不可变对象,字符串中的内容是不可改变。字符串不可被修改,是因为:
1. String类在设计时就是不可改变的,String类实现描述中已经说明了

以下是来自jdk1.8中的String的部分实现:

String类型详解(下),让你StringBuilder和StringBuffer不再迷路_第1张图片

String类型详解(下),让你StringBuilder和StringBuffer不再迷路_第2张图片

String类中的字符实际保存在内部维护的value字符数组中,该图还可以看出:
1. String类被fifinal修饰,表明该类不能被继承
2. value被修饰被fifinal修饰,表明value自身的值不能改变,即不能引用其它字符数组,但是其引用空间中的内容可以修改。
2. 所有涉及到可能修改字符串内容的操作都是创建一个新对象,改变的是新对象
比如replace方法:
String类型详解(下),让你StringBuilder和StringBuffer不再迷路_第3张图片

我们看到,String所引用的对象都使用“ ”来进行,而只要是双引号所引起的对象都是常量,被放在内存中堆上的常量池

参考如下图:

String类型详解(下),让你StringBuilder和StringBuffer不再迷路_第4张图片

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

下面我们以此代码为例,逐个分析

代码走到第一个String,在常量池放入了“”引住的对象

String类型详解(下),让你StringBuilder和StringBuffer不再迷路_第5张图片

代码走到第二行,又有一个双引号,此时如果常量池有“abcde”就不会再放入该常量。

随后又new了一个Sring,于是又在堆上开辟一块内存

String类型详解(下),让你StringBuilder和StringBuffer不再迷路_第6张图片

所以此时“==”比较的是str1与str2的内存地址,由图知并不相同,所以输出的值为false。

String类型详解(下),让你StringBuilder和StringBuffer不再迷路_第7张图片

在明白这一点之后我们继续深入探究

public class Main {
    public static void main(String[] args) {
        String str1 = "abcde";
        String str2 = new String("abcde");
        String str3 = "abcde";
        System.out.println(str1 == str3);
    }
}

此时代码多了一个直接赋值的str3,此时str1和str3会不会相等呢?

同样的道理,程序走到str3处时读取到一个双引号于是在常量池中寻找,发现已有了该常量,所以不再新增,而这是并没有new一个新对象,所以并没有在堆上开辟新内存

String类型详解(下),让你StringBuilder和StringBuffer不再迷路_第8张图片

所以这时二者地址便相同,输出的值也会是true

String类型详解(下),让你StringBuilder和StringBuffer不再迷路_第9张图片

总结:双引号引起来的值就存放在字符串常量池当中。如果有,就不存储,直接返回字符串常量池的对象即可。

(字符串常量池底层是一个哈希表,叫做StringTable,是用c/c++编写的)

纠正 】网上有些人说:字符串不可改变是因为其内部保存数字的数组被final修饰了,因此不能改变。 这种说法是错误的,不是因为String类自身,或者其内部被value修饰而不能改变。
fifinal 修饰类表明该类不想被继承, fifinal 修饰引用类型表明该引用变量不能引用其他对象,但是其引用对象中的内 容是可以修改的

public static void main(String[] args) {

fifinal int array[] = {1,2,3,4,5};

array[0] = 100;

System.out.println(Arrays.toString(array));

// array = new int[]{4,5,6}; // 编译报错:Error:(19, 9) java: 无法为最终变量array分配值

}

例:
public class Main {
    public static void main(String[] args) {
        String str = "abcd";
        System.out.println(str);
    }
}

String类型详解(下),让你StringBuilder和StringBuffer不再迷路_第10张图片

即使String引用的对象被放入常量池,但其中也有value与haxi

String类型详解(下),让你StringBuilder和StringBuffer不再迷路_第11张图片

也就是说这里我们有一个String对象,其有一个value与haxi

String类型详解(下),让你StringBuilder和StringBuffer不再迷路_第12张图片

valu数组被final修饰

也就是说,上述代码和new String是一样的,都是一个对象。

它将以对象的方式去组织,会将字符串转变为一个数组。

String类型详解(下),让你StringBuilder和StringBuffer不再迷路_第13张图片

简单认为它将以数组的形式去存储

String类型详解(下),让你StringBuilder和StringBuffer不再迷路_第14张图片

value代表此时value只能引用这个对象,并且类比数组来说,是可以去修改它的下标的

而之所以String不能被修改

是因为它被封装起来了,而且并没有提供任何get或者set方法,final修饰让它不能被继承,所以只能在String这个类里面才能进行使用

那如果想要修改字符串中内容该如何操作?

二,字符串修改

注意:尽量避免直接对 String 类型对象进行修改,因为 String 类是不能修改的,所有的修改都会创建新对象,效率 非常低下。
提及字符串修改,我们先来看看这串代码
public class Main {
    public static void main(String[] args) {
        String str = "hello";
        str += "abc";
        System.out.println(str);
    }
}

String类型详解(下),让你StringBuilder和StringBuffer不再迷路_第15张图片

很多人误以为这样是字符串的修改

非也,这其实是字符串的拼接

它并没有修改原来str的值,和上面一样,也是使用的新的对象。

而且效率不高,中间会产生很多临时对象

String类型详解(下),让你StringBuilder和StringBuffer不再迷路_第16张图片

我们也可以试着用汇编查看

通过查看汇编,我们模仿着写出该代码:

public class Test {
    public static void main(String[] args) {
        String str = "hello";
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(str);
        stringBuilder.append("abc");
        str = stringBuilder.toString();
        System.out.println(str);
    }
}

输出结果同上

String类型详解(下),让你StringBuilder和StringBuffer不再迷路_第17张图片

而且在这个过程当中,产生了4个对象

当将该代码放入循环时,每循环一次都将产生多个对象,低效率显而易见。

可通过以下代码进行验证

public static void main(Stri 
long start = System.curr 
String s = ""; 
for(int i = 0; i < 10000; ++ 
s += i; 
}
long end = System.curre 
System.out.println(end - 
start = System.currentTimeMillis(); 
StringBuffffer sbf = new StringBuffffer(""); 
for(int i = 0; i < 10000; ++i){ 
sbf.append(i); 
}
end = System.currentTimeMillis(); 
System.out.println(end - start); 
start = System.currentTimeMillis(); 
StringBuilder sbd = new StringBuilder(); 
for(int i = 0; i < 10000; ++i){ 
sbd.append(i); 
}
end = System.currentTimeMillis(); 
System.out.println(end - start); 
}
因此:尽量避免对 String 的直接需要,如果要修改建议尽量使用StringBuffffer 或者 StringBuilder

三, StringBuilderStringBuffer

StringBuilder的介绍

由于String的不可更改特性,为了方便字符串的修改,Java中又提供StringBuilder和StringBuffffer类。StringBuilder和StringBuffffer是两个全新的类,其中String有的它们可以没有,String没有的它们可能又有。这两个类大部分功能是相同的,这里介绍 StringBuilder常用的一些方法,其它需要用到了大家可参阅Overview (Java Platform SE 8 ) (oracle.com)

方法 说明
StringBuffer append(String
str)
在尾部追加,相当于 String += ,可以追加: boolean char char[] 、double、 flfloat int long Object String StringBuffff 的变量
char charAt(int index)
获取 index 位置的字符
int length()
获取字符串的长度
int capacity()
获取底层保存字符串空间总的大小
void ensureCapacity(int
mininmumCapacity)
扩容
void setCharAt(int index,char ch)
index 位置的字符设置为 ch
int indexOf(String str)
返回 str 第一次出现的位置
int indexOf(String str, int
fromIndex)
fromIndex 位置开始查找 str 第一次出现的位置
int lastIndexOf(String str)
返回最后一次出现 str 的位置
int lastIndexOf(String str,int fromIndex)
fromIndex 位置开始找 str 最后一次出现的位置
StringBuffer insert(int
ofset, String str)
ofset 位置插入:八种基类类型 & String 类型 & Object 类型数据
StringBuffer deleteCharAt(int index)
删除 index 位置字符
StringBuffer delete(int
start, int end)
删除 [start, end) 区间内的字符
StringBuffer replace(int
start, int end, String str)
[start, end) 位置的字符替换为 str
String substring(int start)
start 开始一直到末尾的字符以 String 的方式返回
String substring(int
start int end)
[start, end) 范围内的字符以 String 的方式返回
StringBuffffer reverse()
反转字符串
String toString()
将所有字符按照 String 的方式返回

public static void main(String[] args) {

StringBuilder sb1 = new StringBuilder("hello");

StringBuilder sb2 = sb1;

// 追加:即尾插-->字符、字符串、整形数字

sb1.append(' '); // hello

sb1.append("world"); // hello world

sb1.append(123); // hello world123

System.out.println(sb1); // hello world123

System.out.println(sb1 == sb2); // true

System.out.println(sb1.charAt(0)); // 获取0号位上的字符 h

System.out.println(sb1.length()); // 获取字符串的有效长度14

System.out.println(sb1.capacity()); // 获取底层数组的总大小

sb1.setCharAt(0, 'H'); // 设置任意位置的字符 Hello world123

sb1.insert(0, "Hello world!!!"); // Hello world!!!Hello world123

System.out.println(sb1);

System.out.println(sb1.indexOf("Hello")); // 获取Hello第一次出现的位置

System.out.println(sb1.lastIndexOf("hello")); // 获取hello最后一次出现的位置

sb1.deleteCharAt(0); // 删除首字符

sb1.delete(0,5); // 删除[0, 5)范围内的字符

String str = sb1.substring(0, 5); // 截取[0, 5)区间中的字符以String的方式返回

System.out.println(str);

sb1.reverse(); // 字符串逆转

str = sb1.toString(); // StringBufffferString的方式返回

System.out.println(str);

}

从上述例子可以看出: String StringBuilder 最大的区别在于 String 的内容无法修改,而 StringBuilder 的内容可 以修改 。频繁修改字符串的情况考虑使用 StringBuilder
注意: String StringBuilder 类不能直接转换。如果要想互相转换,可以采用如下原则 :
•   String变为 StringBuilder: 利用 StringBuilder 的构造方法或 append() 方法
•   StringBuilder变为 String: 调用 toString() 方法。

四,StringBuider与StringBuffer的区别

StringBuider与StringBuffer有什么区别?

public class Test {
    public static void main(String[] args) {
        StringBuilder stringBuilder = new StringBuilder("hello");
        StringBuffer stringBuffer = new StringBuffer("hello");
        stringBuffer.append("Buffer");
        stringBuilder.append("Builder");
    }
}

可以看到,无论是StringBuider还是StringBuffer都可以实例化并且调用append()方法,那我们如何得知二者的区别呢?

就由上述代码为例,我们查看二者调用appen的源码

Buffer:

String类型详解(下),让你StringBuilder和StringBuffer不再迷路_第18张图片

Builder:

String类型详解(下),让你StringBuilder和StringBuffer不再迷路_第19张图片

我们暂且不管Buffer中toStringCache那行

然后我们进行比较会发现二者源码只有一处不同:

String类型详解(下),让你StringBuilder和StringBuffer不再迷路_第20张图片

Buffer处多了一个synchronized来修饰

而synchronized我们可以把它抽象为一把锁

在多线程的情况下只有有对象进入到方法中,synchronized就会为其“上锁”,待该对象调完该方法后“解锁”让下一个对象调用。而频繁的加锁和解锁会耗费资源,所以在不涉及多线程时一般使用Builder。

所以Buffer一般用于多线程情况

Builder一般用于单线程情况

StringStringBufffferStringBuilder的区别

•    String的内容不可修改, StringBuffffer StringBuilder 的内容可以修改 .
•    StringBuffffer与 StringBuilder 大部分功能是相似的
•    StringBuffffer采用同步处理,属于线程安全操作;而 StringBuilder 未采用同步处理,属于线程不安全操作
练习:
以下总共创建了多少个 String 对象【前提不考虑常量池之前是否存在】

String str = new String("ab"); // 会创建多少个对象

String str = new String("a") + new String("b"); // 会创建多少个对象

答案:2   5

String类型详解(下),让你StringBuilder和StringBuffer不再迷路_第21张图片

String类型详解(下),让你StringBuilder和StringBuffer不再迷路_第22张图片


完.

你可能感兴趣的:(java,开发语言)