String类常用API
# String类常用API
前言
- 字符串长度:
str.length()
(方法,需要加括号)。 - 数组长度:
arr.length
(属性,不加括号)。
记住:字符串是方法,数组是属性!
# 1. 字符串构造方法
String()
:构造一个空的String
对象,表示一个空字符序列。String(char[] value)
:使用字符数组构造一个新的String
对象。String(byte[] bytes)
:通过使用平台的默认字符集解码字节数组来构造一个新的String
对象。String(byte[] bytes, int offset, int length)
:使用指定的偏移量和长度从字节数组构造一个新的String
对象。String(char[] value, int offset, int count)
:从字符数组的指定子数组构造一个新的String
对象。
public class StringConstructorDemo {
public static void main(String[] args) {
// 构造一个空字符串
String emptyString = new String();
System.out.println("空字符串: [" + emptyString + "]");
// 使用字符数组构造字符串
char[] charArray = {'你', '好', ',', '世', '界'};
String charArrayString = new String(charArray);
System.out.println("使用字符数组构造的字符串: " + charArrayString);
// 使用字节数组构造字符串,字节数组表示 "你好"
byte[] byteArray = { -28, -67, -96, -27, -91, -67 }; // 对应 "你好"
String byteArrayString = new String(byteArray);
System.out.println("使用字节数组构造的字符串: " + byteArrayString);
// 使用字节数组的子数组构造字符串
String subByteArrayString = new String(byteArray, 0, 3); // 输出 "你"
System.out.println("使用字节数组的子数组构造的字符串: " + subByteArrayString);
// 使用字符数组的子数组构造字符串
String substringString = new String(charArray, 1, 3); // 输出 "好,世"
System.out.println("使用字符数组的子数组构造的字符串: " + substringString);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
实际开发场景:
- 读取文件内容:通过字节数组读取文件的内容,然后将其转换为字符串。
- 处理字符数据:在处理数据传输或解析时,常使用字节数组和字符数组构建字符串。
# 2. 字符串比较方法 (equals)
boolean equals(Object anObject)
:比较此字符串与指定对象是否相等,考虑大小写。boolean equalsIgnoreCase(String anotherString)
:比较此字符串与指定字符串是否相等,忽略大小写。
public class StringComparisonDemo {
public static void main(String[] args) {
String str1 = "你好";
String str2 = "世界";
String str3 = "你好";
// 使用 equals 方法比较两个字符串是否相等(考虑大小写)
boolean isEqual = str1.equals(str2); // 返回 false,因为内容不相等
System.out.println("str1 和 str2 是否相等: " + isEqual); // 输出: str1 和 str2 是否相等: false
// 使用 equalsIgnoreCase 方法比较字符串是否相等(忽略大小写)
boolean isEqualIgnoreCase = str1.equalsIgnoreCase(str3); // 返回 true
System.out.println("str1 和 str3 忽略大小写是否相等: " + isEqualIgnoreCase); // 输出: str1 和 str3 忽略大小写是否相等: true
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
实际开发场景:
- 用户输入校验:用于比较用户输入的数据时,通常需要忽略大小写以确保正确匹配。
- 字符串匹配:在配置文件或数据处理时,使用精确匹配(
equals
)或忽略大小写匹配(equalsIgnoreCase
)来验证输入的有效性。
# 3. 长度、拼接、访问和截取
int length()
:返回字符串的长度(字符个数)。String concat(String str)
:将指定字符串连接到此字符串的末尾。char charAt(int index)
:返回指定索引处的字符。int indexOf(String str)
:返回指定子字符串在此字符串中第一次出现的索引。String substring(int beginIndex)
:返回从指定索引开始截取到字符串结尾的子字符串。String substring(int beginIndex, int endIndex)
:返回从beginIndex
(含)到endIndex
(不含)截取的子字符串。
public class StringOperationsDemo {
public static void main(String[] args) {
String text = "你好,Java!";
// 获取字符串长度
int length = text.length(); // 返回 7,因为每个汉字占 1 个字符
System.out.println("字符串的长度: " + length); // 输出: 字符串的长度: 7
// 连接字符串
String concatenated = text.concat(" 欢迎使用!"); // 输出 "你好,Java! 欢迎使用!"
System.out.println("连接后的字符串: " + concatenated);
// 获取指定索引处的字符
char character = text.charAt(3); // 返回 'J'
System.out.println("索引 3 处的字符: " + character);
// 查找子字符串在字符串中第一次出现的索引
int indexOfJava = text.indexOf("Java"); // 返回 3
System.out.println("子字符串 \"Java\" 的索引: " + indexOfJava);
// 截取子字符串
String subString = text.substring(3, 7); // 返回 "Java"
System.out.println("截取的子字符串: " + subString);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
实际开发场景:
- 字符串拼接:常用于构建用户提示、日志记录等。
- 子字符串处理:常用于解析文件路径、提取域名等操作。
# 4. 字符串转换和替换方法
char[] toCharArray()
:将字符串转换为字符数组。byte[] getBytes()
:将字符串编码为字节数组。String replaceAll(String regex, String replacement)
:使用正则表达式匹配并替换字符串中所有符合条件的部分。String replace(CharSequence target, CharSequence replacement)
:替换字符串中与目标字符序列匹配的部分。
import java.util.Arrays;
public class StringConversionAndReplacementDemo {
public static void main(String[] args) {
String text = "12345";
// 将字符串转换为字符数组
char[] charArray = text.toCharArray();
System.out.println("字符数组: " + Arrays.toString(charArray)); // 输出: 字符数组: [1, 2, 3, 4, 5]
// 将字符串编码为字节数组
byte[] byteArray = text.getBytes();
System.out.println("字节数组: " + Arrays.toString(byteArray)); // 输出: 字节数组: [49, 50, 51, 52, 53]
// 使用正则表达式替换字符串中的匹配部分
String replacedText = text.replaceAll("[0-9]", "X"); // 输出 "XXXXX"
System.out.println("替换后的字符串 (使用正则): " + replacedText);
// 替换指定字符序列
String replacedSequence = text.replace("123", "ABC"); // 输出 "ABC45"
System.out.println("替换后的字符串 (使用字符序列): " + replacedSequence);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
实际开发场景:
- 数据清洗:通过正则表达式清除或格式化用户输入。
- 字符编码:处理数据传输时,字符串编码为字节数组是常见操作。
# 5. 字符串分割 (split)
String[] split(String regex)
:根据给定的正则表达式将字符串分割为字符串数组。
public class StringSplitDemo {
public static void main(String[] args) {
String text = "苹果,香蕉,樱桃";
// 使用逗号分割字符串
String[] fruits = text.split(",");
System.out.println("分割后的字符串数组: ");
for (String fruit : fruits) {
System.out.println(fruit); // 输出: 苹果, 香蕉,
樱桃
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
实际开发场景:
- 解析 CSV 数据:在解析 CSV 文件时,通过逗号分割行数据。
- 配置文件解析:处理分隔符分割的配置项。
# 6. 基本类型转换为字符串 (valueOf)
static String valueOf(boolean b)
:将布尔类型转换为字符串。static String valueOf(char c)
:将字符类型转换为字符串。static String valueOf(char[] data)
:将字符数组转换为字符串。static String valueOf(char[] data, int offset, int count)
:将字符数组的部分元素转换为字符串。static String valueOf(double d)
:将双精度浮点类型转换为字符串。static String valueOf(float f)
:将浮点类型转换为字符串。static String valueOf(int i)
:将整数类型转换为字符串。static String valueOf(long l)
:将长整型转换为字符串。static String valueOf(Object obj)
:将对象转换为字符串。
public class StringValueOfDemo {
public static void main(String[] args) {
boolean boolValue = true;
char charValue = 'A';
char[] charArrayValue = {'你', '好'};
double doubleValue = 3.14159265;
float floatValue = 2.71828f;
int intValue = 42;
long longValue = 1234567890L;
Object objValue = new Object();
// 各种数据类型转换为字符串
String boolStr = String.valueOf(boolValue); // "true"
String charStr = String.valueOf(charValue); // "A"
String charArrayStr = String.valueOf(charArrayValue); // "你好"
String doubleStr = String.valueOf(doubleValue); // "3.14159265"
String floatStr = String.valueOf(floatValue); // "2.71828"
String intStr = String.valueOf(intValue); // "42"
String longStr = String.valueOf(longValue); // "1234567890"
String objStr = String.valueOf(objValue); // "java.lang.Object@hashcode"
// 打印转换结果
System.out.println("布尔值转换为字符串: " + boolStr);
System.out.println("字符转换为字符串: " + charStr);
System.out.println("字符数组转换为字符串: " + charArrayStr);
System.out.println("双精度数值转换为字符串: " + doubleStr);
System.out.println("浮点数值转换为字符串: " + floatStr);
System.out.println("整数转换为字符串: " + intStr);
System.out.println("长整数转换为字符串: " + longStr);
System.out.println("对象转换为字符串: " + objStr);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
实际开发场景:
- 数据展示:将数值或对象转换为字符串以显示在用户界面或日志中。
- 对象序列化:在对象需要以字符串形式传输或存储时(如 JSON),使用
valueOf
方法进行转换。
# 7. 字符串转换为基本类型 (parseInt)
字符串转换为基本数据类型的常用方法:
int Integer.parseInt(String s)
:将字符串解析为整数。double Double.parseDouble(String s)
:将字符串解析为双精度浮点数。boolean Boolean.parseBoolean(String s)
:将字符串解析为布尔值。
public class StringToPrimitiveDemo {
public static void main(String[] args) {
String intString = "123";
String doubleString = "3.1415";
String boolString = "true";
// 将字符串解析为基本类型
int intValue = Integer.parseInt(intString); // 输出 123
double doubleValue = Double.parseDouble(doubleString); // 输出 3.1415
boolean boolValue = Boolean.parseBoolean(boolString); // 输出 true
// 打印解析结果
System.out.println("解析后的整数: " + intValue);
System.out.println("解析后的双精度数值: " + doubleValue);
System.out.println("解析后的布尔值: " + boolValue);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
实际开发场景:
- 表单数据处理:解析用户输入的字符串,并转换为相应的基本数据类型进行计算或存储。
# 8. 去除字符串左右空格 (trim)
String trim()
:去除字符串左右两端的空格。
public class StringTrimDemo {
public static void main(String[] args) {
String str = " 你好,世界 ";
// 去除左右空格
String trimmedStr = str.trim(); // 输出 "你好,世界"
System.out.println("原始字符串: [" + str + "]");
System.out.println("去除空格后的字符串: [" + trimmedStr + "]");
}
}
2
3
4
5
6
7
8
9
10
实际开发场景:
- 用户输入处理:在表单数据校验中,去除输入内容的左右空格是常见需求。
# 9. 字符串格式化 (format)
static String format(String format, Object... args)
:返回一个格式化的字符串,类似于printf
。
public class StringFormatDemo {
public static void main(String[] args) {
// 格式化输出日期
String formattedStr = String.format("%d年%d月%d日", 2024, 8, 22);
System.out.println(formattedStr); // 输出 "2024年8月22日"
}
}
2
3
4
5
6
7
实际开发场景:
- 日期和时间格式化:常用于格式化日志、报表等。
# 10. 常用字符串操作
boolean startsWith(String prefix)
:测试字符串是否以指定的前缀开头。boolean endsWith(String suffix)
:测试字符串是否以指定的后缀结尾。int lastIndexOf(String str)
:返回指定子字符串在字符串中最后一次出现的索引。String toLowerCase()
:将字符串中的所有字符转换为小写。String toUpperCase()
:将字符串中的所有字符转换为大写。boolean contains(CharSequence s)
:检查字符串是否包含指定的字符序列。boolean isEmpty()
:检查字符串是否为空(即长度为 0)。
public class StringMiscOperationsDemo {
public static void main(String[] args) {
String str = "你好,Java!";
// 检查字符串是否以指定前缀开头
boolean startsWithHello = str.startsWith("你好"); // 返回 true
System.out.println("字符串是否以 \"你好\" 开头: " + startsWithHello);
// 检查字符串是否以指定后缀结尾
boolean endsWithJava = str.endsWith("Java!"); // 返回 true
System.out.println("字符串是否以 \"Java!\" 结尾: " + endsWithJava);
// 获取字符串中最后一次出现 "Java" 的索引
int lastIndex = str.lastIndexOf("Java"); // 返回 4
System.out.println("\"Java\" 在字符串中的最后一次出现位置: " + lastIndex);
// 转换字符串为小写
String lowerCaseStr = str.toLowerCase(); // 输出 "你好,java!"
System.out.println("转换为小写: " + lowerCaseStr);
// 转换字符串为大写
String upperCaseStr = str.toUpperCase(); // 输出 "你好,JAVA!"
System.out.println("转换为大写: " + upperCaseStr);
// 检查字符串是否包含 "Java"
boolean containsJava = str.contains("Java"); // 返回 true
System.out.println("字符串是否包含 \"Java\": " + containsJava);
// 检查字符串是否为空
boolean isEmptyStr = str.isEmpty(); // 返回 false
System.out.println("字符串是否为空: " + isEmptyStr);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
实际开发场景:
- 配置文件解析:检查配置项的前缀、后缀,或包含特定关键词。
- 文本处理:处理和规范化文本数据时经常需要进行大小写转换和索引查找。
# 11. 字符串常量池
字符串常量池(String Pool)是Java虚拟机(JVM)中用于存储唯一字符串实例的特殊存储区域。这个机制允许JVM为相同的字符串字面量只保存一份副本,从而节省内存空间,并提高字符串操作的效率。字符串常量池位于Java堆内存中。
- JDK 6及之前:字符串常量池位于方法区(PermGen space)。由于方法区的空间是固定的,字符串常量池的大小也是固定的,其默认大小为1009。这种设计限制了字符串常量池的可扩展性,如果存储过多的字符串,会增加哈希冲突的可能性,导致性能下降。
- JDK 7:从JDK 7开始,字符串常量池被移到了Java堆内存中。这个改动使得字符串常量池的大小不再受方法区大小的限制,而是由Java堆的大小决定,从而增加了其扩展性。在JDK 7及以后的版本中,通过启动参数
-XX:StringTableSize
可以指定字符串常量池的大小。 - JDK 8及之后:继续沿用JDK 7的设计,字符串常量池仍然位于Java堆内存中。在这些版本中,JVM对字符串去重(String Deduplication)和字符串常量池的管理进行了优化,以进一步提高性能和降低内存占用。
笔记:常量池中存储的是对字符串对象的引用,而字符串对象本身是存储在堆内存中的。
字符串常量池的工作原理
当代码中出现字符串字面量时,JVM会首先检查字符串常量池,如果池中已经包含一个等于此字符串的字符串,则返回池中的字符串实例引用;如果没有,JVM会将此字符串实例添加到池中,并返回此字符串实例的引用。这样做保证了所有的字符串字面量都是唯一的。
- 直接使用
双引号声明的字符串
会自动放入字符串常量池中,例如,String str = "abc";
- 调用字符串对象的
.intern()
方法也可以将字符串添加到常量池中。如果常量池已经包含一个等于此String
对象的字符串,则返回常量池中的字符串;否则,将此String
对象添加到常量池中,并返回此String
对象的引用。 - 使用
new String()
构造器创建的字符串对象会放在堆内存中。每次使用new
关键字创建字符串时,即使字符串的内容相同,也会创建一个新的对象实例。位于堆内存中,受到垃圾回收机制的管理,当没有任何引用指向它时,可以被垃圾回收器回收。
new string(“abc”)创建了几个对象?
使用new String("abc")
表达式在Java中会创建两个对象,前提是这个字符串常量之前没有被放入字符串常量池中。
- 字符串常量池中的对象:"abc" 字符串首先会被检查是否存在于字符串常量池中,如果不存在,就会在字符串常量池中创建一个"abc"的对象。
- 堆中的对象:通过
new String("abc")
显式创建的String对象将在堆上分配内存。
因此,总共会创建两个对象,一个是在字符串常量池中,另一个是在堆上。
# 12. 字符串拼接的规则
字符串拼接的机制在Java中是非常重要的一个特性,它直接关联到字符串的存储位置和性能。
- 常量与常量的拼接:结果在编译期就确定,存储于字符串常量池中,且常量池不会存储内容相同的常量。
- 变量与字符串的拼接:结果在运行时确定,存储于堆内存中。
- 使用
intern()
方法:如果拼接结果调用intern()
方法,则该字符串会被加入到字符串常量池中,如果常量池已存在相同内容的字符串,则直接返回常量池中的那个字符串的引用。
public class StringConcatDemo {
public static void main(String[] args) {
// 定义一个字符串,直接赋值,内容为JavaAndroid,存储在常量池中
String sb = "JavaAndroid";
// 定义两个字符串变量s1和s2,它们的值分别为Java和Android,均存储在常量池中
String s1 = "Java";
String s2 = "Android";
// 常量与常量的拼接,结果在编译期就确定了,存储在常量池中
String s3 = "Java" + "Android";
// 变量与常量的拼接,结果在运行期确定,存储在堆内存中
String s4 = s1 + "Android";
// 变量与常量的拼接,同样结果在运行期确定,存储在堆内存中
String s5 = "Java" + s2;
// 变量与变量的拼接,结果也是在运行期确定,存储在堆内存中
String s6 = s1 + s2;
// 比较字符串引用地址
System.out.println(sb == s3); // true,因为s3在常量池中,内容相同
System.out.println(sb == s4); // false,s4在堆内存中
System.out.println(sb == s5); // false,s5在堆内存中
System.out.println(sb == s6); // false,s6在堆内存中
System.out.println(s4 == s5); // false,s4和s5都在堆中,但是它们是不同的对象
// 使用intern()方法后,s7引用常量池中的字符串
String s7 = s4.intern();
System.out.println(sb == s7); // true,s7是常量池中的引用,内容相同
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
通过这段代码演示,我们可以清楚地看到字符串拼接的不同情况及其结果存储位置的差异。特别是intern()
方法的使用,它可以帮助我们将字符串引用指向常量池中的字符串,从而在一定程度上优化内存的使用。
注意:`有变量参与的字符串`拼接在底层都会实例化StringBuilder的对象
例如看下面这段代码:
public static void main(String[] args) {
String a = "abc";
String b = a + "def"; // 变量+常量
System.out.println(b);
}
2
3
4
5
a变量在编译的时候不知道a是字符串“abc”,所以不会进行编译期优化,不会直接合并为“abcdef”
经过反编译一下:
# 13. String为什么是不可变的
私有最终数组:在Java 8 及之前版本中,
String
通过一个私有的
、final
类型的char[]
数组存储字符数据,而从 Java 9 开始,则通过一个私有的byte[]
数组以及一个字符编码标志来存储。这两种内部数组都是私有的
且被声明为final
,意味着它们的引用不可变,且String
类未提供任何公共方法来改变这些数组的内容,确保了String
对象的不可变性。没有提供修改方法:
String
类没有提供任何可以修改其内容的方法,所有看似会修改内容的方法(如concat
、replace
、toUpperCase
等)实际上都是返回了一个全新的String
对象。// concat源码 public String concat(String str) { int otherLen = str.length(); if (otherLen == 0) { return this; } int len = value.length; char buf[] = Arrays.copyOf(value, len + otherLen); str.getChars(buf, len); return new String(buf, true); // 返回了一个全新的 `String` 对象 }
1
2
3
4
5
6
7
8
9
10
11