V
Vel·ToolKit
简洁 · 高效 · 即开即用
ZH
第 16 章 / 共 20 章

字符串与文本块

String 不可变、StringBuilder、Text Blocks、format、编码

字符串在 Java 中的特殊性

应用场景:日志消息、HTTP 请求/响应、SQL 拼接、模板渲染、CLI 参数处理。Java 的 String 设计目标是"安全 + 快"——它是不可变的、被字面量池缓存、可以作为 Map 的 key 不变质,因此在系统中流转特别频繁。

String 不可变 + 字符串字面量池

String 对象创建后内容不可修改:任何"修改"操作(replace、trim、toLowerCase…)都返回新对象。源码里相同的字面量(如 "hello")被 JVM 池化为同一对象,节省内存。new String("hello") 会强制创建新对象,绕过池——除非有特别理由,**不要 new String**。

// StringIdentity.java
public class StringIdentity {
    public static void main(String[] args) {
        String a = "hello";
        String b = "hello";              // 同一池对象
        String c = new String("hello");  // 新对象,绕过池
        String d = c.intern();             // 显式入池,再次拿到池对象

        System.out.println(a == b);   // true(同一对象)
        System.out.println(a == c);   // false(c 是 new 的)
        System.out.println(a == d);   // true(intern 后入池)

        // 永远用 equals 比较内容
        System.out.println(a.equals(c)); // true
    }
}

StringBuilder:大量拼接必备

应用场景:循环里拼字符串、构建 CSV / SQL / 日志行。String 的 + 操作每次都生成新对象,循环里用 + 是 O(n²) 性能灾难。StringBuilder 是可变的内部缓冲区,最后一次性 toString()——性能差距能到几百倍。StringBuffer 是 StringBuilder 的线程安全版(带 synchronized),日常很少用。

// Builder.java
public class Builder {
    public static void main(String[] args) {
        // ❌ 错误示范:循环里用 +
        // String s = "";
        // for (int i = 0; i < 100_000; i++) s += i;     // 极慢

        // ✅ StringBuilder
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 5; i++) {
            sb.append(i).append(",");
        }
        if (sb.length() > 0) sb.deleteCharAt(sb.length() - 1);
        System.out.println(sb);          // 0,1,2,3,4

        // 链式 API
        String json = new StringBuilder()
            .append("{\"name\":\"").append("Alice").append("\",")
            .append("\"age\":").append(30).append("}")
            .toString();
        System.out.println(json);
    }
}

Text Blocks 多行字符串(Java 15+)

应用场景:内嵌 SQL、JSON、HTML、SQL 模板、错误消息——以前要写一堆 \n 和转义引号,现在用三引号块。编译器自动按最左对齐位置去缩进。

// TextBlock.java
public class TextBlock {
    public static void main(String[] args) {
        String sql = """
                SELECT id, name, email
                FROM users
                WHERE status = 'active'
                  AND created_at > ?
                ORDER BY id
                """;
        System.out.println(sql);

        String json = """
                {
                    "name": "Alice",
                    "age": 30,
                    "tags": ["java", "go"]
                }
                """;
        System.out.println(json);

        // \<newline> 续行:拼成单行
        String long1 = """
                this is a very \
                long single line
                """;
        System.out.println(long1);
    }
}

String.format / formatted

应用场景:构造带占位符的字符串——日志、错误消息、用户面板。printf 风格的 %s %d %.2f 等。Java 15+ 添加了实例方法 "...".formatted(args),可读性更好。

// FormatDemo.java
public class FormatDemo {
    public static void main(String[] args) {
        String name = "Alice";
        int age = 30;
        double score = 87.5;

        // 经典:String.format
        String s1 = String.format("%s is %d (score=%.2f)", name, age, score);
        System.out.println(s1);

        // Java 15+:实例方法
        String s2 = "%s is %d (score=%.2f)".formatted(name, age, score);
        System.out.println(s2);

        // 宽度对齐、补零
        System.out.println(String.format("%-10s|%5d|%08d", "id", 42, 7));
        // id        |   42|00000007
    }
}

String.join 与 split

join 把集合元素用分隔符串起来,常用于 CSV、SQL IN 列表、日志格式化。split 按正则切分。注意:split 入参是正则——切 . 要写 "\\."。

// JoinSplit.java
import java.util.List;

public class JoinSplit {
    public static void main(String[] args) {
        List<String> tags = List.of("java", "go", "rust");
        String csv = String.join(", ", tags);
        System.out.println(csv);              // java, go, rust

        // 构造 SQL IN 列表(实际用 PreparedStatement 占位符更安全)
        String inList = String.join(",", tags);
        String sql = "SELECT * FROM lang WHERE name IN (" + inList + ")";
        System.out.println(sql);

        // split:按正则
        String line = "a, b,  c , d";
        String[] parts = line.split("\\s*,\\s*");  // 任意空白 + 逗号 + 任意空白
        for (String p : parts) System.out.println("[" + p + "]");

        // 切英文句号:需要转义
        String[] segs = "www.example.com".split("\\.");
        for (String s : segs) System.out.println(s);
    }
}

常用方法速查

// StringApi.java
public class StringApi {
    public static void main(String[] args) {
        String s = "  Hello, Java World!  ";

        // 长度 / 索引访问
        System.out.println(s.length());
        System.out.println(s.charAt(2));

        // 大小写、去空白
        System.out.println(s.trim());                 // 去首尾 ASCII 空白
        System.out.println(s.strip());                // 去首尾 Unicode 空白(Java 11+,推荐)
        System.out.println(s.toLowerCase());
        System.out.println(s.toUpperCase());

        // 子串与查找
        System.out.println(s.substring(2, 7));        // Hello
        System.out.println(s.indexOf("Java"));
        System.out.println(s.contains("World"));
        System.out.println(s.startsWith("  Hello"));
        System.out.println(s.endsWith("!  "));

        // 替换
        System.out.println(s.replace("World", "Universe"));
        System.out.println(s.replaceAll("\\s+", "_")); // 正则替换

        // 空判断
        System.out.println("".isEmpty());             // true
        System.out.println("   ".isBlank());          // true(Java 11+)

        // 重复 (Java 11+)
        System.out.println("=".repeat(10));
    }
}

字符串与字节(编码)

应用场景:网络收发、写文件、Base64 编码、加密哈希。String <-> byte[] 转换**必须**明确指定字符集(推荐 UTF-8),否则会用平台默认编码——在不同操作系统上行为不同,是经典的乱码源。

// Bytes.java
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

public class Bytes {
    public static void main(String[] args) {
        String s = "你好 Java";

        // String -> byte[]
        byte[] utf8 = s.getBytes(StandardCharsets.UTF_8);
        byte[] gbk  = s.getBytes(java.nio.charset.Charset.forName("GBK"));
        System.out.println("utf8 bytes = " + utf8.length); // 11
        System.out.println("gbk  bytes = " + gbk.length);  // 9

        // byte[] -> String
        String back = new String(utf8, StandardCharsets.UTF_8);
        System.out.println(back);

        // 16 进制查看
        StringBuilder hex = new StringBuilder();
        for (byte b : utf8) hex.append(String.format("%02x ", b));
        System.out.println(hex);
    }
}