Java字符串详解

Java 字符串详解

一、String(字符串常量)

1. 核心特性

  • 不可变性:String对象一旦创建,值就不能改变

  • 字符串常量池:节省内存,提高性能

  • 线程安全:因为不可变

2. 创建方式对比

复制代码
// 方式1:字面量创建 - 在常量池
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2);  // true,指向常量池同一对象

// 方式2:new创建 - 在堆内存
String s3 = new String("hello");
String s4 = new String("hello");
System.out.println(s3 == s4);    // false,不同对象
System.out.println(s3.equals(s4)); // true,值相同

// 方式3:编译期优化
String s5 = "he" + "llo";  // 编译时优化为"hello"
String s6 = "hello";
System.out.println(s5 == s6);  // true

// 方式4:运行时拼接
String s7 = "he";
String s8 = s7 + "llo";  // 运行时创建新对象
String s9 = "hello";
System.out.println(s8 == s9);  // false

3. 内存模型分析

复制代码
String a = "abc";  // 常量池
String b = "abc";  // 常量池,指向同一个
String c = new String("abc");  // 堆内存,新对象
String d = c.intern();  // 放入常量池,返回常量池引用
System.out.println(a == b);  // true
System.out.println(a == c);  // false
System.out.println(a == d);  // true

4. 常用方法示例

复制代码
String str = "Hello,World!Java";

// 1. 长度和判空
int len = str.length();         // 16
boolean empty = str.isEmpty();  // false
boolean blank = str.isBlank();  // false (Java 11+)

// 2. 查找
char ch = str.charAt(1);        // 'e'
int index1 = str.indexOf('o');  // 4
int index2 = str.indexOf("Java"); // 12
int index3 = str.lastIndexOf('o'); // 8
boolean contains = str.contains("World"); // true
boolean starts = str.startsWith("Hello"); // true
boolean ends = str.endsWith("Java");     // true

// 3. 截取
String sub1 = str.substring(7);      // "World!Java"
String sub2 = str.substring(7, 12);  // "World"

// 4. 替换
String rep1 = str.replace('o', '0');     // "Hell0,W0rld!Java"
String rep2 = str.replace("Java", "Python"); // "Hello,World!Python"
String rep3 = str.replaceAll("[aeiou]", "*"); // "H*ll*,W*rld!J*v*"

// 5. 分割
String[] parts = str.split(",");  // ["Hello", "World!Java"]
String[] parts2 = str.split("[,!]"); // ["Hello", "World", "Java"]

// 6. 转换
String upper = str.toUpperCase();  // "HELLO,WORLD!JAVA"
String lower = str.toLowerCase();  // "hello,world!java"
String trim = "  hello  ".trim();  // "hello"
String strip = "  hello  ".strip(); // "hello" (Java 11+, 支持Unicode)

// 7. 比较
boolean eq1 = "hello".equals("Hello");  // false
boolean eq2 = "hello".equalsIgnoreCase("Hello");  // true
int cmp = "abc".compareTo("abd");  // -1 (字典序比较)

// 8. 其他
String repeat = "ab".repeat(3);  // "ababab" (Java 11+)
String join = String.join("-", "A", "B", "C");  // "A-B-C"
String format = String.format("Name: %s, Age: %d", "Tom", 20);

5. 性能陷阱

复制代码
// ❌ 错误:大量字符串拼接
String result = "";
for (int i = 0; i < 10000; i++) {
    result += i;  // 每次循环都创建新String对象!
}
// 创建了10000个对象,性能极差

// ✅ 正确:使用StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
    sb.append(i);
}
String result = sb.toString();

==和equals的区别

基本类型用 ==,引用类型用 equals

String 比较用 equals,注意常量池特性

Integer 缓存 -128~127,超出范围 equals

重写 equals 必重写 hashCode,遵守契约

null 比较要小心,用 Objects.equals 最安全


二、StringBuilder(非线程安全可变字符串)

1. 核心特性

  • 可变性:内部维护char数组,可修改

  • 非线程安全:性能更高

  • 单线程首选

2. 常用方法

复制代码
StringBuilder sb = new StringBuilder();

// 1. 追加
sb.append("Hello");
sb.append(" ").append("World");  // 链式调用
sb.append(123).append(3.14).append(true);

// 2. 插入
sb.insert(5, ",");  // 在索引5插入

// 3. 删除
sb.delete(5, 6);     // 删除索引5-6(不包括6)
sb.deleteCharAt(0);  // 删除第一个字符

// 4. 替换
sb.replace(0, 5, "Hi");  // 替换索引0-5

// 5. 反转
sb.reverse();  // 字符串反转

// 6. 容量相关
sb.ensureCapacity(100);  // 确保最小容量
int cap = sb.capacity();  // 当前容量
sb.trimToSize();         // 缩容到实际大小

// 7. 其他
int len = sb.length();     // 实际长度
char ch = sb.charAt(0);   // 获取字符
sb.setCharAt(0, 'h');     // 设置字符
String sub = sb.substring(0, 2);  // 截取

3. 内部原理

复制代码
// StringBuilder内部是char数组
public final class StringBuilder extends AbstractStringBuilder {
    char[] value;  // 存储字符
    
    // 默认构造器:容量16
    StringBuilder() {
        super(16);
    }
    
    // 指定初始容量
    StringBuilder(int capacity) {
        super(capacity);
    }
    
    // 指定初始字符串
    StringBuilder(String str) {
        super(str.length() + 16);
        append(str);
    }
}

4. 扩容机制

复制代码
// 当容量不足时,扩容策略:
// 新容量 = 旧容量 * 2 + 2
// 如果还不够,则直接扩容到需要的大小

StringBuilder sb = new StringBuilder();  // 容量16
for (int i = 0; i < 100; i++) {
    sb.append(i);  // 会自动扩容
}
// 扩容历史:16 → 34 → 70 → 142

三、StringBuffer(线程安全可变字符串)

1. 核心特性

  • 可变性:同StringBuilder

  • 线程安全:方法用synchronized修饰

  • 性能略低于StringBuilder

2. 与StringBuilder对比

复制代码
// 性能测试
int count = 1000000;
long start, end;

// StringBuilder测试
start = System.currentTimeMillis();
StringBuilder sb1 = new StringBuilder();
for (int i = 0; i < count; i++) {
    sb1.append(i);
}
end = System.currentTimeMillis();
System.out.println("StringBuilder: " + (end - start) + "ms");

// StringBuffer测试
start = System.currentTimeMillis();
StringBuffer sb2 = new StringBuffer();
for (int i = 0; i < count; i++) {
    sb2.append(i);
}
end = System.currentTimeMillis();
System.out.println("StringBuffer: " + (end - start) + "ms");
// 结果:StringBuilder 比 StringBuffer 快约20-30%

3. 源码分析

复制代码
public final class StringBuffer extends AbstractStringBuilder {
    // 大部分方法都有 synchronized
    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }
    
    @Override
    public synchronized StringBuffer delete(int start, int end) {
        toStringCache = null;
        super.delete(start, end);
        return this;
    }
    
    // 独有的 toStringCache
    private transient char[] toStringCache;
    
    @Override
    public synchronized String toString() {
        if (toStringCache == null) {
            toStringCache = Arrays.copyOfRange(value, 0, count);
        }
        return new String(toStringCache, 0, count);
    }
}

四、StringJoiner(Java 8+ 字符串拼接器)

1. 核心特性

  • 专门用于拼接字符串序列

  • 可指定分隔符、前缀、后缀

  • 内部使用StringBuilder

2. 基本使用

复制代码
// 1. 基本拼接
StringJoiner sj1 = new StringJoiner(", ");
sj1.add("Apple");
sj1.add("Banana");
sj1.add("Orange");
System.out.println(sj1.toString());  // "Apple, Banana, Orange"

// 2. 带前缀后缀
StringJoiner sj2 = new StringJoiner(", ", "[", "]");
sj2.add("Red");
sj2.add("Green");
sj2.add("Blue");
System.out.println(sj2.toString());  // "[Red, Green, Blue]"

// 3. 合并StringJoiner
StringJoiner sj3 = new StringJoiner("-", "(", ")");
sj3.add("A").add("B");

StringJoiner sj4 = new StringJoiner(":", "{", "}");
sj4.add("X").add("Y");

sj3.merge(sj4);
System.out.println(sj3.toString());  // "(A-B-X:Y)"

3. String.join() 的便捷方法

复制代码
// 数组拼接
String[] arr = {"Java", "Python", "C++"};
String result1 = String.join(" | ", arr);  // "Java | Python | C++"

// 集合拼接
List<String> list = Arrays.asList("Tom", "Jerry", "Spike");
String result2 = String.join(", ", list);  // "Tom, Jerry, Spike"

// 多个元素拼接
String result3 = String.join("-", "2024", "01", "15");  // "2024-01-15"

五、Pattern 和 Matcher(正则表达式)

1. Pattern(模式)

复制代码
import java.util.regex.Pattern;
import java.util.regex.Matcher;

// 1. 编译正则表达式
Pattern pattern = Pattern.compile("\\d{3}-\\d{4}-\\d{4}");

// 2. 创建Matcher
Matcher matcher = pattern.matcher("电话:138-1234-5678");

// 3. 常用方法
boolean matches = matcher.matches();  // 全匹配
boolean find = matcher.find();        // 查找匹配
String found = matcher.group();      // 获取匹配文本
int start = matcher.start();         // 匹配开始索引
int end = matcher.end();             // 匹配结束索引

2. Matcher(匹配器)详解

复制代码
String text = "订单号:ORD-2024-001, ORD-2024-002, ORD-2024-003";
String regex = "ORD-\\d{4}-\\d{3}";

Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(text);

// 1. 查找所有匹配
System.out.println("查找所有匹配:");
while (matcher.find()) {
    System.out.println("找到: " + matcher.group() + 
                      " 位置: " + matcher.start() + "-" + matcher.end());
}

// 2. 重置匹配器
matcher.reset();

// 3. 分组捕获
String html = "<h1>标题</h1><p>内容</p>";
Pattern tagPattern = Pattern.compile("<([a-z][a-z0-9]*)[^>]*>([^<]+)</\\1>");
Matcher tagMatcher = tagPattern.matcher(html);

while (tagMatcher.find()) {
    String fullTag = tagMatcher.group(0);  // 整个匹配
    String tagName = tagMatcher.group(1);  // 第一个分组:标签名
    String content = tagMatcher.group(2);  // 第二个分组:内容
    System.out.println("标签: " + tagName + ", 内容: " + content);
}

3. 常用正则表达式示例

复制代码
// 1. 邮箱验证
Pattern emailPattern = Pattern.compile("^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$");
boolean isEmail = emailPattern.matcher("test@example.com").matches();

// 2. 手机号验证
Pattern phonePattern = Pattern.compile("^1[3-9]\\d{9}$");
boolean isPhone = phonePattern.matcher("13812345678").matches();

// 3. 身份证验证
Pattern idPattern = Pattern.compile("^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9Xx]$");

// 4. 提取数字
String text2 = "价格:$199.99,重量:2.5kg";
Pattern numPattern = Pattern.compile("\\d+(\\.\\d+)?");
Matcher numMatcher = numPattern.matcher(text2);
while (numMatcher.find()) {
    System.out.println("数字: " + numMatcher.group());
}

// 5. 替换操作
String replaced = text2.replaceAll("\\d+", "#");
System.out.println(replaced);  // "价格:$#.##,重量:#.#kg"

4. Pattern 的静态方法

复制代码
// 1. matches() 快速匹配
boolean quickMatch = Pattern.matches("\\d+", "12345");  // true

// 2. split() 分割
String[] parts = Pattern.compile("\\s*,\\s*").split("a,b, c , d");
// ["a", "b", "c", "d"]

// 3. quote() 字面量转义
String literal = Pattern.quote("$100.00");
// 返回 "\Q$100.00\E",可安全用作字面量

六、性能对比与选择指南

1. 性能对比表

特性 String StringBuilder StringBuffer StringJoiner
可变性 不可变 可变 可变 可变(内部用StringBuilder)
线程安全
性能 低(拼接时)
使用场景 常量字符串 单线程字符串操作 多线程字符串操作 拼接带分隔符的序列
内存 常量池/堆

2. 选择策略

复制代码
// 场景1:字符串常量
String str = "hello";  // ✅ 最佳选择

// 场景2:少量字符串拼接
String result = "Hello, " + name + "!";  // ✅ 编译器优化

// 场景3:循环中大量拼接
StringBuilder sb = new StringBuilder();  // ✅ 最佳
for (String item : list) {
    sb.append(item);
}

// 场景4:多线程环境拼接
StringBuffer sbf = new StringBuffer();  // ✅ 线程安全
synchronized(lock) {
    sbf.append(data);
}

// 场景5:拼接带分隔符的列表
StringJoiner sj = new StringJoiner(",");  // ✅ 最简洁
for (String item : list) {
    sj.add(item);
}

// 场景6:正则表达式复杂匹配
Pattern pattern = Pattern.compile(regex);  // ✅
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
    // 处理匹配
}

3. 面试常见问题

Q1:String为什么设计为不可变?

  1. 安全性:可作为HashMap的key,线程安全

  2. 缓存哈希值:提高性能

  3. 字符串常量池:节省内存

  4. 线程安全:天然线程安全

Q2:String的intern()方法作用?

  • 将字符串放入常量池

  • 如果常量池已有,返回池中引用

  • 如果常量池没有,放入并返回引用

  • 用于节省内存

Q3:StringBuilder和StringBuffer区别?

  • 线程安全:StringBuffer是线程安全的(synchronized)

  • 性能:StringBuilder在单线程下性能更好

  • 使用场景:单线程用StringBuilder,多线程用StringBuffer

Q4:如何选择字符串拼接方式?

复制代码
// 少量固定字符串
String s1 = "a" + "b" + "c";  // 编译器优化

// 循环中拼接
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append(i);
}

// 列表拼接
String.join(", ", list);  // Java 8+
StringJoiner sj = new StringJoiner(", ");

// 格式化拼接
String.format("姓名:%s,年龄:%d", name, age);
MessageFormat.format("价格:{0,number,#.##}", price);

Q5:String的长度限制?

  • 编译期常量:最大长度65535(类文件限制)

  • 运行期:最大Integer.MAX_VALUE(约21亿)

  • 实际受JVM堆内存限制

最佳实践总结

  1. 字符串常量用String

  2. 单线程大量拼接用StringBuilder

  3. 多线程拼接用StringBuffer

  4. 带分隔符的序列拼接用StringJoiner

  5. 正则复杂匹配用Pattern/Matcher

  6. 简单正则用Stringmatches()/replaceAll()

  7. 优先使用String.join()String.format()等工具方法

相关推荐
麦兜*17 小时前
【Spring Boot】 接口性能优化“十板斧”:从数据库连接到 JVM 调优的全链路提升
java·大数据·数据库·spring boot·后端·spring cloud·性能优化
屋檐上的大修勾17 小时前
AI算力开放-yolov8适配 mmyolo大疆无人机
开发语言·python
郑州光合科技余经理17 小时前
架构解析:同城本地生活服务o2o平台海外版
大数据·开发语言·前端·人工智能·架构·php·生活
天远云服17 小时前
Go语言高并发实战:集成天远多头借贷行业风险版API构建实时风控引擎
大数据·开发语言·golang·iphone
一条咸鱼_SaltyFish17 小时前
[Day12] 合同审查引擎开发中的技术挑战与解决之道 contract-review-engine
开发语言·人工智能·程序人生·开源软件·ddd·个人开发·ai编程
zho_uzhou17 小时前
倍福指针使用——始终为字节形式
开发语言
郑州光合科技余经理17 小时前
开发实战:海外版同城o2o生活服务平台核心模块设计
开发语言·git·python·架构·uni-app·生活·智慧城市
Kratzdisteln17 小时前
【Python】Flask 2
开发语言·python·flask
廋到被风吹走17 小时前
【Spring 】Spring Security深度解析:过滤器链、认证授权架构与现代集成方案
java·spring·架构