一、String类概述
1.String是java定义好的一个类,定义在java.lang包(java核心包)下,所以使用时不需要导包。
2.java程序中所有的字符串文字(例如"abcd"),都被视为此类的对象。
3.字符串不可变,它们的值在创建后不能被更改。(下面会详细解释)
二、创建字符串对象方式
1.使用直接赋值的方式获取一个字符串对象
注:用该方法时,系统会先检查该字符串是否在串池(字符串常量池)中存在。
不存在,创建新的,将地址传给变量
存在,将地址传给变量
优点:字符串重复,会复用,节约内存
public class StringDemo {
public static void main(String[] args) {
String s1 = "abc";
String s2 = "abc";
}
}
起始串池为空,对于s1,在串池中开辟一个新的空间存放"abc",将地址0x0011赋值给s1。
对于s2,会先检查串池中是否存在"abc",发现存在后,将地址0x0011赋值给s2。
这种情况下只创建了一个对象,s1和s2都指向串池中的同一个对象。
为什么说字符串不可变?
字符串的内容是不会发生改变的,他的对象在创建后不会被更改。
举个🌰🌰🌰
public static void main(String[] args) {
String name = "zhangsan";
name = "lisi";
}
给一个已有字符串"zhangsan",第二次重新赋值成"lisi",不是在原内存地址上修改数据,而是重新指向一个新对象,新地址。
所以一共创建了两个字符串对象,原来的字符串对象"zhangsan"仍在,并未修改。
2.使用new的方式来获取一个字符串对象
注:通过该方法,字符串不放在串池中,而是在直接放在堆中。(例子见③)
每new一次,就会在堆中开辟一个新的空间,所以这种方式不会复用,会浪费内存空间。
①空参构造:可以活动一个空白的字符串对象-->""
String str1 = new String();
System.out.println("a" + str1 + "b");//ab
②传递一个字符串,根据传递的字符串内容在创建一个新的字符串对象(没有必要,用的最少)
String str2 = new String("abc");
System.out.println(str2);//abc
③传递一个字符数组,根据字符数组的内容在创建一个新的字符对象。
应用场景:由于字符串不可修改,所以如果想修改字符串的内容,可以先将字符串拆成字符数组,修改数组中的内容后,再利用该方法进行拼接。
String str="dbca";
//toCharArray方法可以将字符串变成字符数组
char[] data=str.toCharArray();
//交换第一个和最后一个元素
char temp = data[0];
data[0]=data[data.length-1];
data[data.length-1]=temp;
String str3 = new String(data);
System.out.println(str3);//abcd
内存模型:
所以,每new一次,就会在堆中开辟一个新的空间,字符串对象不会复用。
④传递一个字节数组,根据字节数组的内容去ASCII表中查找对应字符,在创建成一个字符对象。
应用场景:在网络中传输的数据都是字节信息,一般要把字节信息进行转换通过该方法转成字符串
byte[] bytes = {97, 98, 99, 100, 101};
String str4 = new String(bytes);
System.out.println(str4);//abcde
三、字符串的比较
1.==比较的是什么
①比较基本数据类型:比的是数据值
②比较引用数据类型:比的是地址值
public class CompareString {
public static void main(String[] args) {
String s1=new String("abc");
String s2="abc";
System.out.println(s1==s2);//false
}
}
由上可知,s1是在堆内存中创建的字符串对象,而s2实在串池中创建的字符串对象。两个对象的地址是不一样的,所以为false。
2.equals()比较
比较字符串对象的内容是否严格相等(不忽略大小写)
public class CompareString {
public static void main(String[] args) {
String s1=new String("abc");
String s2="abc";
String s3="ABC";
boolean result1=s1.equals(s2);
System.out.println(result1);//true
boolean result2=s1.equals(s3);
System.out.println(result2);//false
}
}
3.equalsIgnoreCase()比较
比较字符串对象的内容是否相等(忽略大小写)
public class CompareString {
public static void main(String[] args) {
String s1=new String("abc");
String s2="abc";
String s3="ABC";
boolean result3=s1.equalsIgnoreCase(s3);
System.out.println(result3);//true
}
}
Test:模拟登录系统,键盘输入一个字符串abc,再在代码中定义一个字符串abc,用==比较,是否一样?
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
System.out.println("请输入一个字符串");
String str1=sc.next();
String str2="abc";
System.out.println(str1==str2);//false
}
原因:通过源码发现,next的底层逻辑是通过new string()出来的
结论:只要涉及字符串比较,就必须用string类中的方法。
四、字符串的遍历,截取与替换
1.遍历
两个函数:
①length()
在数组中length是数组的属性,不用加();而在字符串中,length是string类的方法,需要加()
②charAt() --> 根据索引返回字符
import java.util.Scanner;
public class IterateString {
public static void main(String[] args) {
//键盘录入一个字符串
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串");
String str = sc.next();
//统计字符串中大写字母,小写字母,数字的个数
int bigCount = 0;
int smallCount = 0;
int numCount = 0;
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
//char类型的变量在参与比较或者计算的时候会自动提升为int型(根据ASCII码表转换)
if (c >= 'a' && c <= 'z') {
smallCount++;
} else if (c >= 'A' && c <= 'Z') {
bigCount++;
}else if(c >= '0' && c <= '9'){
//这里不能写c >= 0 && c <= 9 --> 0所对应的ASCII码是48
numCount++;
}
}
System.out.println("小写字母有"+smallCount+"个");
System.out.println("大写字母有"+bigCount+"个");
System.out.println("数字字母有"+numCount+"个");
}
}
2.截取
注:由于字符串不可修改,调用函数后并未对原字符串进行修改,而是返回一个新的截取后的字符串对象
①subString(开始索引,结束索引) 包头不包尾,包左不包右
②subString(开始索引) 默认截取到最后
public class SubString {
public static void main(String[] args) {
//手机号屏蔽:13112349468-->131****9468
String phone = "13112349468";
String start = phone.substring(0, 3);//131
String end = phone.substring(7);//9468
String result = start + "****" + end;
System.out.println(result);//131****9468
}
}
3.替换
注:由于字符串不可变,原字符串并没有被修改,而是返回了一个新的被替换后的字符串对象
replace(旧值,新值);
public class ReplaceString {
public static void main(String[] args) {
String talk = "NM,你真厉害,TMD";
String[] arr = {"TMD", "NM"};
//循环依次替换每一个敏感词
for (int i = 0; i < arr.length; i++) {
talk = talk.replace(arr[i], "***");
}
System.out.println(talk);//***,你真厉害,***
}
}
4.判断首字母
startsWith("xxx") 判断字符串是否以xxx开头,是返回true,不是返回false
String str = "abc";
boolean result = str.startsWith("ab");
System.out.print(result);//true
5.正则表达式
matches("正则表达式") 校验字符串是否满足规则,满足返回true,不满足返回false
具体细节见:
java正则表达式https://blog.csdn.net/xpy2428507302/article/details/139185880?spm=1001.2014.3001.5502
五、StringBuilder类
1.引言
String的缺点:由于字符串不可变,每次对字符串进行修改都会创建一个新的String对象(见六),导致频繁的内存分配和垃圾回收。所以在大量修改或者拼接字符串时,内存开销会非常大。
StringBuilder可以看成是一个容器,创建之后里面的内容是可变的。
原因:StringBuilder的容量是动态调整的。规则如下:
作用:提高字符串的操作效率(见六3)
应用场景:
①字符串的拼接
②字符串的反转
2.构造方法
3.常用方法
注:
①由于StringBuilder是可变的,有时为了避免对象被修改,需要使用toString()方法将其转换为不可变的String对象再传递或返回。
②因为StringBuilder是java已经写好的类,在底层做了一些特殊处理,使得打印输出的不是地址值,而是属性值(内容)
③由于StringBuilder是可变的,调用方法修改后会直接对容器中的字符串发生改变
public class StringBuilderDemo {
public static void main(String[] args) {
StringBuilder sb=new StringBuilder();
System.out.println(sb);//空字符串
//添加字符串
sb.append(1).append(2.3).append(true);
System.out.println(sb);
//反转
sb.reverse();
System.out.println(sb);
//获取长度
int len=sb.length();
System.out.println(len);
//把StringBuilder变回字符串
String str=sb.toString();
System.out.println(str);
}
}
运行结果:
六、字符串拼接的底层原理
在编译时,java会做一个检查,检查是否有变量参与拼接
1.如果没有变量参与,都是字符串直接相加
触发字符串的优化机制,编译之后就已经是最终拼接后的结果。
String s="a"+"b"+"c";等价于String s="abc";
所以字符串是在串池中创建的,后面会复用串池中的字符串。
Test:下列带代码运行结果是?
由上可知,String s2 = "a"+"b"+"c";等价于String s2 = "abc"; s1和s2为直接赋值的方式,s1所创建的字符串变量会存储在串池中,留给s2复用,所以此时s1和s2指向同一个对象,所以答案为true。
2.如果有变量参与
JDK8之前:
对于 s2 = s1 + "b"; 首先创建一个StringBuilder对象,通过append方法将s1和"b"中的内容都添加到该对象中,在通过toString方法变回字符串对象。
s3 = s2 + "c"; 同理
需要注意的是,toString方法的底层实际上是new string(),所以每一个+号都等于一次性创建了两个对象(一个StringBuilder对象和一个String对象),浪费内存。
JDK8之后:
先去预估最终字符串的长度,在根据长度创建一个数组,将内容填充到数组中后,再去根据该数组创建一个字符串对象。
需要注意的是,虽然JDK8之后进行了优化,但每次拼接时还是会new String()创建一个新的字符串对象,仍会造成内存浪费。
结论:
如果很多字符串拼接,不要直接+。每一次+都会创建对象,多个+会在底层创建多个对象,浪费时间,浪费性能
Test:下列带代码运行结果是?
由上可知,+拼接最终一定会new String()产生一个字符串对象,该对象s3存储在堆内存中,而s1直接赋值存储在串池中,所以两者地址不同,答案为false。
3.StringBuilder提高效率的原理图
StringBuilder是一个内容可变的容器,通过append方法将所有的要拼接的数据往同一个StringBuilder对象中存储。至始至终都只有一个对象,而不是像上述String一样多次拼接后产生了多个对象,从而减小了内存开销。
七、StringJoiner类
1.概述
JDK8后出现的一个可变的操作字符串的容器,可以更高效、方便的拼接字符串。
其性质和StringBuilder类似,注意点基本都和StringBuilder一样。
在拼接的时候,可以指定间隔符号,开始符号,结束符号。
2.构造方法
3.常用方法
public class StringJoinerDemo {
public static void main(String[] args) {
StringJoiner sj1=new StringJoiner("---");
StringJoiner sj2=new StringJoiner(", ","[","]");
System.out.println(sj1);//空字符串
System.out.println(sj2);//[]
//添加数据
sj1.add("aaa").add("bbb").add("ccc");
sj2.add("a").add("b").add("c");
System.out.println(sj1);
System.out.println(sj2);
//获取长度
int len1=sj1.length();
int len2=sj2.length();
System.out.println(len1);//15
System.out.println(len2);//9
//把StringJoiner变回字符串
String str1=sj1.toString();
String str2=sj2.toString();
System.out.println(str1);
System.out.println(str2);
}
}
运行结果: