JavaSE - 字符串
本节学习目标:
- 了解并掌握字符串对象的声明与初始化;
- 了解并掌握字符串使用运算符操作;
- 了解并掌握如何获取字符串信息;
- 了解并掌握字符串操作的有关方法;
- 了解并掌握字符串的格式化方法;
- 了解并掌握两种字符串生成器。
1. String 类
字符串(String
)是Java语言中的一个引用数据类型,String
类位于java.lang
包下。在Java中所有字符串字面量,如"ABCDEFG"
、"1234567"
等,
用两个双引号包括起来的一串字符,都是实现了String
类的一个实例,所以在Java语言中,字符串被看做是一个String
类的对象。
1.1 字符串的声明
字符串变量的声明方式:
String 标识符;
字符串字面量(常量)的声明方式:使用两个双引号包括:
"Hello World!"
,"Java Programming Language"
1.2 字符串变量的初始化
String
类提供了很多构造方法,常用的构造方法如下:
String(String original)
:引用字符串字面量初始化一个字符串变量:
String str = new String("Hello");
// 上面的等价于
String str = "Hello";
String(char[] value)
:使用char
类型的数组初始化一个字符串变量:
char[] charArr = new char[] {'H', 'e', 'l', 'l', 'o'};
String str = new String(charArr);
// 上面的等价于
String str = "Hello";
String(char[] value, int offset, int count)
:使用char
类型的数组中,将从索引为offset
开始的count
个元素拼接作为字符串:
char[] charArr = new char[] {'H', 'e', 'l', 'l', 'o'};
String str = new String(charArr, 1, 4);
// 上面的等价于
String str = "ello";
1.3 String 类的细节
String
类本身被final
关键字修饰,也就是说String
类本身不可被继承;- 查看源码我们会发现,字符串底层是用的字符型数组(
char[]
)实现的,这个字符型数组成员变量也被final
关键字修饰,所以说字符串一旦确定就不能更改。 - 直接使用相同的字符串字面量初始化的两个字符串引用,它们引用的是同一个字符串对象。使用
new
关键字通过构造方法的字符串引用,即使内容相同,引用的是不同的对象。
String str1 = "HelloWorld";
String str2 = "HelloWorld";
String str3 = new String("HelloWorld");
System.out.println(str1 == str2);
System.out.println(str1 == str3);
// 运行结果
true
false
2. 连接字符串
字符串与字符串之间可以使用+
运算符进行拼接操作,将两个字符串首尾相连拼接在一起成为一个新的字符串:
String str1 = "Hello";
String str2 = "World!";
String str3 = str1 + " " + str2;
// 上面的等价于
String str3 = "Hello World!";
如果字符串字面量太长需要换行,在Java语言中不能直接换行,需要使用+
运算符进行拼接:
// 错误的换行
String str = "Hello
World!";
// 正确的换行
String str = "Hello" +
" World!";
字符串也可以和其他数据类型之间使用+
运算符进行运算,运算结果为字符串类型,实质是将其他数据类型转换为字符串类型进行拼接:
int i = 59;
String str = "Hello" + i;
// 上面的等价于
String str = "Hello59";
只要+
运算符两侧有一个为字符串类型,则运算结果就为字符串类型,所以需要多个操作数进行运算时要注意先后顺序,以免出现错误的结果。
3. 获取字符串信息
String
类提供了很多方法来获取字符串的信息。
3.1 获取字符串长度
int length()
方法可以获取字符串对象的长度。
String str = "Hello World!";
System.out.println(str.length());
// 运行结果
12
3.2 查找子字符串
int indexOf(String str)
方法与int indexOf(String str, int fromIndex)
方法:
第一个方法用于返回字符串str
在指定字符串中首次出现的索引位置,会从指定字符串的开始位置开始从前往后查找。如果没有查找到,返回-1
;
第二个方法可以指定查找的开始位置fromIndex
。
String str = "AAA123AAA123AAA";
System.out.println(str.indexOf("123"));
System.out.println(str.indexOf("123", 4));
// 运行结果
3
9
int lastIndexOf(String str)
方法与int lastIndexOf(String str, int fromIndex)
方法:
第一个方法用于返回字符串str
在指定字符串中最后一次出现的索引位置,会从指定字符串的末尾位置开始从后往前查找,如果没有查找到,返回-1
;
第二个方法可以指定查找的开始位置fromIndex
。
String str = "AAA123AAA123AAA";
System.out.println(str.lastIndexOf("123"));
System.out.println(str.lastIndexOf("123", 8));
// 运行结果
9
3
3.3 返回指定索引处的字符
char charAt(int index)
方法返回指定索引处的字符。
String str = "ABCDEFG";
System.out.println(str.charAt(4));
// 运行结果
E
4. 字符串的操作方法
String
类提供了很多操作字符串的方法。
4.1 获取子字符串
String substring(int beginIndex)
方法与String substring(int beginIndex, int endIndex)
方法:
第一个方法返回从指定的索引位置beginIndex
开始截取直到该字符串结尾的子字符串;
第二个方法返回从指定的索引位置beginIndex
开始截取直到索引位置endIndex
结尾的子字符串。
String str = "HelloWorld!";
System.out.println(str.substring(3));
System.out.println(str.substring(4, 8));
// 运行结果
loWorld!
oWor
4.2 去除空格
String trim()
方法返回字符串的副本,忽略字符串开头与结尾的空格,字符与字符中间的空格不会被忽略。
String str = " Hello World! ";
System.out.println("原来的字符串:" + str + ",它的长度" + str.length())
System.out.println("trim后字符串:" + str.trim() + ",它的长度" + str.trim().length());
// 运行结果
原来的字符串: Hello World! ,它的长度18
trim后字符串:Hello World!,它的长度12
4.3 字符串替换
String replace(char oldChar, char newChar)
方法与String replace(CharSequence target, CharSequence replacement)
方法。
第一个方法返回将原字符串内的指定字符oldChar
替换为新字符newChar
之后的字符串;
第二个方法返回将原字符串内的指定字符序列target
替换为新字符序列replacement
之后的字符串(字符序列可以看做字符串)。
如果一个字符串中出现多次oldChar
或target
,则两者会全部进行替换。
String str = "Hello World!";
System.out.println(str.replace('l', 'A'));
System.out.println(str.replace("o", "OOO"));
// 运行结果
HeAAo WorAd!
HellOOO WOOOrld!
4.4 判断字符串的开头与结尾
boolean startsWith(String prefix)
方法与boolean endsWith(String suffix)
方法。
startsWith()
方法判断字符串是否以指定的内容开始,endsWith()
方法判断字符串是否以指定的内容结束。
String str = "Hello World";
System.out.println("字符串str是否以“He”开头?" + str.startsWith("He"));
System.out.println("字符串str是否以“d!”结尾?" + str.endsWith("d!"));
// 运行结果
字符串str是否以“He”开头?true
字符串str是否以“d!”结尾?false
4.5 判断字符串是否相等
由于字符串为引用数据类型,所以不能直接使用==
运算符判断。
String
类提供了两种方法比较字符串是否相等:
- 重写
Object
类提供的boolean equals(Object obj)
方法:
String
类的equals()
方法判断步骤:
①直接使用==
运算符判断对象obj
与本字符串对象的地址是否相同,如果是则返回true
;
②使用instanceof
关键字判断对象obj
是否是String
类的一个实例,如果不是则返回false
;
③将对象obj
转换为字符串,比较obj
的字符串形式的长度与本字符串长度是否相同,如果不相同则返回false
;
④将对象obj
的字符串形式的每个字符和本字符串的每个字符一一比对,如果全都相同则返回true,否则返回false。
equalsIgnoreCase(String anotherString)
方法:与equals()
方法判断步骤一样,但是忽略了字母的大小写。
String str1 = "HelloWorld";
Object str2 = new String("helloworld");
System.out.println("equals()方法判断:" + str1.equals(str2));
System.out.println("equalsIgnoreCase()方法判断:" + str1.equalsIgnoreCase(str2.toString()));
// 运行结果
equals()方法判断:false
equalsIgnoreCase()方法判断:true
4.6 按字典顺序比较两个字符串
int compareTo(String anotherString)
方法按字典顺序比较两个字符串,该比较基于字符串中每个字符的Unicode值,
按字典顺序将本字符串与字符串anotherString
进行比较,如果按字典顺序本字符串在anotherString
之前,则返回一个负整数,
之后则返回一个正整数,如果本字符串使用equals()
方法与anotherString
字符串比较为true
时,compareTo()
方法返回0
。
String str1 = "a";
String str2 = "b";
String str3 = "b";
String str4 = "c";
System.out.println("b与a比较" + str2.compareTo(str1));
System.out.println("b与b比较" + str2.compareTo(str3));
System.out.println("b与c比较" + str2.compareTo(str4));
// 运行结果
b与a比较1
b与b比较0
b与c比较-1
4.7 字母大小写转换
String toLowerCase()
方法将本字符串中的大写字母转换为小写字母后返回该字符串;
String toUpperCase()
方法将本字符串中的小写字母转换为大写字母后返回该字符串。
String str = "Hello World!";
System.out.println(str.toLowerCase());
System.out.println(str.toUpperCase());
// 运行结果
hello world!
HELLO WORLD!
4.8 字符串分割
String[] split(String regex)
方法和String[] split(String regex, int limit)
方法。
第一个方法可以使字符串按指定的字符,字符串或正则表达式分割成子字符串数组。第二个方法可以指定分割后的子字符串数组的元素个数。
String str = "HelloWorld!";
System.out.println(Arrays.toString(str.split("o")));
System.out.println(Arrays.toString(str.split("o", 2)));
// 运行结果
[Hell, W, rld!]
[Hell, World!]
5. 字符串的格式化
String
类提供了静态方法format()
用于格式化字符串,format()
方法有两种重载形式:
-
String format(String format, Object... args)
format
:格式化的字符串,使用格式化转换符;args
:格式化转换符引用的参数,参数的个数是可变的,如果参数个数多于格式化转换符个数,则剩下的参数个数将会被忽略,参数个数可以为0。
-
String format(Locale l, String format, Object... args)
l
:格式化时使用的语言环境,指定为null
时不进行本地化;format
:格式化的字符串,使用格式化转换符;args
:格式化转换符引用的参数,参数的个数是可变的,如果参数个数多于格式化转换符个数,则剩下的参数个数将会被忽略,参数个数可以为0。
5.1 日期字符串格式化
常用的日期格式化转换符:
日期格式化转换符 | 说明 | 示例 |
---|---|---|
%te | 一个月中的某一天 | 2 |
%tb | 指定语言环境的月份简称 | Feb、二月 |
%tB | 指定语言环境的月份全称 | February、二月 |
%tA | 指定语言环境的星期全称 | Monday、星期一 |
%ta | 指定语言环境的星期简称 | Mon、星期一 |
%tc | 全部日期和时间信息 | 星期二 三月 25 13:37:22 CST 2017 |
%tY | 4位年份 | 2017 |
%tj | 一年中的第几天 | 076 |
%tm | 一年中的第几月 | 07 |
%td | 一个月中的第一天 | 15 |
%ty | 2位年份 | 17 |
编写代码进行测试:
import java.util.Date;
import java.util.Locale;
public class TestDateFormat {
public static void main(String[] args) {
Locale.setDefault(Locale.ENGLISH); // 设置当前语言环境为英文环境
Date date = new Date(); // 获取当前日期的对象
System.out.println(String.format("%te", date));
System.out.println(String.format("%tb", date));
System.out.println(String.format("%tB", date));
System.out.println(String.format("%tA", date));
System.out.println(String.format("%ta", date));
System.out.println(String.format("%tc", date));
System.out.println(String.format("%tY", date));
System.out.println(String.format("%tj", date));
System.out.println(String.format("%tm", date));
System.out.println(String.format("%td", date));
System.out.println(String.format("%ty", date));
}
}
运行结果:
2
Nov
November
Tuesday
Tue
Tue Nov 02 19:36:57 CST 2021
2021
306
11
02
21
5.2 时间字符串格式化
常用的时间格式化转换符:
时间格式化转换符 | 说明 | 示例 |
---|---|---|
%tH | 24小时制的2位小时数(有0占位,00~23) | 14 |
%tI | 12小时制的2位小时数(有0占位,01~12) | 06 |
%tk | 24小时制的2位小时数(无0占位,0~23) | 9 |
%tl | 12小时制的2位小时数(无0占位,1~12) | 7 |
%tM | 2位分钟数(00~59) | 10 |
%tS | 2位秒钟数(00~60) | 09 |
%tL | 3位毫秒数(000~999) | 457 |
%tN | 9位微秒数(000000000~999999999) | 062000000 |
%tp | 指定语言环境的上下午标记 | pm、下午 |
%tz | GMT时区偏移量 | +0900 |
%tZ | 时区缩写形式的字符串 | CST |
%ts | UNIX时间戳秒钟数(从1970-01-01 00:00:00至今经过的秒钟数) | 1635853859 |
%tQ | UNIX时间戳毫秒数(从1970-01-01 00:00:00至今经过的毫秒数) | 1635853859077 |
编写代码进行测试:
import java.util.Date;
import java.util.Locale;
public class TestTimeFormat {
public static void main(String[] args) {
Locale.setDefault(Locale.SIMPLIFIED_CHINESE); // 指定当前语言环境为简体中文环境
Date date = new Date(); // 获取当前日期的对象
System.out.println(String.format("%tH", date));
System.out.println(String.format("%tI", date));
System.out.println(String.format("%tk", date));
System.out.println(String.format("%tl", date));
System.out.println(String.format("%tM", date));
System.out.println(String.format("%tS", date));
System.out.println(String.format("%tL", date));
System.out.println(String.format("%tN", date));
System.out.println(String.format("%tp", date));
System.out.println(String.format("%tz", date));
System.out.println(String.format("%tZ", date));
System.out.println(String.format("%ts", date));
System.out.println(String.format("%tQ", date));
}
}
运行结果:
19
07
19
7
50
59
077
077000000
下午
+0800
CST
1635853859
1635853859077
5.3 日期时间组合的格式化
常用的日期和时间组合的格式化转换符:
日期时间组合格式化转换符 | 说明 | 示例 |
---|---|---|
%tF | “年-月-日”格式(4位年份) | 2021-11-02 |
%tD | “月/日/年”格式(2位年份) | 11/02/21 |
%tc | 全部日期和时间信息 | 星期二 十一月 02 20:02:20 CST 2021 |
%tr | “时:分:秒 上下午”格式(12小时制) | 08:02:20 下午 |
%tT | “时:分:秒”格式(24小时制) | 20:02:20 |
%tR | “时:分”(24小时制) | 20:02 |
编写代码进行测试:
import java.util.Date;
import java.util.Locale;
public class TestDateTimeFormat {
public static void main(String[] args) {
Locale.setDefault(Locale.ENGLISH); // 设置当前语言环境为英文环境
Date date = new Date(); // 获取当前日期的对象
System.out.println(String.format("%tF", date));
System.out.println(String.format("%tD", date));
System.out.println(String.format("%tc", date));
System.out.println(String.format("%tr", date));
System.out.println(String.format("%tT", date));
System.out.println(String.format("%tR", date));
}
}
运行结果:
2021-11-02
11/02/21
Tue Nov 02 20:02:20 CST 2021
08:02:20 PM
20:02:20
20:02
5.4 基本类型字符串格式化
基本类型的格式化可以应用于任何参数类型,格式化转换符一览:
格式化转换符 | 说明 | 示例 |
---|---|---|
%b 、%B | 结果被格式化为布尔类型 | false |
%h 、%H | 结果被格式化为散列码(Hash值) | eada0001 |
%s 、%S | 结果被格式化为字符串类型 | 266.6666666666667 |
%c 、%C | 结果被格式化为字符类型 | ‘N’ |
%d | 结果被格式化为十进制整数 | 266 |
%o | 结果被格式化为八进制整数 | 412 |
%x 、%X | 结果被格式化为十六进制整数 | 10a |
%e | 结果被格式化为科学计数法十进制数 | 2.666667e+02 |
%a | 结果被格式化为带有有效位数和指数的十六进制浮点数 | 266.666667 |
%n | 结果为换行符 | |
%<n>.f | 结果被格式化为有n位小数的浮点数 | 266.667 |
%% | 结果为字面值% | % |
编写代码进行测试:
public class TestBaseFormat {
public static void main(String[] args) {
System.out.println("3>5 格式化 %b 为 " + String.format("%b", 3>5));
System.out.println("1 格式化 %b 为 " + String.format("%b", 1));
System.out.println("800/3.0 格式化 %h 为 " + String.format("%h", 800/3.0));
System.out.println("800/3.0 格式化 %s 为 " + String.format("%s", 800/3.0));
System.out.println("78 格式化 %c 为 " + String.format("%c", 78));
System.out.println("800/3 格式化 %d 为 " + String.format("%d", 800/3));
System.out.println("800/3 格式化 %o 为 " + String.format("%o", 800/3));
System.out.println("800/3 格式化 %b 为 " + String.format("%x", 800/3));
System.out.println("800/3.0 格式化 %b 为 " + String.format("%e", 800/3.0));
System.out.println("800/3.0 格式化 %.3f 为 " + String.format("%.3f", 800/3.0));
System.out.println("800/3.0 格式化 %.6f 为 " + String.format("%.6f", 800/3.0));
System.out.println("%n 为 " + String.format("%n"));
System.out.println("%% 为 " + String.format("%%"));
}
}
运行结果:
3>5 格式化 %b 为 false
1 格式化 %b 为 true
800/3.0 格式化 %h 为 eada0001
800/3.0 格式化 %s 为 266.6666666666667
78 格式化 %c 为 N
800/3 格式化 %d 为 266
800/3 格式化 %o 为 412
800/3 格式化 %b 为 10a
800/3.0 格式化 %b 为 2.666667e+02
800/3.0 格式化 %.3f 为 266.667
800/3.0 格式化 %.6f 为 266.666667
%n 为
%% 为 %
6. 字符串生成器
查看源码我们了解到String
类底层使用了字符型数组来存储字符串,但是用来存储字符串的字符型数组成员变量被final
关键字修饰,
也就意味着字符串对象一旦完成初始化,它的长度就被固定了,并且内容不能被改变。
在Java中虽然可以使用+
运算符进行字符串的拼接,但是这种方法会产生一个新的String
实例,会在内存中创建新的String
对象,
如果重复的使用+
运算符对字符串进行修改,将会极大地增加系统资源开销。
为了解决这个问题,Java提供了两种可变的字符串序列类StringBuffer
和StringBuilder
,它们也被称为字符串生成器,可以对字符串进行多次修改且不会增加新的对象,
大大提高了频繁修改字符串的效率。
6.1 StringBuffer 类与 StringBuilder 类的区别
AbstractStringBuilder
抽象类位于java.lang
包下,它是StringBuffer
类和StringBuilder
类的父类。
String
类,StringBuffer
类与StringBuilder
类的继承树:
AbstractStringBuilder
抽象类已经定义好了绝大多数字符串生成器使用的方法,唯一的抽象方法为toString()
。
StringBuffer
的方法都被synchronized
关键字修饰,因此StringBuffer
是线程安全的,但执行效率要比StringBuilder
低;StringBuilder
的方法没有被synchronized
关键字修饰,因此StringBuffer
是非线程安全的,所以执行效率要比StringBuffer
高;StringBuilder
和StringBuffer
都继承于AbstractStringBuilder
抽象类,所以绝大多数方法的使用方式相同;- 在大多数情况下建议使用
StringBuilder
,因为它执行效率高。但是在多线程环境下必须使用StringBuffer
,因为它是线程安全的。
6.2 字符串生成器变量的初始化
以StringBuilder
为例,常用的构造方法如下:
StringBuilder()
:初始化一个空内容的生成器,默认设置生成器的容量为16
。StringBuilder(int capacity)
:初始化一个空内容的生成器,可以指定生成器的容量;StringBuilder(String str)
:使用字符串str
初始化一个生成器,此生成器的容量为str的长度+16;StringBuilder(CharSequence seq)
:使用字符序列seq
初始化一个生成器,此生成器的容量为seq的长度+16;
6.3 字符串生成器的容量
字符串生成器是可变长度的,所以它们的容量(Capacity)可以改变。
使用无参构造方法初始化的生成器的默认容量为16
个字符:
StringBuilder sb = new StringBuilder();
System.out.println(sb.capacity());
// 运行结果
16
当生成器的容量不足时,会自动扩容当前容量,扩容规则:
- 每次新增或插入新字符序列时会对当前容量进行判断;
- 确定需要的最小容量(已经存储的字符串的长度加上准备存储的字符串的长度)是否大于当前容量,如果大于就扩容;
- 容量扩充至当前容量的两倍+2,再次判断需要的最小容量是否大于当前容量,如果小于就扩容完成,如果大于则扩容至需要的最小容量;
- 检测扩容后的容量(即
char
数组的长度)是否超过允许的最大值(Integer.MAX_VALUE - 8
),如果超过则抛出异常(OutOfMemoryError
)。
StringBuilder sb = new StringBuilder();
System.out.println(sb.capacity());
sb.append("ABCDEFGHIJKLMNOPQ");
// 添加17个字符,按照扩容规则16*2+2=34 > 17,所以扩容至当前容量的两倍
System.out.println(sb.capacity());
sb.append("ABCDEFGHIJKLMNOPQABCDEFGHIJKLMNOPQABCDEFGHIJKLMNOPQABC");
// 再添加54个字符,按照扩容规则34*2+2=70 < 17+54=71,所以扩容至最小容量
// 运行结果
16
34
71
6.4 字符串生成器的常用方法
以StringBuilder
为例,常用的方法如下:
-
length()
:获取生成器中当前字符串的长度。 -
capacity()
:获取生成器的当前容量。 -
trimToSize()
:将生成器的当前容量缩小至当前字符串的长度。
StringBuilder sb = new StringBuilder();
sb.append("HelloWorld!");
System.out.println(sb.capacity());
sb.trimToSize();
System.out.println(sb.capacity());
// 运行结果
16
11
append(Object obj)
:新增方法有很多重载形式,几乎支持所有数据类型的新增操作,此方法直接把任何对象的字符串形式增加到当前字符串的尾部。
StringBuilder sb = new StringBuilder("Hello");
sb.append(1);
sb.append(false);
sb.append("World");
System.out.println(sb.toString());
// 运行结果
Hello1falseWorld
insert(int offset, Object obj)
:插入方法也有很多重载形式,此方法将对象的字符串形式插入至索引offset
位置。
StringBuilder sb = new StringBuilder("Hello");
sb.insert(3, "123");
System.out.println(sb.toString());
// 运行结果
Hel123lo
delete(int start, int end)
:删除方法,删除索引start
到索引end
之间的字符串;deleteChatAt(int index)
:删除指定位置方法:删除索引index
位置的字符。
StringBuilder sb = new StringBuilder("HelloWorld!");
sb.delete(1, 5);
System.out.println(sb.toString());
sb.deleteCharAt(5);
System.out.println(sb.toString());
// 运行结果
HWorld!
HWorl!
replace(int start, int end, String str)
:替换方法:将索引start
到索引end
之间的字符串替换为指定字符串。
StringBuilder sb = new StringBuilder("HelloWorld!");
sb.replace(1, 5, "0");
System.out.println(sb.toString());
// 运行结果
H0World!
reverse()
:反转方法:将当前字符串反转。
StringBuilder sb = new StringBuilder("HelloWorld!");
sb.reverse();
System.out.println(sb.toString());
// 运行结果
!dlroWolleH