== 与 equals 的本质区别,熟练用 equals/equalsIgnoreCase 比较内容StringBuilder/StringBuffer 的可变性与拼接性能差异(线程安全 vs 非安全)new String("x") 绕过常量池、循环 + 拼接性能差等陷阱字符串是 Java 程序中经常处理的对象,如果字符串运用得不好,将影响到程序运行的效率。在 Java 中字符串作为 String 类的实例来处理。以对象的方式处理字符串,将使字符串更加灵活、方便。
Java 中 String 是一个非常重要的类,用于表示和操作字符串。String 类位于 java.lang 包中,是 Java 核心类之一。由于字符串在程序中非常常用,Java 对 String 类进行了高度优化,使其成为不可变(immutable)的、线程安全的对象。
单个字符可以用 char 类型保存,多个字符组成的文本就需要保存在 String 对象中。String 通常被称为字符串,一个 String 对象最多可以保存 Integer.MAX_VALUE(约 21.4 亿)个字符的文本内容。
不可变性(Immutability)是指:一旦 String 对象被创建,它的值就不能被改变。所有看起来修改字符串的操作,实际上都是创建了一个新的字符串对象。
JDK 8 及之前:
String 类内部使用 final char[] value 存储字符数据final 修饰符保证引用不可变JDK 9 及之后:
final byte[] value + byte coder 存储数据// JDK 8 源码
public final class String {
private final char[] value;
// ...
}
// JDK 9+ 源码
public final class String {
private final byte[] value;
private final byte coder; // 编码标识
// ...
}String 类被 final 修饰,不能被继承final 修饰,引用不可变1. 线程安全
2. 安全性
3. 缓存优化
4. 性能优化
hashCode() 只计算一次)1. 拼接开销
StringBuilder 或 StringBuffer2. 内存占用
3. 更新成本
// 不可变性示例
String str = "Hello";
str = str + " World"; // 创建了一个新的字符串对象
// 内存中实际存在:
// 1. "Hello" 字符串(可能在常量池)
// 2. " World" 字符串(可能在常量池)
// 3. "Hello World" 字符串(新创建)
System.out.println(str); // 输出: Hello World字符串常量池(String Pool / String Constant Pool)是 Java 堆内存中的一块特殊区域,用于存储字符串字面量,以提高内存利用率并优化性能。
| JDK 版本 | 位置 | 说明 |
|---|---|---|
| JDK 6 及之前 | 方法区(永久代) | 大小受限于永久代,容易 OOM |
| JDK 7 | 堆内存 | 从永久代移到堆中,更容易管理 |
| JDK 8 及之后 | 堆内存 | 元空间取代永久代,但字符串常量池仍在堆中 |
String s1 = "Hello";
String s2 = new String("Hello");
String s3 = s2.intern();
// 内存布局:
// ┌─────────────────┐
// │ 字符串常量池 │ "Hello" ← s1, s3
// └─────────────────┘
// ┌─────────────────┐
// │ 堆内存 │ String对象 ← s2
// └─────────────────┘
System.out.println(s1 == s2); // false
System.out.println(s1 == s3); // true
System.out.println(s2 == s3); // false字面量创建:
String str = "Hello";"Hello" 被放入 class 文件的常量池new 创建:
String str = new String("Hello");"Hello" 被放入 class 文件的常量池"Hello" 放入字符串常量池"Hello" 的值1. 优先使用字面量
// 推荐
String str = "Hello";
// 不推荐
String str = new String("Hello"); // 创建不必要的堆对象2. 合理使用 intern()
// 适用于:大量重复字符串的场景
// 例如:从数据库读取的状态值、国家代码等
// 注意:不要滥用,可能导致常量池过大3. 字符串拼接优化
// 编译期可确定的拼接,编译器会优化
String str = "Hello" + " " + "World"; // 编译为 "Hello World"
// 运行时拼接,Java 8 编译器会转为 StringBuilder
String result = str1 + str2 + str3; // Java 9+ 改用 invokedynamic 动态拼接,语义不变| 特性 | String | StringBuilder | StringBuffer |
|---|---|---|---|
| 可变性 | 不可变 | 可变 | 可变 |
| 线程安全 | 是(不可变) | 否 | 是(synchronized) |
| 性能 | 低(频繁拼接) | 最高 | 中等 |
| 引入版本 | JDK 1.0 | JDK 5 | JDK 1.0 |
| 适用场景 | 字符串常量、少量拼接 | 单线程大量拼接 | 多线程大量拼接 |
特点:
适用场景:
String str = "Hello";
str = str + " World"; // 创建新对象
// 编译器优化:少量拼接会被优化
String result = "Hello" + " " + "World"; // 编译后直接是 "Hello World"特点:
常用方法:
append(String str):追加字符串insert(int offset, String str):插入字符串delete(int start, int end):删除指定范围reverse():反转字符串replace(int start, int end, String str):替换指定范围// 基本使用
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" ");
sb.append("World");
String result = sb.toString(); // "Hello World"
// 链式调用
StringBuilder sb2 = new StringBuilder();
sb2.append("Hello").append(" ").append("World");
// 其他操作
StringBuilder sb3 = new StringBuilder("Hello World");
sb3.insert(5, " Java"); // "Hello Java World"
sb3.delete(5, 10); // "Hello World"
sb3.reverse(); // "dlroW olleH"特点:
synchronized 修饰)StringBuilder适用场景:
// 使用方式与 StringBuilder 相同
StringBuffer sb = new StringBuffer();
sb.append("Hello");
sb.append(" World");
String result = sb.toString(); // "Hello World"1. 少量字符串拼接(<10次)
// 直接使用 + 拼接
String result = "Hello" + " " + "World";2. 单线程大量拼接
// 使用 StringBuilder
StringBuilder sb = new StringBuilder();
for (String item : items) {
sb.append(item);
}
String result = sb.toString();3. 多线程大量拼接
// 使用 StringBuffer
StringBuffer sb = new StringBuffer();
// 多线程环境下安全使用
sb.append("Hello");4. 字符串常量
// 使用 String 字面量
String CONSTANT = "Hello World";5. 频繁修改单个字符串
// 使用 StringBuilder
StringBuilder sb = new StringBuilder("Hello");
sb.insert(5, " World"); // 在原对象上修改intern() 方法是一个 native 方法,用于将字符串手动加入字符串常量池。
public native String intern();JDK 6 及之前:
intern() 时,如果常量池中不存在该字符串,则在常量池中复制一份并返回引用JDK 7 及之后:
intern() 时,如果常量池中不存在该字符串,则在常量池中记录堆中对象的引用(不再复制)1. 大量重复字符串的场景
// 示例:从数据库读取大量重复的状态值
public class StatusProcessor {
public void process(List<String> statuses) {
for (String status : statuses) {
// 使用 intern() 复用字符串对象
String interned = status.intern();
if ("ACTIVE".equals(interned)) {
// 处理 ACTIVE 状态
} else if ("INACTIVE".equals(interned)) {
// 处理 INACTIVE 状态
}
}
}
}2. 减少内存占用
// 示例:读取大量数据,其中某些字段值重复
public class DataReader {
public List<Data> readData(Connection conn) throws SQLException {
List<Data> list = new ArrayList<>();
String sql = "SELECT id, status, country FROM users";
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
Data data = new Data();
data.setId(rs.getLong("id"));
// 使用 intern() 复用重复的状态值
data.setStatus(rs.getString("status").intern());
// 使用 intern() 复用重复的国家代码
data.setCountry(rs.getString("country").intern());
list.add(data);
}
}
return list;
}
}1. 不要滥用 intern()
2. intern() 的性能开销
3. 内存泄漏风险
4. 替代方案
// 不推荐:所有字符串都 intern()
String str = someString.intern(); // 可能导致常量池过大
// 推荐:仅对重复率高的字符串使用
if (isFrequentlyUsed(someString)) {
str = someString.intern();
}length():返回字符串长度
String str = "Hello World";
System.out.println(str.length()); // 输出: 11
// 注意:length() 返回的是字符数,不是字节数
String chinese = "你好世界";
System.out.println(chinese.length()); // 输出: 4(字符数)charAt(int index):返回指定索引的字符
String str = "Hello";
System.out.println(str.charAt(0)); // 输出: H
System.out.println(str.charAt(4)); // 输出: o
// 注意:索引从 0 开始,范围 [0, length()-1]
// 超出范围会抛出 StringIndexOutOfBoundsExceptiontoCharArray():转换为字符数组
String str = "Hello";
char[] chars = str.toCharArray();
for (char c : chars) {
System.out.print(c + " "); // H e l l o
}String str = "Hello World, Hello Java";
// indexOf(): 查找字符或子串第一次出现的位置
System.out.println(str.indexOf('o')); // 4
System.out.println(str.indexOf("Hello")); // 0
System.out.println(str.indexOf('o', 5)); // 7(从索引5开始查找)
System.out.println(str.indexOf("Python")); // -1(未找到)
// lastIndexOf(): 查找最后一次出现的位置
System.out.println(str.lastIndexOf('o')); // 17
System.out.println(str.lastIndexOf("Hello")); // 13
// contains(): 判断是否包含子串
System.out.println(str.contains("World")); // true
System.out.println(str.contains("Python")); // false
// startsWith() 和 endsWith()
System.out.println(str.startsWith("Hello")); // true
System.out.println(str.endsWith("Java")); // true
System.out.println(str.startsWith("World", 6)); // true(从索引6开始)String str = "Hello World, Hello Java";
// replace(char oldChar, char newChar): 替换所有字符
System.out.println(str.replace('o', '0')); // "Hell0 W0rld, Hell0 Java"
// replace(CharSequence target, CharSequence replacement): 替换所有子串
System.out.println(str.replace("Hello", "Hi")); // "Hi World, Hi Java"
// replaceFirst(String regex, String replacement): 替换第一个匹配
System.out.println(str.replaceFirst("Hello", "Hi")); // "Hi World, Hello Java"
// replaceAll(String regex, String replacement): 替换所有匹配(支持正则)
String text = "abc123def456";
System.out.println(text.replaceAll("\\d+", "")); // "abcdef"(移除所有数字)
System.out.println(text.replaceFirst("\\d+", "")); // "abcdef456"// 字符串转基本类型
String intStr = "123";
int num = Integer.parseInt(intStr); // 123
Integer numObj = Integer.valueOf(intStr); // 123(返回 Integer 对象)
String doubleStr = "123.45";
double d = Double.parseDouble(doubleStr); // 123.45
String boolStr = "true";
boolean b = Boolean.parseBoolean(boolStr); // true
// 基本类型转字符串
int num2 = 456;
String str1 = String.valueOf(num2); // "456"
String str2 = Integer.toString(num2); // "456"
String str3 = num2 + ""; // "456"(不推荐)
// 其他类型
double d2 = 78.9;
String str4 = String.valueOf(d2); // "78.9"
String str5 = Double.toString(d2); // "78.9"
boolean b2 = false;
String str6 = String.valueOf(b2); // "false"
String str7 = Boolean.toString(b2); // "false"String str = "Hello World";
// repeat(): 重复字符串(Java 11+)
String repeated = "Hello".repeat(3); // "HelloHelloHello"
// lines(): 按行分割(Java 11+)
String multiLine = "Line1\nLine2\nLine3";
multiLine.lines().forEach(System.out::println);
// indent(): 缩进(Java 12+)
String indented = "Hello\nWorld".indent(4);
// " Hello\n World\n"
// transform(): 转换(Java 12+)
String transformed = "hello".transform(s -> s.toUpperCase()); // "HELLO"
// stripIndent(): 去除缩进(Java 13+,文本块相关)
// translateEscapes(): 转义字符处理(Java 13+)文本块用三引号 """ 包裹多行字符串,免去手工拼接换行与转义,可读性大幅提升。Java 15(JEP 378)正式发布。
// 传统写法:手工转义,可读性差
String json = "{\"name\":\"Java\",\"version\":21}";
// 文本块写法:保留原始格式
String json = """
{
"name": "Java",
"version": 21
}
""";关键规则:
\n、\t 等;\" 不再需要," 可直接书写\\ 可显式保留反斜杠;\s 避免尾部空白被裁剪(Java 14 预览引入)与 formatted() 组合(Java 15 正式):
String sql = """
SELECT id, name
FROM users
WHERE age > %d
AND status = '%s'
""".formatted(18, "ACTIVE");适用场景:SQL 语句、JSON/XML/HTML 模板、帮助文本、正则表达式(避免双重转义)。
正则表达式(Regular Expression)是一种用于匹配字符串模式的强大工具。Java 通过 java.util.regex 包提供正则表达式支持。
字符匹配:
. - 任意单个字符\d - 数字字符 [0-9]\D - 非数字字符 [^0-9]\w - 单词字符 [a-zA-Z_0-9]\W - 非单词字符\s - 空白字符(空格、制表符、换行等)\S - 非空白字符量词:
* - 0次或多次+ - 1次或多次? - 0次或1次{n} - 恰好n次{n,} - 至少n次{n,m} - n到m次边界匹配:
^ - 行的开头$ - 行的结尾\b - 单词边界\B - 非单词边界分组:
() - 捕获分组(?:) - 非捕获分组(?=) - 正向预查(?!) - 负向预查字符类:
[abc] - a、b 或 c[^abc] - 除 a、b、c 之外的字符[a-z] - a 到 z[a-zA-Z] - a 到 z 或 A 到 Z转义字符:
\\ - 反斜杠\. - 点号\* - 星号\+ - 加号\? - 问号\(, \) - 括号\[, \] - 方括号// matches(): 判断是否匹配整个正则表达式
String email = "user@example.com";
boolean isEmail = email.matches("^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$"); // true
// split(): 根据正则表达式分割字符串
String text = "Hello World\tJava\nPython"; // 多种空白字符
String[] words = text.split("\\s+");
// ["Hello", "World", "Java", "Python"]
// replaceFirst(): 替换第一个匹配
String str = "abc123def456";
String result = str.replaceFirst("\\d+", "XXX"); // "abcXXXdef456"
// replaceAll(): 替换所有匹配
String result2 = str.replaceAll("\\d+", "XXX"); // "abcXXXdefXXX"对于复杂的正则表达式操作,可以使用 Pattern 和 Matcher 类。
1. 预编译正则表达式
// 不推荐:每次调用都编译
public boolean isValid(String str) {
return str.matches("\\d+"); // 内部会编译正则表达式
}
// 推荐:预编译
private static final Pattern NUMBER_PATTERN = Pattern.compile("\\d+");
public boolean isValid(String str) {
return NUMBER_PATTERN.matcher(str).matches();
}2. 避免过度使用贪婪量词
// 贪婪量词:尽可能多匹配,可能回溯
String text = "This is a <b>bold</b> and <i>italic</i> text";
String regex1 = "<.+>"; // 匹配整个 "<b>bold</b> and <i>italic</i>"
// 勉强量词:尽可能少匹配
String regex2 = "<.+?>"; // 匹配 "<b>" 和 "</b>" 和 "<i>" 和 "</i>"3. 使用非捕获分组
// 捕获分组:会保存匹配内容,影响性能
String regex1 = "(\\d{4})-(\\d{2})-(\\d{2})";
// 非捕获分组:不保存匹配内容
String regex2 = "(?:\\d{4})-(?:\\d{2})-(?:\\d{2})";4. 避免复杂的正则表达式
1. 字面量拼接
// 编译期优化:直接合并为一个字符串
String str = "Hello" + " " + "World";
// 编译后:String str = "Hello World";2. 常量表达式拼接
final String PREFIX = "Hello";
final String SUFFIX = "World";
// 编译期优化
String str = PREFIX + " " + SUFFIX; // "Hello World"3. 运行时拼接优化(Java 8 为 StringBuilder,Java 9+ 为 invokedynamic)
// Java 8 会自动使用 StringBuilder 优化
String str = str1 + str2 + str3;
// Java 8 编译后等价于:
String str = new StringBuilder()
.append(str1)
.append(str2)
.append(str3)
.toString();
// Java 9+ 改用 invokedynamic(StringConcatFactory)在运行期生成拼接代码,语义不变public class ConcatenationPerformance {
public static void main(String[] args) {
int count = 100000;
// 1. 使用 + 拼接(性能最差)
long start1 = System.currentTimeMillis();
String result1 = "";
for (int i = 0; i < count; i++) {
result1 += i;
}
System.out.println("+: " + (System.currentTimeMillis() - start1) + "ms");
// 2. 使用 StringBuilder(推荐)
long start2 = System.currentTimeMillis();
StringBuilder sb = new StringBuilder(count * 5); // 预分配容量
for (int i = 0; i < count; i++) {
sb.append(i);
}
String result2 = sb.toString();
System.out.println("StringBuilder: " + (System.currentTimeMillis() - start2) + "ms");
// 3. 使用 String.concat()
long start3 = System.currentTimeMillis();
String result3 = "";
for (int i = 0; i < count; i++) {
result3 = result3.concat(String.valueOf(i));
}
System.out.println("concat: " + (System.currentTimeMillis() - start3) + "ms");
// 4. 使用 String.format()
long start4 = System.currentTimeMillis();
StringBuilder sb4 = new StringBuilder();
for (int i = 0; i < count; i++) {
sb4.append(String.format("%d", i));
}
System.out.println("format: " + (System.currentTimeMillis() - start4) + "ms");
}
}
// 典型输出(100000次拼接):
// +: 5000ms+
// StringBuilder: 5ms
// concat: 1000ms+
// format: 200ms+1. 少量拼接(< 5 个字符串)
+ 操作符2. 循环中拼接
StringBuilder 或 StringBuffer3. 集合拼接
String.join() 或 Collectors.joining()4. 多线程环境
StringBuffer5. 格式化拼接
String.format()6. 大量重复拼接
常见字符编码:
// Java 内部使用 UTF-16 编码
String str = "Hello 你好";
// JDK 8: char[] 存储 UTF-16 编码单元
// JDK 9+: byte[] 存储,根据内容选择 Latin-1 或 UTF-16// 字符串转字节数组
String str = "Hello 你好";
// 1. 使用默认字符集(不推荐)
byte[] bytes1 = str.getBytes(); // 使用平台默认字符集
// 2. 指定字符集(推荐)
byte[] bytes2 = str.getBytes("UTF-8");
byte[] bytes3 = str.getBytes(StandardCharsets.UTF_8); // 推荐(无异常)
// 3. 其他编码
byte[] bytes4 = str.getBytes("GBK");
byte[] bytes5 = str.getBytes("ISO-8859-1");
// 字节数组转字符串
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
// 1. 使用默认字符集(不推荐)
String str1 = new String(bytes);
// 2. 指定字符集(推荐)
String str2 = new String(bytes, StandardCharsets.UTF_8);
String str3 = new String(bytes, "UTF-8");
// 3. 其他编码
String str4 = new String(bytes, "GBK");1. 始终显式指定字符集
// 不推荐:使用平台默认字符集
byte[] bytes = str.getBytes();
// 推荐:显式指定字符集
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);2. 统一使用 UTF-8
// 推荐:全局统一使用 UTF-8
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;3. 处理中文等非 ASCII 字符
// 必须指定正确的字符集
String chinese = "你好世界";
byte[] bytes = chinese.getBytes("GBK"); // 或 UTF-8
String decoded = new String(bytes, "GBK"); // 必须与编码一致4. 注意平台差异
// 不同平台的默认字符集可能不同
// Windows: GBK
// Linux/Mac: UTF-8
// 必须显式指定字符集以保持跨平台一致性5. 使用 StandardCharsets
// 推荐:使用 StandardCharsets 常量(无异常)
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
// 不推荐:使用字符串(可能抛异常)
byte[] bytes;
try {
bytes = str.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
// 理论上 UTF-8 不会不支持,但仍需处理异常
}public class StringUtils {
/**
* 判断字符串是否为空
*/
public static boolean isEmpty(String str) {
return str == null || str.isEmpty();
}
/**
* 判断字符串是否为空白
*/
public static boolean isBlank(String str) {
if (str == null || str.isEmpty()) {
return true;
}
for (int i = 0; i < str.length(); i++) {
if (!Character.isWhitespace(str.charAt(i))) {
return false;
}
}
return true;
}
/**
* 首字母大写
*/
public static String capitalize(String str) {
if (isEmpty(str)) {
return str;
}
return str.substring(0, 1).toUpperCase() + str.substring(1);
}
/**
* 首字母小写
*/
public static String uncapitalize(String str) {
if (isEmpty(str)) {
return str;
}
return str.substring(0, 1).toLowerCase() + str.substring(1);
}
/**
* 驼峰转下划线
*/
public static String camelToSnake(String str) {
if (isEmpty(str)) {
return str;
}
StringBuilder result = new StringBuilder();
result.append(Character.toLowerCase(str.charAt(0)));
for (int i = 1; i < str.length(); i++) {
char c = str.charAt(i);
if (Character.isUpperCase(c)) {
result.append('_').append(Character.toLowerCase(c));
} else {
result.append(c);
}
}
return result.toString();
}
/**
* 下划线转驼峰
*/
public static String snakeToCamel(String str) {
if (isEmpty(str)) {
return str;
}
StringBuilder result = new StringBuilder();
boolean nextUpper = false;
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (c == '_') {
nextUpper = true;
} else {
if (nextUpper) {
result.append(Character.toUpperCase(c));
nextUpper = false;
} else {
result.append(c);
}
}
}
return result.toString();
}
/**
* 反转字符串
*/
public static String reverse(String str) {
if (isEmpty(str)) {
return str;
}
return new StringBuilder(str).reverse().toString();
}
/**
* 判断字符串是否为数字
*/
public static boolean isNumeric(String str) {
if (isEmpty(str)) {
return false;
}
for (int i = 0; i < str.length(); i++) {
if (!Character.isDigit(str.charAt(i))) {
return false;
}
}
return true;
}
/**
* 填充字符串到指定长度
*/
public static String padLeft(String str, int length, char padChar) {
if (str == null) {
str = "";
}
if (str.length() >= length) {
return str;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length - str.length(); i++) {
sb.append(padChar);
}
sb.append(str);
return sb.toString();
}
/**
* 截断字符串
*/
public static String truncate(String str, int maxLength) {
if (isEmpty(str) || str.length() <= maxLength) {
return str;
}
return str.substring(0, maxLength) + "...";
}
}public class SensitiveDataMasker {
/**
* 手机号脱敏:138****5678
*/
public static String maskPhone(String phone) {
if (phone == null || phone.length() < 7) {
return phone;
}
return phone.substring(0, 3) + "****" + phone.substring(phone.length() - 4);
}
/**
* 身份证号脱敏:370***********1234
*/
public static String maskIdCard(String idCard) {
if (idCard == null || idCard.length() < 8) {
return idCard;
}
return idCard.substring(0, 3) + "***********" + idCard.substring(idCard.length() - 4);
}
/**
* 银行卡号脱敏:6222 **** **** 1234
*/
public static String maskBankCard(String cardNo) {
if (cardNo == null || cardNo.length() < 8) {
return cardNo;
}
return cardNo.substring(0, 4) + " **** **** " + cardNo.substring(cardNo.length() - 4);
}
/**
* 邮箱脱敏:u***@example.com
*/
public static String maskEmail(String email) {
if (email == null || !email.contains("@")) {
return email;
}
int atIndex = email.indexOf("@");
if (atIndex <= 1) {
return email;
}
return email.charAt(0) + "***" + email.substring(atIndex);
}
/**
* 姓名脱敏:张**
*/
public static String maskName(String name) {
if (name == null || name.isEmpty()) {
return name;
}
return name.charAt(0) + "**";
}
/**
* 地址脱敏:北京市朝阳区***
*/
public static String maskAddress(String address) {
if (address == null || address.length() < 6) {
return address;
}
return address.substring(0, 6) + "***";
}
}
// 使用示例
public class Demo {
public static void main(String[] args) {
System.out.println(SensitiveDataMasker.maskPhone("13812345678"));
// 输出: 138****5678
System.out.println(SensitiveDataMasker.maskIdCard("370123199001011234"));
// 输出: 370***********1234
System.out.println(SensitiveDataMasker.maskEmail("user@example.com"));
// 输出: u***@example.com
}
}public class StringParser {
/**
* 解析键值对字符串
* 示例:"name=John&age=25&city=Beijing"
*/
public static Map<String, String> parseKeyValue(String str, String pairSeparator, String kvSeparator) {
Map<String, String> result = new HashMap<>();
if (str == null || str.isEmpty()) {
return result;
}
String[] pairs = str.split(pairSeparator);
for (String pair : pairs) {
String[] kv = pair.split(kvSeparator, 2);
if (kv.length == 2) {
result.put(kv[0].trim(), kv[1].trim());
}
}
return result;
}
/**
* 解析 CSV 行
*/
public static List<String> parseCSVLine(String line) {
List<String> result = new ArrayList<>();
if (line == null || line.isEmpty()) {
return result;
}
StringBuilder field = new StringBuilder();
boolean inQuotes = false;
for (int i = 0; i < line.length(); i++) {
char c = line.charAt(i);
if (c == '"') {
if (inQuotes && i + 1 < line.length() && line.charAt(i + 1) == '"') {
field.append('"');
i++; // 跳过下一个引号
} else {
inQuotes = !inQuotes;
}
} else if (c == ',' && !inQuotes) {
result.add(field.toString());
field.setLength(0);
} else {
field.append(c);
}
}
result.add(field.toString());
return result;
}
/**
* 提取字符串中的所有数字
*/
public static List<String> extractNumbers(String str) {
List<String> numbers = new ArrayList<>();
Pattern pattern = Pattern.compile("\\d+(?:\\.\\d+)?");
Matcher matcher = pattern.matcher(str);
while (matcher.find()) {
numbers.add(matcher.group());
}
return numbers;
}
/**
* 提取 HTML 标签中的文本
*/
public static String extractTextFromHtml(String html) {
if (html == null) {
return "";
}
// 移除 HTML 标签
return html.replaceAll("<[^>]+>", "");
}
/**
* 统计单词出现次数
*/
public static Map<String, Integer> countWords(String text) {
Map<String, Integer> wordCount = new HashMap<>();
if (text == null || text.isEmpty()) {
return wordCount;
}
// 转小写,分割单词
String[] words = text.toLowerCase().split("\\W+");
for (String word : words) {
if (!word.isEmpty()) {
wordCount.put(word, wordCount.getOrDefault(word, 0) + 1);
}
}
return wordCount;
}
}public class StringBuilders {
/**
* 构建 SQL IN 子句
*/
public static String buildInClause(List<String> values) {
if (values == null || values.isEmpty()) {
return "()";
}
StringBuilder sb = new StringBuilder("(");
for (int i = 0; i < values.size(); i++) {
if (i > 0) {
sb.append(", ");
}
sb.append("'").append(values.get(i)).append("'");
}
sb.append(")");
return sb.toString();
}
/**
* 构建缩进字符串
*/
public static String indent(String str, int spaces) {
if (str == null) {
return null;
}
String indentStr = " ".repeat(spaces);
return Arrays.stream(str.split("\n"))
.map(line -> indentStr + line)
.collect(Collectors.joining("\n"));
}
/**
* 构建表格字符串
*/
public static String formatTable(List<String[]> rows) {
if (rows == null || rows.isEmpty()) {
return "";
}
// 计算每列最大宽度
int[] widths = new int[rows.get(0).length];
for (String[] row : rows) {
for (int i = 0; i < row.length; i++) {
widths[i] = Math.max(widths[i], row[i] != null ? row[i].length() : 0);
}
}
// 构建表格
StringBuilder sb = new StringBuilder();
for (String[] row : rows) {
for (int i = 0; i < row.length; i++) {
String cell = row[i] != null ? row[i] : "";
sb.append(String.format("%-" + widths[i] + "s", cell));
if (i < row.length - 1) {
sb.append(" | ");
}
}
sb.append("\n");
}
return sb.toString();
}
/**
* 构建JSON字符串(简单版)
*/
public static String toJson(Map<String, Object> map) {
if (map == null || map.isEmpty()) {
return "{}";
}
StringBuilder sb = new StringBuilder("{");
boolean first = true;
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (!first) {
sb.append(", ");
}
first = false;
sb.append("\"").append(entry.getKey()).append("\": ");
Object value = entry.getValue();
if (value == null) {
sb.append("null");
} else if (value instanceof String) {
sb.append("\"").append(value).append("\"");
} else {
sb.append(value);
}
}
sb.append("}");
return sb.toString();
}
}// × 错误:使用 == 比较字符串内容
String str1 = new String("Hello");
String str2 = new String("Hello");
if (str1 == str2) { // false,比较的是引用
System.out.println("相等");
}
// √ 正确:使用 equals() 比较字符串内容
if (str1.equals(str2)) { // true,比较内容
System.out.println("相等");
}
// √ 更安全:常量在前,避免空指针
if ("Hello".equals(str1)) { // 推荐
System.out.println("相等");
}// × 错误:循环中使用 + 拼接,性能极差
String result = "";
for (int i = 0; i < 10000; i++) {
result += i; // 每次都创建新对象,O(n²) 时间复杂度
}
// √ 正确:使用 StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append(i); // O(n) 时间复杂度
}
String result = sb.toString();// × 错误:所有字符串都 intern(),可能导致常量池过大
public void processData(List<String> data) {
for (String item : data) {
String interned = item.intern(); // 滥用!
// ...
}
}
// √ 正确:仅对大量重复的字符串使用 intern()
public void processData(List<String> data) {
for (String item : data) {
// 仅对有限的状态值使用 intern()
if (isStatusValue(item)) {
String interned = item.intern();
}
}
}// × 错误:不指定字符编码,跨平台可能出错
byte[] bytes = str.getBytes(); // 使用平台默认字符集
String decoded = new String(bytes);
// √ 正确:显式指定字符编码
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
String decoded = new String(bytes, StandardCharsets.UTF_8);// JDK 6 中,substring() 会共享原字符数组
// 如果原字符串很大,substring 后的小字符串会持有大数组引用
// × JDK 6 问题代码
String large = new String(new char[1000000]); // 1MB 字符串
String small = large.substring(0, 10); // 只需要 10 字符
// 但 small 仍持有 1MB 数组的引用!
// √ 解决方案:创建新字符串
String small = new String(large.substring(0, 10));
// JDK 7+ 已修复此问题,substring() 会创建新数组// JDK 6: 字符串常量池在永久代,大小有限
// JDK 7+: 字符串常量池在堆中,更容易管理
// 在 JDK 6 中大量使用 intern() 可能导致 PermGen OOM
// 在 JDK 7+ 中会抛出 Java heap space OOM// 编译器优化有限,仅适用于常量表达式
// √ 编译期优化
String str1 = "Hello" + " " + "World"; // 编译为 "Hello World"
// × 运行时无法优化
String str2 = getString() + " " + getString(); // 运行时拼接
// √ 循环外无法优化
String str3 = "";
for (int i = 0; i < 10; i++) {
str3 += i; // 必须使用 StringBuilder
}Q: String 为什么是不可变的?有什么好处?
A:
为什么不可变:
final 修饰类,不可继承final 修饰字符数组,引用不可变好处:
Q: 字符串常量池是什么?有什么作用?
A:
new String() 不进入常量池intern() 手动加入常量池Q: String、StringBuilder、StringBuffer 的区别?
A:
| 特性 | String | StringBuilder | StringBuffer |
|---|---|---|---|
| 可变性 | 不可变 | 可变 | 可变 |
| 线程安全 | 是 | 否 | 是 |
| 性能 | 低 | 最高 | 中等 |
Q: String.intern() 方法的作用?
A:
Q: 如何优化字符串拼接性能?
A:
+,编译器会优化StringBuilderString.join() 或 Collectors.joining()StringBufferStringBuilder 预分配容量Q: == 和 equals() 的区别?
A:
==: 比较引用(地址),判断是否为同一对象equals(): 比较内容,判断字符串内容是否相同equals() 比较字符串内容"constant".equals(variable)Q: String 的 hashCode() 是如何实现的?
A:
// JDK 源码
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]Q: JDK 9 中 String 的变化?
A:
byte[] 代替 char[]Q: 如何反转字符串?
A:
// 方法 1: 使用 StringBuilder
String reversed = new StringBuilder(str).reverse().toString();
// 方法 2: 手动反转
char[] chars = str.toCharArray();
for (int i = 0, j = chars.length - 1; i < j; i++, j--) {
char temp = chars[i];
chars[i] = chars[j];
chars[j] = temp;
}
String reversed = new String(chars);Q: 字符串编码的注意事项?
A:
== 比较字符串内容+ 拼接intern() 优化内存,但不要滥用strip()、isBlank()、repeat() 等"constant".equals(variable) 比较方式StringBuilder,多线程用 StringBufferPattern.compile()| 特性 | 旧版(Java 8) | Java 11/15/21 |
|---|---|---|
| 去除空白 | trim() 仅去 ASCII 空格 | strip() 按 Unicode 空白处理(Java 11) |
| 空白判断 | trim().isEmpty() 手写 | isBlank()(Java 11) |
| 重复/分割 | 手写循环拼接 | repeat()、lines()(Java 11) |
| 多行文本 | + 拼接 + 手工转义 | 文本块 """..."""(Java 15 正式)+ formatted() |
| 函数式转换 | 无 | transform()(Java 12)、indent()(Java 12) |