
前序
根据String 的源码可以发现(或者JDK21的帮助文档)https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/String.html

一、基本用法
String类就是字符串(例:字符串"ABC小+#",字符是 '1' ),其为引用类型,内部并不存储字符串本身,不可变即不可更改
1.构造String
①使用常量串构造
②直接new String对象
③使用字符数组构造
java
public class Demo1 {
public static void main(String[] args) {
// 使用常量串构造
String name = "小华";
System.out.println(name);
// 直接 new String 对象
String str1 = new String("hello word");
System.out.println(str1);
// 使用字符数组进行构造
char[] chars = new char[]{'h','e','l','l','o'};
String str2 = new String(chars);
System.out.println(str2);
}
}
2.比较String(boolean)
① == 比较两个引用是否指向同一个地址
相同常量构造String,地址是相同的
String str1 = "狗";
String str2 = "狗";
str1和str2地址是相等的
会输出true
创造String对象,是和常量的String,地址不相等的,是两个String引用的不同地址String str1 = new String("狗");//String[0]@111
String str2 = "狗";//String[0]@222
false
② .equals() 比较字符串的内容
比较的是两个字符串里面的内容是否相等,考虑大小写

③ .equalsIgnoreCase()忽略大小写比较字符串的内容
忽略大小写比较字符串的内容是否相等

④compareTo()比较字符串的字典序
字典序比较:判断两个字符串在字典里的先后顺序,即按a-z的顺序大小,apple 在 banana前面
compareTo():
先按照字典次序大小比较,如果出现不等的字符,直接返回这两个字符的大小差值
如果前k个字符相等(k为两个字符长度最小值),返回值两个字符串长度差值
第一个就不相等

前k个相等,比较下一个不相等的字符

compareTo()是要考虑大小写的

⑤compareToIgnoreCase() 忽略大小写比较字符串的字典序
就是 compareToIgnoreCase() 的比较方法不管大小写,compareTo()是要考虑大小写的

java
public class Demo3 {
public static void main(String[] args) {
String str1 =new String( "hello");
//str1和str2指向不同的对象,两地址不同
String str2 =new String( "world");
String str3 = new String("hello bit");
// 1、==比较的是引用是否相同,即是否指向同一对象
System.out.println("str1 == str2:"+(str1 == str2));//(str1≠str2)false
// 2、equals()比较的是字符串的内容是否相同
System.out.println("str1.equals(str2):"+str1.equals(str2));//("hello"≠"world")false
System.out.println("\"hello\".equals(\"HeLLO\"):"+str1.equals("HeLLO"));//false
// 3.equalsIgnoreCase()忽略大小写比较字符串的内容是否相同
System.out.println("\"hello\".equalsIgnoreCase(\"HeLLO\"):"+str1.equalsIgnoreCase("HeLLO"));//true
// 4、compareTo()比较的
//①没有相等的字符
System.out.println("\"apple\".compareTo(\"banana\"):"+"apple".compareTo("banana"));//a=97 b =98 a-b=-1
//前k个字符相等,比较下一个
System.out.println("\"apple\".compareTo(\"apply\"):" + "apple".compareTo("apply"));//e=101 y=121 e-y=-20
System.out.println("\"apple\".compareTo(\"Apply\"):" + "apple".compareTo("Apply"));//a=97 A=65 a-A=32
// 5、AcompareToIgnoreCase()忽略大小写比较字符串的字典序
System.out.println("\"banana\".compareToIgnoreCase(\"Banana\"):"+"banana".compareToIgnoreCase("Banana"));//相等返回0
}
}
3.字符串查找
在帮助文档中可以看见大概有这些,不背,用时查多看多敲




java
public class Demo1 {
public static void main(String[] args) {
String str1 = new String("hello word");
String str2 = new String("abcabcabcabc");
// char charAt(int index)返回index位置的字符
System.out.println(str1.charAt(4));//("hello word")o
//利用charAt遍历字符串
System.out.println("******charAt**********");
for (int i = 0; i < str1.length(); i++) {
char print = str1.charAt(i);
System.out.print(print + " ");
}
System.out.println();
// int indexOf(String str)返回str在字符串中第一次出现的位置
System.out.println("******indexOf**********");
System.out.println("hello word:");
System.out.println("l第一次出现的位置:"+str1.indexOf("l"));//2
// int indexOf(String str,int fromIndex)返回str在字符串中第一次出现的位置,从fromIndex开始查找,[ , ) 前闭后开
System.out.println("从第3个字符开始查找l第一次出现的位置:"+str1.indexOf("l",3));// 3
// int indexOf(int ch)返回ch在字符串中第一次出现的位置
System.out.println("h第一次出现的位置:"+str1.indexOf('l'));//2
// int indexOf(int ch,int fromIndex)返回ch在字符串中第一次出现的位置,从fromIndex开始查找
System.out.println("从第3个字符开始查找h第一次出现的位置:"+str1.indexOf('h',3));//-1
// int lastIndexOf(String str)返回str在字符串中最后一次出现的位置,从后往前查找,int lastIndexOf(int ch)与之类似
System.out.println("******lastIndexOf**********");
System.out.println("l最初出现的位置:"+str1.indexOf("l"));//2
System.out.println("l最后出现的位置:"+str1.lastIndexOf("l"));//3
System.out.println("c最初出现的位置:"+str2.indexOf("c"));//2
System.out.println("c最后出现的位置:"+str2.lastIndexOf("c"));//11
// int lastIndexOf(String str,int fromIndex)返回str在字符串中最后一次出现的位置,从fromIndex开始查找,int lastIndexOf(int ch,int fromIndex)与之类似
System.out.println("从str2的第6个字符查找c:"+str2.lastIndexOf("c",6));//5
}
}
4.字符串转换(字符串数字相互转换、大小写转换、数组字符串想换转换)
java
public class Demo2 {
public static void main(String[] args) {
// 1.数字转字符串
String str1 = String.valueOf(123678);
String str2 = String.valueOf(123.578);
String str3 = String.valueOf(true);
// 通过String类new一个Student对象
String str4 = String.valueOf(new Student("yyt",20));
System.out.println("**********数字转字符串**********");
System.out.println(str1);
System.out.println(str2);
System.out.println(str3);
System.out.println(str4.toString());
// 2.字符串转数字
int num1 = Integer.parseInt(str1);
double num2 = Double.parseDouble(str2);
boolean num3 = Boolean.parseBoolean(str3);
boolean num4 = Boolean.parseBoolean(str3+"false");//ture+false=false
System.out.println("**********字符串转数字**********");
System.out.println(num1);
System.out.println(num2);
System.out.println(num3);
System.out.println(num4);
// 3.大小写的转换
String str5 = "Hello World";
String str6 = str5.toUpperCase();
String str7 = str5.toLowerCase();
System.out.println("**********大小写的转换**********");
// 小写转大写UpperCase
System.out.println(str5);
System.out.println("toUppercase: " + str6);
// 大写转小写LowerCase
System.out.println("toLowercase: " + str7);
// 4.字符串转字符数组
char[] chars = str5.toCharArray();
System.out.println("**********字符串转字符数组**********");
for (char ch: chars) {
System.out.print(ch + " ");
}
System.out.println();
// 5.字符数组转字符串
String str8 = new String(chars);
System.out.println("**********字符数组转字符串**********");
System.out.println(str8);
// 6.格式化
String str9 = String.format("Hello %s, your age is %d", "yyt", 20);/*像printf一样*/
System.out.println("**********格式化**********");
System.out.println(str9);
}
}
在Demo2类外还创建了个Student类

5.字符串替换
替换该字符串中与字面目标匹配的每个子串 与指定的字面替换序列。

java
public class Demo3 {
public static void main(String[] args) {
String str = "abcabcabc";
//只要是abc,就都替换成123
String ret1 = str.replace("abc", "123");//123123123
//只要是abc,就都替换成yyt
String ret2 = str.replace("abc", "yyt");//yytyytyyt
//ab全都替换
String ret3 = str.replaceAll("ab", "xx");//xxcxxcxxc
//只替换第一个c
String ret4 = str.replaceFirst("c", "111");//ab111abcabc
//字符char
String ret5 = str.replace('a','g');//gbcgbcgbc
System.out.println(str);
System.out.println("*****字符串替换*****");
System.out.println(ret1);
System.out.println(ret2);
System.out.println(ret3);
System.out.println(ret4);
System.out.println(ret5);
}
}
在替换的是普通无特殊含义的子串是replace和replaceAll得到一样的结果
注意当遇到有特殊含义的字符(正则表达式中有不同含义的), 比如 . 在正则里面是任何角色
replace(旧串, 新串)
replaceAll(正则, 新串)
replaceFirst(正则, 新串)

就像下列代码, . 可以代表a/b/c所以replaceAll直接都变成了00000,repalce则是不管正则的
replaceFirst和replaceAll一样

6.字符串拆分
将完整的字符串按照指定分隔符划分为若干个子字符串。
split(String regex)将字符串全部拆分

其实就是空格拆分,几个空格就拆分几次,然后一个字符串就被分成多个字符串
根据调试我们就可以详细的看见str1被分割成了两个字符串存储在数组中

split(String regex, int limit)将字符串以指定形式拆分为limit组


java
public class Demo4 {
public static void main(String[] args) {
String str1 = "hello world";
String str2 = "小华&小明&小红&老王";
String str3 = "小华\\小明\\小红\\老王";//注意:这里的反斜杠需要转义,即用两个反斜杠 \\*
//split(String regex)将字符串全部拆分
String[] strArr1 = str1.split(" ");//按空格拆分
String[] strArr2 = str2.split("&");
String[] strArr3 = str2.split("(?<=\\w)[&]");
String[] strArr5 = str3.split("\\\\");//注意:这里的反斜杠需要转义,即用两个反斜杠 \\\\
//split(String regex, int limit)将字符串以指定形式拆分为limit组
String[] strArr4 = str2.split("&",2);
System.out.println("*******str1 = \"hello world\"*******");
for (String str :strArr1) {
System.out.println(str);
}
System.out.println("*******str2 = \"小华&小明&小红&老王\"*******");
for (String str : strArr2) {
System.out.println(str);
}
System.out.println("*******str2 = \"小华&小明&小红&老王\"*******");
for (String str : strArr3) {
System.out.println(str);
}
System.out.println("*******str2 = \"小华&小明&小红&老王\"*******");
for (String str : strArr4) {
System.out.println(str);
}
// 如果是转义字符\,那么就要写成\\\\才行,如:String str3 = "小华\\小明\\小红\\老王";
System.out.println("*******str3 = \"小华\\小明\\小红\\老王\"*******");
for (String str : strArr5) {
System.out.println(str);
}
}
}

7.字符串的截取
将完整字符串截取部分内容
String substring(beginIndex)从指定索引截取到结尾
String substring(beginIndex, endIndex)截取部分内容 ([start,end)前闭后开)

trim()去除左右两端空格
二、字符串常量池
在Java中字面类型的常量经常被使用而为了使程序运行速度更快更节省内存Java给8种基本数据类型和string类都提供了常量池
在Java中为了提高效率还有很多的池(pool);比如内存池、线程池等
为了节省内存空间和提高程序的运行效率,Java中还有这些池:
class文件常量池(Constant pool):放字面量"小华"、"布丁"、123、类名、方法名
运行时常量池:把 .class常量池加载到内存
字符串常量池(StringTable):只存String对象的引用、放在堆、底层是HashTable
class文件常量池:每个 .class 文件都有,存的是这个类的字面量和符号引用。

这是之前的存放构造String的类Demo1的Demo1.class 文件内部,我们可以看见这就是Demo.class的常量池包含了很多类型
字符串常量池(StringTable)在JVM中是StringTable类,实际上是一个固定大小的HashTable不同JDK版本下的字符串常量池的位置是不同的
| JDK版本 | 字符串常量池位置 | 大小设置 |
|---|---|---|
| JDK1.0-1.6 | 方法区 | 1009桶 |
| JDK1.7 | 堆 | 1009桶 |
| JDK1.8-JDK21 | 堆 | 60013桶 |
字符串常量池=JVM里专门放字符串字面量的共享缓存池
目的:
相同类型的字符串,只创建一次,大家共用,节省内存
JVM优化String内存的缓存区域,intern()可手动入池,核心是复用字符串字面量
Qustion:常量池到底放什么?
1.字面量
String s1 = "abc";//进入常量池
2.调用了intern()的字符串
String s = new String("小华").intern();//强制入池
intern()作用:
去常量池找"小华"
找到→返回池中对象
没找到→把当前对象加入池,再返回
只new String("abc")不intern() , "abc"不会进池,只在堆里,没在池里
String s = new String("小华");//只在堆里,没有在池里
java
public class Demo6 {
public static void main(String[] args) {
// yyt 是字面值常量, 本身就是在常量池中的.
String str1 = "yyt";//在字符串常量池
String str2 = new String("yyt");//不在StringTable
System.out.println(str1 == str2);//false
System.out.println(str1 == str2.intern());//true
}
}
二、字符串不可变性
字符串不可变性:

Question: 当我将name改为 "布丁" 时,会不会报错?
不会,正常打印,没有问题

但String类是不可变类,不可改变,为啥能重新赋值,String是引用类型,不存储字符串,只存地址,name在栈中,"小华" 则在字符串的常量池中(属于堆),只是引用的指向发生改变

我们通过观察源码可以看见,String类的地址是没有变的,value被private final修饰,表明value是私有的,自身的值不能改变,即不能引用其他字符数组,但其引用空间的内容可以修改


四、StringBuffer(单线程高效)和 StringBuilder(多线程安全)
String:不可变
String是一个不可变类,它内部存储的字符序列不可变(value)
private final char[] value;
final修饰的数组引用不能指向新的数组,同时private是私有的外部拿不到这个变量
所以像"小华"、"布丁"
String name = "小华";
name = "布丁";
原来的"小华"对象并没有改变,而是name引用指向"布丁"这个新的对象
举个生动例子
java
public class Demo7 {
public static void main(String[] args) {
String str1 = "Hello";//name={String[0]@701},"Hello"={byte[5]@704}
// 这个操作本质上是创建了一个新的字符串"HelloWorld",而不是修改"Hello"
// str1指向了新对象"HelloWorld"
str1 += "World";//name={String[0]@701},"HelloWorld"={byte[10]@709}
System.out.println(str1);
}
}

StringBuff和StringBuilder:可变
StringBuff和StringBuilder:
StringBuffer和StringBuilder都继承自AbstractStringBuilder类,AbstractStringBuilder中也是使用字符数组保存字符串,是可变类,并且提供了一系列插入、追加、改变字符串里的字符序列的方法,它们的用法基本相同
两者用法:
StringBuilder.append:添加任意类型数据的字符串形式,并返回当前对象本身
java
public class Demo7 {
public static void main(String[] args) {
//创建一个StringBuilder对象
StringBuilder stringBuilder = new StringBuilder("Hello");
// 这个操作就是操作 StringBuilder对象本体,修改的""Hello""字符串并没有被创建新的对象
// StringBuilder的可变性
stringBuilder.append("World");
System.out.println(stringBuilder.toString());//HelloWorld
}
}
StringBuilder.reverse:逆置字符串
java
public class Demo1 {
public static void main(String[] args) {
//创建StringBuilder对象
StringBuilder stringBuilder = new StringBuilder();
//添加字符串,改变其本身可变性
stringBuilder.append("hello");
stringBuilder.append("world");
//StringBuilder.reverse逆置字符串
stringBuilder.reverse();
System.out.println(stringBuilder);//dlrowolleh
}
}
StringBuffer.append:添加任意类型数据的字符串形式,并返回当前对象本身
java
public class Demo2 {
public static void main(String[] args) {
//创建一个StringBuffer的对象
StringBuffer stringBuffer = new StringBuffer();
//添加任意类型数据以字符串的形式
stringBuffer.append("hello");
stringBuffer.append("world");
System.out.println(stringBuffer);//helloworld
}
}
StringBUffer.reverse:逆置字符串
java
public class Demo2 {
public static void main(String[] args) {
//创建一个StringBuffer的对象
StringBuffer stringBuffer = new StringBuffer();
//添加任意类型数据以字符串的形式,返回对象本身
stringBuffer.append("hello");
stringBuffer.append("world");
//逆置字符串
stringBuffer.reverse();
System.out.println(stringBuffer);//dlrowolleh
}
}
还有其他与String相同的常用方法
charAt(int index):获取index的字符
capacity():获取底层保护字符串的空间总大小
ensureCapacity():扩容
setCharAt(int index, char ch):将index位置设置为给定字符
indexOf(String str, int fromIndex):从fromIndex开始查找str第一次出现的位置,不存在返回-1
delete(int start, int end)删除start到end位置的字符
replace(int start, int end, String str) 和String的替换不同,直接修改对象不用返回对象
java
public class Demo9 {
public static void main(String[] args) {
StringBuilder stringBuilder = new StringBuilder("hello world");
//获取index为5的字符
System.out.println("*********charAt获取index为5的字符*********");
System.out.println(stringBuilder.charAt(7));//o
//获取底层保护字符串的空间总的大小
System.out.println("*********capacity获取底层保护字符串的空间总的大小*********");
System.out.println(stringBuilder.capacity());//27
//扩容
stringBuilder.ensureCapacity(60);
//将 index 位置的字符设置为给定的字符
System.out.println("*********setCharAt设置字符*********");
stringBuilder.setCharAt(5,'#');
System.out.println(stringBuilder);//hello#world
//返回str第一次出现的位置,如果不存在则返回-1
System.out.println("*********indexOf查找字符*********");
System.out.println(stringBuilder.indexOf("l"));//2
//从fromIndex开始查找str第一次出现的位置,如果不存在则返回-1
System.out.println("*********indexOf查找指定位置的字符*********");
System.out.println(stringBuilder.indexOf("o",5));//7
//delete(int start, int end) 删除start到end位置的字符
System.out.println("*********delete*********");
stringBuilder.delete(0,5);
System.out.println(stringBuilder);//#world
//和String 不同,直接修改对象不用返回对象
System.out.println("*********replace替换字符串*********");
stringBuilder.replace(5,11,"yyt");
System.out.println(stringBuilder);//#worlyyt
//字符串的逆序,修改对象本身
System.out.println("*********reverse逆序*********");
stringBuilder.reverse();
System.out.println(stringBuilder.toString());//tyylrow#
}
}
StringBuilder和StringBuffer两者的区别:
看append的源码可以发现:

两者像比较,StringBuffer更安全,线程安全(方法加了sychronized修饰),性能较低(要同步开销),适合在多线程环境下字符串的操作
StringBuilder线程不安全(方法没有被sychronized修饰),性能较高(没有同步开销),适合单线程环境字符串操作
三者效率比较:
StringBuilder > StringBuffer > String
安全性和操作数量比较:
操作数量小,优先使用String类
单线程大量数据,使用StringBuilder
多线程大量数据,StringBuffer
StringBuffer使用缓存区,StringBuilder没有缓存区,所以在没有修改数据情况下,多次调用StringBuffer的toString()方法获取字符串是共享底层的字符数组的,而StringBuilder不是共享fiction数组的,每次都生成了新的字符数组
String类和StringBuilder大量拼接字符串的性能效率比较:
我们可以通过用时间戳计时法, 比较两者用时时长谁更快,效率就更高
java
public class Demo3 {
public static void main(String[] args) {
//时间戳计时法,直观对比
//String和StringBuilder在大量字符串拼接操作时的执行效率差异
//定义两个初始长度相同的String和StringBuilder对象
String string = "";
StringBuilder stringBuilder = new StringBuilder("");
//用足够大的次数(10万次)才能明显体现String和StringBuilder的性能差异
int loopCount = 10_0000;//循环次数10,0000次
//获取毫秒级别时间戳
//System.currentTimeMillis()
// 会返回从1970.1.1 00:00:00到当前时间的毫秒数(long类型)
//开始前的时间
long startTime = System.currentTimeMillis();//开始前记录一个时间戳
// //String 类大量字符串拼接,循环次数10万次
// for (int i = 0; i < loopCount; i++) {
// string += i;
// }
//StringBuilder大量拼接字符串,循环次数10万次
for (int i = 0; i < loopCount; i++) {
stringBuilder.append(i);
}
//完成后的时间
long endTime = System.currentTimeMillis();//结束后记录时间戳
// System.out.println("String类大量拼接字符串的用时:" + (endTime - startTime));
System.out.println("StringBuilder类大量拼接字符串的用时:" + (endTime - startTime));
}
}
他们用时比较


可以看见StringBuilder类的效率远远大于(StringBuilder > String)String,在大量拼接字符串中
六、三者比较
| 特性 | String | StringBuff | StringBuilder |
|---|---|---|---|
| 可变性 | 不可变 | 可变 | 可变 |
| 线程安全 | 安全(不可变) | 安全(方法加sychronized) | 不安全 |
| 效率 | 低(频繁创建对象) | 中(同步开销) | 高(无同步) |
| 底层实现 | char[] /byte[](final) | char[] (可扩容) | char[](可扩容) |
| 适用场景 | 少量字符串操作 | 多线程环境频繁修改 | 单线程环境频繁修改 |
七、String类核心认知
不可变字符序列,字面量创建复用常量池,new创建堆对象,==比较地址,equals()比较内容
八、总结方法
String常用方法:
| 方法 | 作用 |
|---|---|
| length() | 返回字符串的长度 |
| charAt(int index) | 获取指定索引位置的字符 |
| equals(Object obj) | 比较两个字符串的内容是否相等 |
| equalsIgnoreCase(String another) | 忽略大小写比较两个字符串的内容 |
| indexOf(String str) | 查找子字符串首次出现的索引,找不到返回 -1 |
| lastIndexOf(String str) | 查找子字符串最后出现的索引,找不到返回 -1 |
| substring(int begin) | 从指定索引开始截取子串到末尾 |
| replace(char old, char new) | 将字符串中所有 old 字符替换为 new 字符 |
| replace( , ) | 将字符串中所有 target 子串替换为 replacement |
| trim() | 去除字符串首尾的空格 |
| split(String regex) | 按正则表达式分割字符串,返回字符串数组 |
| toLowerCase() | 将字符串转为小写 |
| toUpperCase() | 将字符串转为大写 |
| isEmpty() | 判断字符串是否为空(长度为0) |
| substring(int begin, int end) | 截取 [begin, end) 区间的子串 |
String类转为其他类型
类型包装类.parse类型(String s)
Integer.parseInt(String s)
Double.parseDouble(String s)
Boolean.parseBoolean(String s)
char数组:s.toCharArray()
其他类型转为String:
String.valueOf(其他类型)
StringBuilder和StringBuffer 常用方法:
| 方法 | 作用 |
|---|---|
| append(xxx) | 追加内容(字符串、数字、对象等)到末尾,支持链式调用 |
| insert(int offset, xxx) | 在指定索引位置插入内容(字符串、数字、对象等) |
| delete(int start, int end) | 删除 [start, end) 区间的字符 |
| deleteCharAt(int index) | 删除指定索引位置的字符 |
| replace(int start, int end, String str) | 将 [start, end) 区间的字符替换为 str |
| reverse() | 将字符串反转 |
| length() | 返回当前字符串的长度 |
| capacity() | 返回底层字符数组的容量 |
| charAt(int index) | 获取指定索引位置的字符 |
| toString() | 将 StringBuffer 转为 String 对象 |