String类
1. String类的重要性
概念:Java专门提供的字符串类,符合面向对象思想(数据+操作封装在一起)
注意:
-
C语言使用字符数组/指针+库函数操作字符串,是面向过程的
-
String在开发、笔试、面试中都非常重要
2. 常用方法
2.1 字符串构造
常用构造方式:
// 1. 使用常量串构造(最常用)
String s1 = "hello bit";
// 2. 使用new关键字
String s2 = new String("hello bit");
// 3. 使用字符数组构造
char[] array = {'h','e','l','l','o',' ','b','i','t'};
String s3 = new String(array);
注意易错点:
-
String是引用类型,存储的是字符串的引用(地址)
-
""引起来的也是String对象 -
查看其他构造方法参考官方文档
2.2 String对象的比较
4种比较方式:
1. ==比较引用地址
String s1 = new String("hello");
String s2 = new String("hello");
String s3 = s1;
System.out.println(s1 == s2); // false,不同对象
System.out.println(s1 == s3); // true,同一对象
2. equals()按字典序比较内容
String s1 = new String("hello");
String s2 = new String("hello");
String s3 = new String("Hello");
System.out.println(s1.equals(s2)); // true
System.out.println(s1.equals(s3)); // false(大小写敏感)
3. compareTo()按字典序比较,返回int
String s1 = "abc";
String s2 = "ac";
String s3 = "abcdef";
System.out.println(s1.compareTo(s2)); // -1,'b'<'c'
System.out.println(s1.compareTo(s3)); // -3,长度差
4. compareToIgnoreCase()忽略大小写比较
String s1 = "abc";
String s2 = "ABC";
System.out.println(s1.compareToIgnoreCase(s2)); // 0
注意易错点:
-
==比较地址,equals比较内容 -
compareTo返回差值,可用于排序
2.3 字符串查找
常用查找方法:
String s = "aaabbbcccaaabbbccc";
System.out.println(s.charAt(3)); // 'b'
System.out.println(s.indexOf('c')); // 6(第一次出现位置)
System.out.println(s.indexOf("bbb")); // 3
System.out.println(s.indexOf('c', 10)); // 15(从指定位置开始找)
System.out.println(s.lastIndexOf('c')); // 17(从后往前找)
注意易错点:
-
indexOf找不到返回-1 -
charAt索引越界会抛出IndexOutOfBoundsException
2.4 字符串转化
1. 数值和字符串互转
// 数字转字符串
String s1 = String.valueOf(1234);
String s2 = String.valueOf(12.34);
// 字符串转数字
int num1 = Integer.parseInt("1234");
double num2 = Double.parseDouble("12.34");
2. 大小写转换
String s1 = "Hello";
System.out.println(s1.toUpperCase()); // "HELLO"
System.out.println(s1.toLowerCase()); // "hello"
3. 字符串与数组互转
// 字符串转字符数组
String s = "hello";
char[] ch = s.toCharArray();
// 字符数组转字符串
char[] array = {'h','e','l','l','o'};
String s2 = new String(array);
4. 格式化
String s = String.format("%d-%02d-%02d", 2023, 9, 5);
System.out.println(s); // "2023-09-05"
注意易错点:
-
parseInt/parseDouble可能抛出NumberFormatException -
格式化字符串语法类似C语言的
printf
2.5 字符串替换
String str = "helloworld";
System.out.println(str.replaceAll("l", "_")); // "he__owor_d"
System.out.println(str.replaceFirst("l", "_")); // "he_loworld"
注意易错点:
-
String不可变,替换操作返回新字符串
-
replaceAll支持正则表达式
2.6 字符串拆分
String str = "hello world hello bit";
// 按空格拆分
String[] result = str.split(" ");
// ["hello", "world", "hello", "bit"]
// 限制拆分组数
String[] result2 = str.split(" ", 2);
// ["hello", "world hello bit"]
// 特殊字符需要转义
String ip = "192.168.1.1";
String[] parts = ip.split("\\.");
// ["192", "168", "1", "1"]
注意易错点:
-
.、|、*等特殊字符需要转义 -
\`需要写成\\` -
多分隔符用
|连接:split(",|;")
2.7 字符串截取
String str = "helloworld";
System.out.println(str.substring(5)); // "world"(从5到末尾)
System.out.println(str.substring(0, 5)); // "hello"([0,5)前闭后开)
注意易错点:
-
索引从0开始
-
前闭后开区间:
substring(begin, end)包含begin,不包含end
2.8 其他操作
String str = " hello world ";
// 去除首尾空白
System.out.println("[" + str.trim() + "]"); // "[hello world]"
// 大小写转换(只影响字母)
System.out.println("Hello123".toLowerCase()); // "hello123"
3. 字符串的不可变性
概念:String对象一旦创建,内容不可更改
源码关键点:
public final class String {
private final char value[];
}
-
final class:不能被继承 -
final char[]:value引用不能指向其他数组,但数组内容理论上可修改(实际被设计为不可修改)
代码示例:
String s = "hello";
s = s + " world"; // 创建了新对象,s指向新对象
为什么设计为不可变:
-
便于实现字符串常量池
-
线程安全
-
缓存hash值,提高HashMap效率
注意易错点:
-
修改字符串会创建新对象,频繁修改效率低
-
final修饰引用类型:引用不能变,引用对象的内容可修改(但String内部做了限制)
4. 字符串修改的正确方式
错误方式(效率低):
String s = "";
for (int i = 0; i < 10000; i++) {
s += i; // 每次循环创建新String对象
}
正确方式(使用StringBuilder/StringBuffer):
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append(i); // 在原有对象上修改
}
String result = sb.toString();
5. StringBuilder和StringBuffer
5.1 常用方法
StringBuilder sb = new StringBuilder("hello");
// 追加
sb.append(" world"); // "hello world"
sb.append(123); // "hello world123"
// 插入
sb.insert(0, "Hi "); // "Hi hello world123"
// 删除
sb.delete(0, 3); // "hello world123"
sb.deleteCharAt(0); // "ello world123"
// 替换
sb.replace(0, 4, "HELLO"); // "HELLO world123"
// 反转
sb.reverse(); // "321dlrow OLLEH"
// 转String
String str = sb.toString();
5.2 String vs StringBuilder vs StringBuffer
| 特性 | String | StringBuilder | StringBuffer |
|---|---|---|---|
| 可变性 | 不可变 | 可变 | 可变 |
| 线程安全 | 是(天然) | 否 | 是(同步方法) |
| 性能 | 修改时慢 | 快 | 稍慢(有同步开销) |
| 使用场景 | 字符串常量 | 单线程字符串操作 | 多线程字符串操作 |
注意易错点:
-
String和StringBuilder不能直接转换:
-
String→StringBuilder:构造方法或
append() -
StringBuilder→String:
toString()
-
-
单线程用StringBuilder,多线程用StringBuffer
6. 字符串常量池
概念:JVM为String开辟的特殊内存区域,避免重复创建相同字符串
String s1 = "hello"; // 在常量池创建
String s2 = "hello"; // 直接引用常量池中的对象
String s3 = new String("hello"); // 在堆中创建新对象
System.out.println(s1 == s2); // true(同一对象)
System.out.println(s1 == s3); // false(不同对象)
intern()方法:将字符串放入常量池或返回已有引用
String s4 = new String("world").intern();
String s5 = "world";
System.out.println(s4 == s5); // true
注意易错点:
-
""直接赋值使用常量池 -
new String()在堆中创建新对象 -
大量相同字符串时,常量池可节省内存
7. 常见OJ题示例
7.1 第一个只出现一次的字符
public int firstUniqChar(String s) {
int[] count = new int[256];
for (char c : s.toCharArray()) {
count[c]++;
}
for (int i = 0; i < s.length(); i++) {
if (count[s.charAt(i)] == 1) {
return i;
}
}
return -1;
}
7.2 最后一个单词的长度
public int lengthOfLastWord(String s) {
s = s.trim();
return s.length() - s.lastIndexOf(' ') - 1;
}
7.3 验证回文串
public boolean isPalindrome(String s) {
s = s.toLowerCase();
int left = 0, right = s.length() - 1;
while (left < right) {
// 跳过非字母数字字符
while (left < right && !Character.isLetterOrDigit(s.charAt(left))) left++;
while (left < right && !Character.isLetterOrDigit(s.charAt(right))) right--;
if (s.charAt(left) != s.charAt(right)) return false;
left++;
right--;
}
return true;
}
关键要点总结
-
String不可变:修改操作返回新对象,原对象不变
-
比较字符串 :
==比较地址,equals比较内容 -
效率问题:频繁修改用StringBuilder/StringBuffer
-
线程安全:单线程用StringBuilder,多线程用StringBuffer
-
常量池 :
""直接赋值使用常量池,new String()在堆中创建 -
常用操作:查找、替换、拆分、截取、转换要熟练掌握
-
特殊字符:拆分时注意转义字符问题