🚆简单说明:
在C语言中是没有字符串这种数据类型的,但是在Java中有字符串类型 ➡ String。
- 字符:使用单引号引起的一个字符。'a','中'
- 字符串:使用双引号引起的若干个字符。"abcde","a"
💥注意:在Java中字符串没有所谓的以 '\0' 结尾的说法!
目录
七、StringBuffer 和 StringBuilder
🍓普通的String拼接,底层会被优化为StringBuilder
🍓StringBuffer 和 StringBuilder的区别
🍓String 与 StringBuffer 或 StringBuilder 之间的转换
一、创建字符串
✨String 类是被final修饰的,所以它不可以被继承。
🍓常见的构造 String 的方式
public class TestDemo {
public static void main(String[] args) {
// 方式一:直接赋值
String str = "Hello Java";
// 方式二:调用构造方法进行构造对象
String str2 = new String("Hello Java");
// 方式三:使用数组
char[] array = {'a', 'b', 'c'};
String str3 = new String(array);
}
}
官方文档可以看到 String 还支持很多其他的构造方式, 用到的时候去查就可以了。
💥注意事项:
- "hello" 这样的字符串字面值常量, 类型也是 String.
- String 也是引用类型。
String str = "Hello";
代码内存布局
🍓引用
- 在介绍数组的文章中就提到了引用的概念.
- 引用类似于 C 语言中的指针, 只是在栈上开辟了一小块内存空间保存一个地址. 但是引用和指针又不太相同, 指针能进行各种数字运算(指针+1)之类的, 但是引用不能, 这是一种 "没那么灵活" 的指针.
- 也可以把引用想象成一个标签, "贴" 到一个对象上. 一个对象可以贴一个标签, 也可以贴多个。如果一个对象上面一个标签都没有, 那么这个对象就会被 JVM 当做垃圾对象回收掉.
- Java 中数组, String, 以及自定义的类都是引用类型
String 是引用类型, 因此对于以下代码
String str1 = "Hello";
String str2 = str1;
内存布局
如果修改 str1 , str2 会不会随之变化呢?
事实上, str1 = "world" 这样的代码并不算 "修改" 字符串, 而是让 str1 这个引用指向了一个新的 String 对象。
内存布局
补充:
- 虽然str1与str2指向了同一个字符串"Hello",但是不可以通过str1修改字符串"Hello"的值,因为双引号引起来的是字面常量,常量是不可以被修改的。
- 上面的代码是修改的是str1的指向。
🍓方法传参
✨传引用不一定可以改变实参的值,要看这个引用具体做了什么。
public class TestDemo1 {
public static void func(String s,char[] array){
s = "abcdef";
array[0] = 'P';
}
public static void main(String[] args) {
String str = "Hello";
char[] chars = {'W','o','r','l','d'};
func(str,chars);
System.out.println(str);
System.out.println(Arrays.toString(chars));
}
}
内存布局
运行结果
二、字符串常量池
🍓常量池的概念
🎄Class文件常量池:
- 程序编译后生成字节码文件,在字节码文件中会有一个常量池(Class文件常量池)。
- 例如:int a = 10; 在编译期间,10就放在Class文件常量池。
- Class文件常量池存放在磁盘上的。
🎄运行时常量池:
- 当程序把编译好的字节码文件加载到JVM当中后,会生成一个运行是常量池,这个常量池存放在方法区。
- 运行时常量池实际上是Class文件常量池被加载后放到了JVM中。
🎄字符串常量池:
- 主要存放字符串常量。
- 字符串常量池本质上是一个哈希表(StringTable)。
- 双引号引起来的内容会放到字符串常量池中。
- 从JDK1.8开始,将字符串常量池存放在堆里面。
🎄理解 "池" (pool)
- "池" 是编程中的一种常见的, 重要的提升效率的方式,。"池" 有很多,例如: "内存池", "线程池", "数据库连接池"。
- 以字符串常量池举例:需要使用的字符串在常量池中,用的时候直接在常量池里拿,常量池里面没有需要的字符串就去创建,下一次使用的时候就不用再创建了,直接在常量池里拿。
🎄哈希表:
- 是一种数据结构,描述和组织数据的一种方法。
- 存储数据的时候,会根据一个映射关系进行存储,至于如何映射,需要设计一个函数(哈希函数)。
这里只是简单的描述一下哈希表。有关哈希表的具体内容会在后面的数据结构中详细介绍。
因为双引号引起来的内容会存放到字符串常量池中,字符串常量池本质上是一个哈希表,所以字符串会挂到哈希表的结点上。
三、字符串比较相等
🍓易错题
🌀 易错 1:
public class TestDemo1 {
public static void main(String[] args) {
String str1 = "hello";
String str2 = new String("hello");
System.out.println(str1==str2);
}
}
//运行结果:false
内存布局
✨代码分析:
最终str1产生了一个"hello"字符串对象
str2产生了两个"hello"字符串对象:
- 常量池中的"hello"对象赋给了str2
- str2 new 的一个"hello"对象
🌀 易错 2:
public class TestDemo1 {
public static void main(String[] args) {
String str1 = "hello";
String str2 = "hello";
System.out.println(str1==str2);
}
}
//运行结果:true
内存布局
🌀 易错 3:
public class TestDemo1 {
public static void main(String[] args) {
String str1 = "hello";
String str2 = "he"+"llo"; //此时,两个都是常量,在编译的时候就已经确定是"hello"
System.out.println(str1==str2);
}
}
//运行结果:true
🌀 易错 4:
public class TestDemo1 {
public static void main(String[] args) {
String str1 = "hello";
String str2 = "he";
String str3 = str2 + "llo"; //str3 是一个变量,在编译的时候并不确定里面是什么
System.out.println(str1 == str3);
}
}
//运行结果:false
内存布局
🌀 易错 5:
public class TestDemo1 {
public static void main(String[] args) {
String str1 = "11";
String str2 = new String("1")+new String("1");
System.out.println(str1==str2);
}
}
//运行结果:false
内存布局
🌀 易错 6:
public class TestDemo1 {
public static void main(String[] args) {
String str2 = new String("1")+new String("1");
str2.intern();//手动入池
String str1 = "11";
System.out.println(str1==str2);
}
}
//运行结果:true
内存布局
🌀 易错 7:针对上面的代码换一个顺序
public class TestDemo1 {
public static void main(String[] args) {
String str1 = "11";
str1.intern();//手动入池 -》当字符串常量池中没有该对象的时候,就会入池。如果有就不会入池。
String str2 = new String("1")+new String("1");
System.out.println(str1==str2);
}
}
//运行结果:false
✨代码分析:
- 将str1的"11"字符串先入池,定义str2最终拼接出字符串"11",字符串常量池中有"11"对象,所以不会将str2指向的对象入池。
- str1 与 str2 指向不同的地址,所以打印出来的结果是false。
- 这个代码的内存布局图与第(5)个一样。
⭐String 使用 == 比较并不是在比较字符串内容, 而是比较两个引用是否是指向同一个对象
🍎Java 中要想比较字符串的内容, 必须采用String类提供的 equals 方法
🍓equals方法
🌌Java底层中的 equals 方法的分析
Object.java
String.java
- Java底层中的 equals 方法在 Object 类中比较两个引用是否相等(是否指向同一个对象)。
- 在String类中重写了 equals 方法,重写 equals 方法后,先比较两个字符串长度是否相等,如果不相等就返回false,如果相等再比较两个数组中的每一个字符 ,如果字符都相等就返回 true,只要有一个字符不相等就返回 false。
🌀 equals 方法的使用(1):
public class TestDemo1 {
public static void main(String[] args) {
String str1 = "hello";
String str2 = new String("hello");
System.out.println(str1.equals(str2));
}
}
//运行结果:true
🌀 equals 方法的使用(2):
public class TestDemo1 {
public static void main(String[] args) {
String str1 = "hello";
//"Hello"这样的字面值常量, 本质上也是一个 String 对象, 完全可以使用 equals 等 String 对象的方法
System.out.println("hello".equals(str1));
}
}
🌀 equals 方法的使用(3):
public class TestDemo1 {
public static void main(String[] args) {
String str1 = null; //str1这个引用不指向任何对象
String str2 = " "; //str2这个引用指向的字符串是空
//这两个对象不可能相等
}
}
💥注意:任何一个引用在调用方法时都要预防空指针异常。
✨如果将str1放到括号里面就不会出现空指针异常,因为调用方法的 str2 不是空指针。
⭐底层的 equals 方法中一开始是判断两个引用是否是相等,如果相等就返回true。
四、理解字符串不可变
- 字符串是一种不可变对象. 它的内容不可改变.
- String 类的内部实现也是基于 char[] 来实现的, 但是 String 类并没有提供 set 方法之类的来修改内部的字符数组。
🍓分析字符串拼接
public class TestDemo1 {
public static void main(String[] args) {
String str = "hello" ;
str = str + " world" ;
str += "!!!" ;
System.out.println(str);
}
}
//运行结果:hello world!!!
形如 += 这样的操作, 表面上好像是修改了字符串, 其实不是。这段代码中共创建了5次对象:
- "hello"
- "world"
- "hello world"
- "!!!"
- "hello world!!!"
+= 之后 str 打印的结果却是变了, 但不是 String 对象本身发生改变, 而是 str 引用到了其他的对象。
🍓修改字符串的方法
- 例如, 现有字符串 str = "Hello" , 想改成 str = "hello"
🌊常见办法: 借助原字符串, 创建新的字符串
public class TestDemo1 {
public static void main(String[] args) {
String str = "Hello";
str = "h" + str.substring(1);
System.out.println(str);
}
}
//运行结果:hello
- 关于substring()方法的使用在文章的后面会介绍
🌊特殊办法:使用 "反射" 操作可以破坏封装, 访问一个类内部的 private 成员。
- 通过前面的内容可以知道,字符串是将字符保存在 value 数组中的。通过"反射"更改value数组中的字符。
import java.lang.reflect.Field;
public class TestDemo1 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
String str = "Hello";
// 获取 String 类中的 value 字段. 这个 value 和 String 源码中的 value 是匹配的.
Field valueField = String.class.getDeclaredField("value");
// 将这个字段的访问属性设为 true
valueField.setAccessible(true);
// 把 str 中的 value 属性获取到.
char[] value = (char[]) valueField.get(str);
// 修改 value 的值
value[0] = 'h';
System.out.println(str);
}
}
//运行结果:hello
关于反射
- 反射是面向对象编程的一种重要特性, 有些编程语言也称为 "自省".
- 指的是程序运行过程中, 获取/修改某个对象的详细信息(类型信息, 属性信息等), 相当于让一个对象更好的 "认清自己"
- 这里只是简单的用了一下反射,关于反射的具体内容会在后面的文章中详细介绍。
五、字符,字节与字符串
🍓字符与字符串
- 字符串内部包含一个字符数组,String 可以和 char[] 相互转换。
💦将字符数组中的所有内容变成字符串
public class TestDemo1 {
public static void main(String[] args) {
char ch[] = {'a','b','c','d','e'};
String str = new String(ch);
System.out.println(str);
}
}
//运行结果:abcde
💦将部分字符数组中的内容变为字符串
public class TestDemo1 {
public static void main(String[] args) {
char ch[] = {'a','b','c','d','e'};
String str = new String(ch,1,3); //从下标1开始,将后面的3个字符变成字符串
System.out.println(str);
}
}
//运行结果:bcd
💦获取指定索引(下标)位置的字符,索引从0开始
public class TestDemo1 {
public static void main(String[] args) {
String str = "hello";
char ch = str.charAt(2);//获取2下标的字符
System.out.println(ch);
}
}
//运行结果:l
💦将字符串变成字符数组
import java.util.Arrays;
public class TestDemo1 {
public static void main(String[] args) {
String str = "hello";
char chars[] = str.toCharArray(); //将str指向的字符串对象,变成字符数组
System.out.println(Arrays.toString(chars));
}
}
//运行结果:[h, e, l, l, o]
练习
- 给定字符串一个字符串, 判断其是否全部由数字所组成。
🌊方法一:使用 charAt() 方法获取指定位置的字符,然后使用ASCII码值进行判断
public class TestDemo1 {
public static boolean isNumberChar(String s){
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
if(ch<'0' || ch>'9'){
return false;
}
}
return true;
}
public static void main(String[] args) {
String str = "123456";
System.out.println(isNumberChar(str));
}
}
//运行结果:true
🌊方法二:使用 charAt()方法获取指定位置的字符,然后使用 Character 类中的 isDigit() 方法。
public class TestDemo1 {
public static boolean isNumberChar(String s){
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
//判断某个字符是不是数字(是返回 true,不是返回 false)
boolean flag = Character.isDigit(ch);
if(flag==false){
return false;
}
}
return true;
}
public static void main(String[] args) {
String str = "123456";
System.out.println(isNumberChar(str));
}
}
//运行结果:true
🍓字节与字符串
- 字节常用于数据传输以及编码转换的处理之中,String 也能方便的和 byte[] 相互转换。
💦将字节数组变为字符串
public class TestDemo1 {
public static void main(String[] args) {
byte[] bytes = {97,98,99,100};
String str = new String(bytes);
System.out.println(str);
}
}
//abcd
💦将部分字节数组中的内容变为字符串
public class TestDemo1 {
public static void main(String[] args) {
byte[] bytes = {97,98,99,100};
String str = new String(bytes,1,2);
System.out.println(str);
}
}
//运行结果:bc
💦将字符串以字节数组的形式返回
import java.util.Arrays;
public class TestDemo1 {
public static void main(String[] args) {
String str = "abcd";
byte[] bytes = str.getBytes();
System.out.println(Arrays.toString(bytes));
}
}
//运行结果:[97, 98, 99, 100]
💦编码转换处理
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
public class TestDemo1 {
public static void main(String[] args) throws UnsupportedEncodingException {
String str = "语言";
byte[] bytes = str.getBytes("utf-8");
System.out.println(Arrays.toString(bytes));
byte[] bytes2 = str.getBytes("GBK");
System.out.println(Arrays.toString(bytes2));
}
}
运行结果
- 除数字和英文字母以外,不同编码格式转换后的字节数组不同。
💦何时使用 byte[], 何时使用 char[]?
- byte[] 是将 String 按照一个字节一个字节的方式处理, 这种适合在网络传输, 数据存储这样的场景下使用. 更适合针对二进制数据来操作.
- char[] 是将 String 按照一个字符一个字符的方式处理, 更适合针对文本数据来操作, 尤其是包含中文的时候。
六、字符串常见操作
🍓字符串比较
- 前面简单的介绍过String类提供的 equals()方法,该方法本身是可以进行区分大小写的相等判断。除了这个方法之外,String类还提供了一些比较操作。
💦区分大小写的比较
public class TestDemo1 {
public static void main(String[] args) {
String str1 = "Hello";
String str2 = "hello";
System.out.println(str1.equals(str2));
}
}
//运行结果:false
💦不区分大小写的比较
public class TestDemo1 {
public static void main(String[] args) {
String str1 = "Hello";
String str2 = "hello";
System.out.println(str1.equalsIgnoreCase(str2));
}
}
//运行结果:true
💦比较两个字符串的大小关系,使用 compareTo() 方法
public class TestDemo1 {
public static void main(String[] args) {
String str1 = "abc";
String str2 = "Abc";
int ret = str1.compareTo(str2);
System.out.println(ret);
}
}
//运行结果:32
在String类中compareTo()方法是一个非常重要的方法,该方法返回一个整型,该数据会根据大小关系返回三类内容:
- 相等:返回0
- 小于:返回内容小于0
- 大于:返回内容大于0
🍓字符串查找
- 从一个完整的字符串之中可以判断指定内容是否存在,查找方式如下:
💦判断子字符串是否存在主字符串中
public class TestDemo1 {
public static void main(String[] args) {
String str = "ababcabcd";
String tmp = "abc";
boolean flag = str.contains(tmp);
System.out.println(flag);
}
}
//运行结果:true
- 该判断形式是从JDK1.5之后开始追加的,在JDK1.5以前要想实现与之类似的功能,就必须借助 indexOf() 方法完成。
💦从头开始查找指定字符串的位置,找到了然后位置的开始索引,找不到返回-1
public class TestDemo1 {
public static void main(String[] args) {
String str = "ababcabcd";
String tmp = "abc";
//返回主串中第一次出现子串的起始位置
int index = str.indexOf(tmp);
System.out.println(index);
}
}
//运行结果:2
- 使用indexOf():如果内容重复,它只能返回查找的第一个位置
💦从指定位置开始查找子串的起始位置
public class TestDemo1 {
public static void main(String[] args) {
String str = "ababcabcd";
String tmp = "abc";
//从下标为3的位置开始查找子串的起始位置
int index = str.indexOf(tmp,3);
System.out.println(index);
}
}
//运行结果:5
💦从后向前查找字符的起始位置
public class TestDemo1 {
public static void main(String[] args) {
String str = "ababcabcd";
String tmp = "abc";
//从后向前查找子串的起始位置
int index = str.lastIndexOf(tmp);
System.out.println(index);
}
}
//运行结果:5
💦从指定位置开始,从后向前查找子串的起始位置
public class TestDemo1 {
public static void main(String[] args) {
String str = "ababcabcd";
String tmp = "abc";
//从下标为4的位置开始从后向前查找子串的起始位置
int index = str.lastIndexOf(tmp,4);
System.out.println(index);
}
}
//运行结果:2
💦判断一个字符串是否以指定字符串开头
public class TestDemo1 {
public static void main(String[] args) {
String str = "ababcabcd";
String tmp = "ac";
//判断str指向的字符串是否以 "ac" 字符串开头
boolean flag = str.startsWith(tmp);
System.out.println(flag);
}
}
//运行结果:false
💦从指定位置开始,判断一个字符串是否以指定字符串开头
public class TestDemo1 {
public static void main(String[] args) {
String str = "ababcabcd";
String tmp = "c";
//从下标为4的位置开始,判断str指向的字符串是否以 "c" 字符串开头
boolean flag = str.startsWith(tmp,4);
System.out.println(flag);
}
}
//运行结果:true
💦判断一个字符串是否以指定字符串结尾
public class TestDemo1 {
public static void main(String[] args) {
String str = "ababcabcd";
String tmp = "cd";
//判断str指向的字符串是否以 "cd" 字符串结尾
boolean flag = str.endsWith(tmp);
System.out.println(flag);
}
}
//运行结果:true
🍓字符串替换
- 使用一个指定的新的字符串替换掉已有的字符串数据。
💦替换所有的指定内容
public class TestDemo1 {
public static void main(String[] args) {
String str = "ababcabcd";
//将字符串中的"ab"替换为字符串"vn"
String s = str.replaceAll("ab","vn");
System.out.println(s);
}
}
//运行结果:vnvncvncd
💦替换第一个内容
public class TestDemo1 {
public static void main(String[] args) {
String str = "ababcabcd";
//将字符串中的第一个"ab"替换为字符串"vn"
String s = str.replaceFirst("ab","vn");
System.out.println(s);
}
}
//运行结果:vnabcabcd
💥注意事项: 由于字符串是不可变对象, 替换不修改当前字符串, 而是产生一个新的字符串
🍓字符串拆分
- 可以将一个完整的字符串按照指定的分隔符划分为若干个子字符串
💦将字符串全部拆分(多次拆分)
public class TestDemo1 {
public static void main(String[] args) {
String str = "name=zhangsan&age=20";
//先按 "&" 进行分割
String[] strings = str.split("&");
for (String ret : strings) {
System.out.println(ret);
//再按 "=" 进行分割
String[] ss = ret.split("=");
for (String tmp : ss) {
System.out.println(tmp);
}
}
}
}
//运行结果:
name=zhangsan
name
zhangsan
age=20
age
20
💦将字符串部分拆分
public class TestDemo1 {
public static void main(String[] args) {
String str = "hello world hello java";
//将字符串以空格" " 部分拆分成两部分,不会均匀拆分
String[] ret = str.split(" ",2);
for (String s:ret) {
System.out.println(s);
}
}
}
//运行结果:
hello
world hello java
💦有些特殊字符作为分割符可能无法正确切分, 需要加上转义
💥注意事项:
- 字符"|","*","+"都得加上转义字符,前面加上"\".
- 而如果是"\",那么就得写成"\\\\".
- 如果一个字符串中有多个分隔符,可以用"|"作为连字符.
🌊代码示例: 拆分IP地址
public class TestDemo1 {
public static void main(String[] args) {
String str = "192.168.1.1";
//因为'.'是一个特殊字符,要使用转义字符'\'
//又因为'\'也是一个特殊字符,要使用"\\"表示'\',然后通过'\'对'.'进行转义
String[] strings = str.split("\\.");
for (String s:strings) {
System.out.println(s);
}
}
}
//运行结果:
192
168
1
1
🌊代码示例: 以 '\' 字符进行拆分
public class TestDemo1 {
public static void main(String[] args) {
//使用"\\"表示'\'
String str = "192\\168\\1\\1";
//以 '\' 进行拆分,需要"\\\\"
//因为'\'是一个特殊字符,要使用"\\"表示'\','\'又会对后面的字符进行转义,所以要使用4个
String[] strings = str.split("\\\\");
for (String s:strings) {
System.out.println(s);
}
}
}
//运行结果:
192
168
1
1
🌊代码示例: 符串中有多个分隔符,用 "|" 作为连字符
public class TestDemo1 {
public static void main(String[] args) {
String str = "Java30 12&21#hello";
String[] strings = str.split(" |&|#");
for (String s:strings) {
System.out.println(s);
}
}
}
//运行结果:
Java30
12
21
hello
🍓字符串截取
- 从一个完整的字符串之中截取出部分内容
💥注意事项:
- 索引从0开始,返回原字符串
- 注意前闭后开区间的写法, substring(0, 5) 表示包含 0 号下标的字符, 不包含 5 号下标
💦从字符串指定索引位置截取到结尾
public class TestDemo1 {
public static void main(String[] args) {
String str = "abcdefg";
//从下标为2的位置开始截取字符串
String s = str.substring(2);
System.out.println(s);
}
}
//运行结果:cdefg
💦截取部分内容(左闭右开)
public class TestDemo1 {
public static void main(String[] args) {
String str = "abcdefg";
//从下标为2的位置开始截取字符串到下标为3的位置 [2,4)
String s = str.substring(2,4);
System.out.println(s);
}
}
//运行结果:cd
🍓其他操作方法
💦去除字符串中的左右空格,保留中间的空格
public class TestDemo1 {
public static void main(String[] args) {
String str = " hello world " ;
System.out.println("["+str+"]");
System.out.println("["+str.trim()+"]");
}
}
//运行结果:
[ hello world ]
[hello world]
- trim()方法会去掉字符串开头和结尾的空白字符(空格, 换行, 制表符等)
💦字符串转大写
public class TestDemo1 {
public static void main(String[] args) {
String str = "abcdeFYKL";
String ret = str.toUpperCase();
System.out.println(ret);
}
}
//运行结果:ABCDEFYKL
💦字符串转小写
public class TestDemo1 {
public static void main(String[] args) {
String str = "abcdeFYKL";
String ret = str.toLowerCase();
System.out.println(ret);
}
}
//运行结果:abcdefykl
💦字符串入池操作
- 使用 intern() 方法,此方法在前面已经介绍了。
💦字符串连接,连接后的字符串不会放到常量池中。
public class TestDemo1 {
public static void main(String[] args) {
String str = "hello";
String ret = str.concat("world");
System.out.println(ret);
}
}
//运行结果:helloworld
💦获取字符串长度
public class TestDemo1 {
public static void main(String[] args) {
String str = "abcdef";
int len = str.length();
System.out.println(len);
}
}
//运行结果:6
- 注意:数组长度使用数组名称.length属性,而String中使用的是length()方法
💦判断字符串是否为空。(字符串长度是否为0,不是null)
public class TestDemo1 {
public static void main(String[] args) {
String str1 = ""; //长度为0,所以该字符串为空
String str2 = " ";//空格,长度不为0,所以该字符串不为空
boolean flag1 = str1.isEmpty();
boolean flag2 = str2.isEmpty();
System.out.println(flag1);
System.out.println(flag2);
}
}
//运行结果:
true
false
🌊练习:将字符串的首字母变成大写
public class TestDemo1 {
public static String fistUpper(String s){
if("".equals(s) || s==null){
return s;
}
if(s.length()>1){
return s.substring(0,1).toUpperCase()+s.substring(1);
}
return s.toUpperCase();
}
public static void main(String[] args) {
String str = "hello";
String ret = fistUpper(str);
System.out.println(ret);
}
}
//运行结果:Hello
七、StringBuffer 和 StringBuilder
🚀回顾String类的特点:
- 任何的字符串常量都是String对象,而且String的常量一旦声明不可改变,如果改变对象内容,改变的是其引用的指向而已。
- 通常来讲String的操作比较简单,但是由于String的不可更改特性,为了方便字符串的修改,提供StringBuffer和StringBuilder类。
- StringBuffer 和 StringBuilder 大部分功能是相同的,这里主要介绍 StringBuilder
- 在String中使用"+"来进行字符串连接,但是这个操作在StringBuffer类中需要更改为append()方法
🍓StringBuilder 的使用
💦使用构造方法赋值
public class TestDemo1 {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("abcde");
//StringBuilder 类中有一个 toString()方法,直接传引用会默认调用toString()方法打印
System.out.println(sb); //默认调用toString()方法
System.out.println(sb.toString()); //直接调用toString()方法
}
}
//运行结果
abcde
abcde
💦使用append()方法赋值
public class TestDemo1 {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
sb.append("abcde");
System.out.println(sb);
}
}
运行结果:abcde
public class TestDemo1 {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
sb.append("abced");
sb.append("1234");
System.out.println(sb);
}
}
//运行结果:abced1234
✨append()方法可以连用
public class TestDemo1 {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
sb.append("abced").append("1234");
System.out.println(sb);
}
}
//abced1234
🍓普通的String拼接,底层会被优化为StringBuilder
public class TestDemo2 {
public static void main(String[] args) {
String str = "hello";
str +="abc";
System.out.println(str);
}
}
//运行结果:helloabc
如果在循环里面进行字符串拼接,尽量不要使用String,优先使用StringBuffer 和 StringBuilder。
🍓StringBuffer 和 StringBuilder的区别
- 可以看到StringBuffer类中的方法都被 synchronized 关键字修饰,而StringBuilder类中的方法并没有。
- synchronized关键字可以保证线程安全。
- StringBuffer 一般用于多线程,StringBuilder 一般用于单线程
🍓String 与 StringBuffer 或 StringBuilder 之间的转换
✨String和StringBuffer类不能直接转换。如果要想互相转换,可以采用如下原则:
⭐String变为StringBuffer:利用StringBuffer的构造方法或append()方法
/**
* String -> StringBuffer 或 StringBuilder
* 使用构造方法
* @return
*/
public static StringBuffer func1(){
String str = "abcde";
StringBuffer sb = new StringBuffer(str);
return sb;
}
/**
* String -> StringBuffer 或 StringBuilder
* 使用append()方法
* @return
*/
public static StringBuffer func2(){
String str = "abcde";
StringBuffer sb = new StringBuffer();
sb.append(str);
return sb;
}
⭐StringBuffer变为String:调用toString()方法
/**
* StringBuffer 或 StringBuilder -> String
* 调用 toString() 方法
* @return
*/
public static String func3(){
StringBuffer sb = new StringBuffer("abcde");
return sb.toString();
}
🍓StringBuffer类中部分方法的使用
💦字符串反转
public synchronized StringBuffer reverse()
代码演示:
public class TestDemo1 {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("abcdef");
System.out.println(sb.reverse());
}
}
//运行结果:fedcba
💦删除指定范围的数据
public synchronized StringBuffer delete(int start, int end)
代码演示:
public class TestDemo1 {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("hello world");
System.out.println(sb.delete(5, 10));
}
}
//运行结果:hellod
💦插入数据
public synchronized StringBuffer insert(int offset, 各种数据类型 b)
代码演示:
public class TestDemo1 {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("helloworld");
System.out.println(sb.delete(5, 10).insert(0, "你好"));
}
}
//运行结果:你好hello
八、字符串的常见问题
1、解释String类中两种对象实例化的区别?
- 直接赋值:只会开辟一块堆内存空间,并且该字符串对象可以自动保存在对象池中以供下次使用。
- 构造方法:会开辟两块堆内存空间,不会自动保存在对象池中,可以使用intern()方法手工入池。
2、为什么 String 要不可变?(不可变对象的好处是什么?)
- 方便实现字符串对象池. 如果 String 可变, 那么对象池就需要考虑何时深拷贝字符串的问题。
- 不可变对象可以使线程是安全的。
- 不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中。
3、解释String、StringBuffer、StringBuilder的区别
- String的内容不可修改,StringBuffer与StringBuilder的内容可以修改。频繁修改字符串的情况考虑使用 StingBuffer 或 StringBuilder。
- StringBuffer与StringBuilder大部分功能是相似的。
- StringBuffer采用同步处理,属于线程安全操作;而StringBuilder未采用同步处理,属于线程不安全操作。