Java 中的 String 类使用 final 修饰,底层依靠不可变字符数组实现。这种设计基于性能、安全性和并发处理的系统化思考,为 Java 提供了可靠的文本处理基础。
String 的底层实现
在 Java 9 之前,String 内部使用char[]
数组存储字符:
java
public final class String {
private final char value[];
private int hash; // 缓存的哈希码
// 其他字段和方法...
}
Java 9 及以后版本中,String 改用byte[]
数组加编码标记:
java
public final class String {
private final byte[] value;
private final byte coder; // 标记编码方式
private int hash;
// 其他字段和方法...
}
Java 9 的编码优化
Java 9 引入的 CompactStrings 优化将字符串内部表示从固定使用 UTF-16(char 数组)改为根据内容选择 Latin-1 或 UTF-16 编码(byte 数组):
java
static final byte LATIN1 = 0;
static final byte UTF16 = 1;
这种设计为主要由 ASCII 字符组成的字符串(如英文文本、JSON 等)节省了约 50%的内存空间。默认通过 JVM 参数启用:
ruby
-XX:+CompactStrings // 默认启用
-XX:-CompactStrings // 禁用压缩字符串
String 内部结构图示:

不可变性的实现机制
String 的不可变性是通过整体设计而非单一关键字实现的,包括:
- 类被 final 修饰,防止子类破坏不可变性
- 内部字符数组被 private final 修饰,引用不能改变
- 没有提供修改内部数组内容的方法
- 所有返回 String 的操作都创建新对象
重要说明:final 仅确保引用不变,但不保证引用指向的对象内容不变。String 通过不提供修改数组内容的方法,从而确保真正的不可变性。
简单示例:
java
private static final Logger logger = LoggerFactory.getLogger(StringExample.class);
public void demonstrateImmutability() {
String original = "Hello";
String modified = original.concat(" World");
if (logger.isDebugEnabled()) {
logger.debug("原始字符串: {}", original); // 输出: Hello
logger.debug("修改后字符串: {}", modified); // 输出: Hello World
logger.debug("是否相同对象: {}", original == modified); // 输出: false
}
}
不可变性的优势
1. 安全性
字符串常用于存储敏感信息,不可变性保证数据不被意外修改。
java
public void authenticate(String username, String password) {
// password在传递过程中不会被修改
validateUser(username, password);
logAttempt(username); // 即使这里有问题,也不会影响原始password
}
Web 安全中,不可变性也有助于防御 XSS 攻击:
java
private static final Logger logger = LoggerFactory.getLogger(WebSecurity.class);
// 使用专业库进行HTML转义,而非简单替换
public String sanitizeUserInput(String userInput) {
if (userInput == null) {
return "";
}
try {
// 使用OWASP Java Encoder进行安全转义
return org.owasp.encoder.Encode.forHtml(userInput);
} catch (Exception e) {
logger.error("HTML转义失败", e);
// 失败时返回空字符串,确保安全
return "";
}
}
2. 线程安全
不可变对象天生线程安全,多个线程可以同时访问而无需同步。
java
// 可以被多线程安全读取的配置
public static final Map<String, String> CONFIG = new ConcurrentHashMap<>();
private static final Logger logger = LoggerFactory.getLogger(ThreadSafeExample.class);
// 多线程环境中,不需要担心String值被修改
public void process(String key) {
String value = CONFIG.get(key); // 无需额外同步
if (value != null && logger.isDebugEnabled()) {
logger.debug("处理配置项: {}", key);
// 处理value...
}
}
3. 哈希缓存
String 类缓存其哈希值,提高在 HashMap 和 HashSet 中的性能。
java
public final class String {
private int hash; // 默认为0
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
// 计算哈希值
hash = h = ...计算哈希...;
}
return h;
}
}
实际应用:
java
Map<String, User> userCache = new HashMap<>();
userCache.put(userId, user);
// 多次获取同一key,哈希值只计算一次
User user1 = userCache.get(userId); // 第一次计算哈希
User user2 = userCache.get(userId); // 使用缓存的哈希值
4. 字符串池优化
字符串池(String Pool)能安全实现,因为池中的字符串不会被修改。可通过 JVM 参数调整字符串池大小:
ini
-XX:StringTableSize=N // 调整字符串池大小,Java 8默认为60013
字符串池结构:

字符串池与 intern()方法
字符串池是 Java 堆内存中的一个特殊区域,用于存储字符串字面量和通过intern()
方法显式添加的字符串。
java
private static final Logger logger = LoggerFactory.getLogger(StringPoolExample.class);
public void demonstrateStringPool() {
// 字面量,直接进入字符串池
String s1 = "Hello";
String s2 = "Hello";
if (logger.isDebugEnabled()) {
logger.debug("s1 == s2: {}", s1 == s2); // true,指向同一对象
}
// 使用new,创建堆中的新对象
String s3 = new String("Hello");
logger.debug("s1 == s3: {}", s1 == s3); // false,不同对象
// 使用intern()将字符串添加到池中或返回池中已有的引用
String s4 = s3.intern();
logger.debug("s1 == s4: {}", s1 == s4); // true,s4指向池中对象
}
intern()的内存风险
过度使用intern()
可能导致问题:
java
private static final Logger logger = LoggerFactory.getLogger(InternWarning.class);
// 风险示例:大量intern()可能导致字符串池溢出
public void riskyInternUsage() {
List<String> list = new ArrayList<>();
try {
for (int i = 0; i < 10_000_000; i++) {
// 每个字符串都不同,但都被intern到池中
String data = ("Data" + i).intern();
list.add(data);
}
} catch (OutOfMemoryError e) {
logger.error("字符串池内存溢出", e);
}
}
// 安全使用intern()
public static String internWithCaution(String str) {
// 只对短字符串且频繁出现的使用intern()
if (str != null && str.length() <= 15) {
return str.intern();
}
return str;
}
Java 11+引入的新 String 方法
Java 11 及更高版本引入了多个便捷的 String 处理方法:
java
private static final Logger logger = LoggerFactory.getLogger(NewStringMethods.class);
public void demonstrateNewMethods() {
// Java 11: strip() - 类似trim()但支持Unicode空白
String text = " Hello World ";
logger.info("strip(): '{}'", text.strip()); // "Hello World"
logger.info("stripLeading(): '{}'", text.stripLeading()); // "Hello World "
logger.info("stripTrailing(): '{}'", text.stripTrailing()); // " Hello World"
// Java 11: isBlank() - 检查字符串是否为空或仅包含空白
logger.info("isBlank(): {}", " ".isBlank()); // true
logger.info("isBlank(): {}", "Hello".isBlank()); // false
// Java 11: repeat() - 重复字符串指定次数
logger.info("repeat(): {}", "Ha".repeat(3)); // "HaHaHa"
// Java 12: transform() - 转换字符串
String transformed = "hello".transform(s -> s.toUpperCase());
logger.info("transform(): {}", transformed); // "HELLO"
// Java 12: indent() - 添加或移除缩进
String code = "if (condition) {\n System.out.println();\n}";
logger.info("indent(2):\n{}", code.indent(2)); // 添加2个缩进
}
Java 15 的文本块
Java 15 正式引入文本块(Text Blocks),使多行字符串处理更简洁:
java
private static final Logger logger = LoggerFactory.getLogger(TextBlockExample.class);
public void demonstrateTextBlocks() {
// 传统多行字符串
String oldSql = "SELECT id, name, email\n" +
"FROM users\n" +
"WHERE active = true\n" +
"ORDER BY name";
// Java 15文本块
String sql = """
SELECT id, name, email
FROM users
WHERE active = true
ORDER BY name
""";
logger.info("SQL查询:\n{}", sql);
// HTML示例
String html = """
<html>
<body>
<h1>Hello, World!</h1>
</body>
</html>
""";
// JSON示例
String json = """
{
"name": "John Doe",
"age": 30,
"address": {
"street": "123 Main St",
"city": "Anytown"
}
}
""";
}
文本块优势:
- 保留格式,无需转义引号
- 避免手动添加换行符
- 提高复杂字符串的可读性
- 编译器优化,性能与常规字符串相同
内存分析与监控
使用专业工具分析 String 的内存使用:
java
private static final Logger logger = LoggerFactory.getLogger(MemoryAnalysis.class);
public void analyzeStringMemory() {
// 创建大量字符串
List<String> strings = new ArrayList<>(1_000_000);
for (int i = 0; i < 1_000_000; i++) {
strings.add("String" + i);
}
// 记录内存使用
Runtime runtime = Runtime.getRuntime();
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
logger.info("创建100万个字符串后使用内存: {} MB", usedMemory / (1024 * 1024));
// 使用VisualVM或JProfiler进行进一步分析
logger.info("应用PID: {}, 请使用VisualVM连接进行分析", ProcessHandle.current().pid());
try {
Thread.sleep(10000); // 等待连接分析工具
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
使用 Java Flight Recorder 分析 String 操作
java
private static final Logger logger = LoggerFactory.getLogger(JfrAnalysis.class);
public void analyzeWithJFR() {
// 启动JFR记录
try {
logger.info("启动JFR记录...");
ProcessBuilder pb = new ProcessBuilder(
"jcmd", String.valueOf(ProcessHandle.current().pid()),
"JFR.start", "name=StringAnalysis", "duration=60s", "filename=string_analysis.jfr"
);
pb.start();
// 执行String密集型操作
logger.info("执行String操作...");
performStringOperations();
// 等待JFR完成记录
logger.info("等待JFR记录完成...");
Thread.sleep(61000);
logger.info("JFR记录已完成,请分析string_analysis.jfr文件");
} catch (Exception e) {
logger.error("JFR分析失败", e);
}
}
private void performStringOperations() {
// 执行各种String操作以便JFR捕获
for (int i = 0; i < 100000; i++) {
String s1 = "test" + i;
String s2 = s1.toUpperCase();
String s3 = s1 + s2;
// 其他操作...
}
}
不同 JDK 版本的内存使用对比
Java 8 与 Java 11 在处理相同字符串数据时的内存差异:
JDK 版本 | 100 万个 ASCII 字符串 | 100 万个 Unicode 字符串 |
---|---|---|
Java 8 (char[]) | 约 76MB | 约 76MB |
Java 11 (byte[]) | 约 38MB | 约 76MB |
这显示了 Java 9+的 CompactStrings 优化对 ASCII 文本的显著内存节省。
equals()与==比较
初学者常见的困惑点:
java
private static final Logger logger = LoggerFactory.getLogger(StringCompare.class);
public void demonstrateComparison() {
String a = "test";
String b = "test";
String c = new String("test");
logger.info("a == b: {}", a == b); // true,都指向字符串池中同一对象
logger.info("a == c: {}", a == c); // false,a指向池,c指向堆中新对象
logger.info("a.equals(c): {}", a.equals(c)); // true,内容相同
}
规则:
==
比较引用(对象地址)equals()
比较字符串内容

String 与字符数组互转的性能影响
字符串与字符数组互转是常见操作,但有性能影响:
java
// 字符数组转字符串 - 创建新对象
char[] chars = {'H', 'e', 'l', 'l', 'o'};
String str = new String(chars); // 复制数组内容
// 字符串转字符数组 - 创建新数组
char[] chars2 = str.toCharArray(); // 复制字符串内容
性能注意点:
- 每次转换都会复制全部内容,产生新对象
- 频繁转换会增加 GC 压力
- 对大字符串操作时尤其耗费资源
优化示例:
java
// 避免重复转换
char[] buffer = new char[1024];
int len = reader.read(buffer);
process(buffer, len); // 直接处理字符数组
为什么 String 类被设计为 final
1. 保证不可变性
防止子类覆盖方法破坏不可变特性,例如:
java
// 如果String不是final,可能出现这种破坏不可变性的子类
class MutableString extends String {
private char[] value;
// 构造函数...
public void modify(int index, char c) {
value[index] = c; // 破坏不可变性
}
}
2. 安全机制支持
String 在 Java 安全架构中扮演重要角色,用于:
- 类加载路径
- 权限检查
- 安全管理器配置
如果 String 可变,攻击者可能通过修改字符串内容绕过安全机制。
3. 性能优化
JVM 可以对 final 类进行更多优化:
- 方法内联
- 运行时优化
- 缓存处理
4. 设计一致性
final 修饰符与不可变特性协同工作,确保 API 行为的一致性和可预测性。
Java 编译器对字符串拼接的优化
Java 编译器会自动优化字符串拼接操作:
java
// 源代码
String result = "Hello" + " " + "World";
// 编译器优化后等价于
String result = "Hello World";
对于变量拼接:
java
// 源代码
String s1 = "Hello";
String s2 = s1 + " World";
// 编译器优化后等价于
String s1 = "Hello";
String s2 = new StringBuilder().append(s1).append(" World").toString();
Java 17+的字符串拼接优化
Java 17 引入了更强大的字符串拼接优化,使用了连接点替换技术和增强的 StringConcatFactory:
java
// Java 17前
String result = prefix + s1 + s2 + s3 + suffix;
// 编译为: new StringBuilder().append(prefix).append(s1)...
// Java 17优化
// 使用增强的StringConcatFactory,通过invokedynamic实现
// 以下是伪代码表示底层机制:
StringConcatFactory.makeConcatWithConstants(
MethodHandles.lookup(),
"结果: \u0001 \u0001 \u0001", // 特殊格式的模板
methodType,
new Object[] { prefix, s2, suffix } // 直接传递参数
);
StringConcatFactory 的优化包括:
- 减少中间对象创建
- 避免 StringBuilder 的多次 append 调用
- 使用 invokedynamic 指令实现动态方法调用
- 根据字符串长度选择最优策略
垃圾收集对 String 的影响
String 的不可变性会影响垃圾收集:
java
private static final Logger logger = LoggerFactory.getLogger(StringGC.class);
// 大量临时字符串对象会增加GC压力
public void stringGcImpact() {
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
String data = "Data" + i; // 创建大量临时字符串对象
processString(data);
}
long end = System.currentTimeMillis();
logger.info("处理耗时: {}ms", end - start);
}
// 优化版本
public void optimizedStringUsage() {
long start = System.currentTimeMillis();
StringBuilder sb = new StringBuilder(20);
for (int i = 0; i < 100000; i++) {
sb.setLength(0);
sb.append("Data").append(i);
processString(sb.toString());
}
long end = System.currentTimeMillis();
logger.info("优化版处理耗时: {}ms", end - start);
}
多行文本的 Stream 处理
Java 8+的 Stream API 可以高效处理多行文本:
java
private static final Logger logger = LoggerFactory.getLogger(StringStream.class);
// 统计非空行数量
public long countNonEmptyLines(Path filePath) throws IOException {
try {
long count = Files.lines(filePath)
.filter(line -> !line.isBlank())
.count();
logger.info("文件 {} 包含 {} 行非空内容", filePath, count);
return count;
} catch (IOException e) {
logger.error("读取文件失败: {}", filePath, e);
throw e;
}
}
// 提取文本中的所有邮箱地址
public List<String> extractEmails(String text) {
if (text == null) return Collections.emptyList();
Pattern emailPattern = Pattern.compile("[\\w.-]+@[\\w.-]+\\.[a-z]{2,}");
return Arrays.stream(text.split("\\s+"))
.filter(word -> emailPattern.matcher(word).matches())
.collect(Collectors.toList());
}
// 统计文本中每个单词的出现频率
public Map<String, Long> wordFrequency(String text) {
if (text == null) return Collections.emptyMap();
return Arrays.stream(text.toLowerCase().split("\\W+"))
.filter(word -> !word.isEmpty())
.collect(Collectors.groupingBy(
Function.identity(),
Collectors.counting()
));
}
JMH 基准测试示例
使用 JMH 进行精确的性能测试:
java
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Thread)
@Fork(1)
@Warmup(iterations = 3)
@Measurement(iterations = 5)
public class StringConcatBenchmark {
@Benchmark
public String testStringConcat() {
String result = "";
for (int i = 0; i < 1000; i++) {
result += i + ",";
}
return result.substring(0, result.length() - 1);
}
@Benchmark
public String testStringBuilder() {
StringBuilder sb = new StringBuilder(1024);
for (int i = 0; i < 1000; i++) {
sb.append(i).append(",");
}
sb.setLength(sb.length() - 1);
return sb.toString();
}
@Benchmark
public String testStringFormat() {
String result = "";
for (int i = 0; i < 100; i++) {
result += String.format("%d,", i);
}
return result.substring(0, result.length() - 1);
}
public static void main(String[] args) throws Exception {
Options opt = new OptionsBuilder()
.include(StringConcatBenchmark.class.getSimpleName())
.build();
new Runner(opt).run();
}
}
不同 JDK 版本的性能对比
JMH 基准测试在不同 JDK 版本下的结果(平均执行时间,毫秒):
方法 | Java 8 | Java 11 | Java 17 |
---|---|---|---|
testStringConcat | 97.45 | 93.21 | 87.64 |
testStringBuilder | 0.37 | 0.33 | 0.31 |
testStringFormat | 23.12 | 22.89 | 20.45 |
可见 Java 版本升级对字符串操作性能有持续优化。
正则表达式性能优化
String 类提供正则表达式方法,但每次调用都会重新编译正则表达式:
java
private static final Logger logger = LoggerFactory.getLogger(RegexOptimization.class);
// 低效方式 - 每次都重新编译正则
public boolean validateEmailLowPerformance(String email) {
if (email == null) return false;
return email.matches("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$");
}
// 高效方式 - 预编译正则表达式
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$");
public boolean validateEmailHighPerformance(String email) {
if (email == null) return false;
return EMAIL_PATTERN.matcher(email).matches();
}
字符串处理缓存模式
对于重复的字符串转换操作,使用缓存可显著提高性能:
java
public class StringTransformCache<T> {
private static final Logger logger = LoggerFactory.getLogger(StringTransformCache.class);
private final Function<String, T> transformer;
private final Map<String, T> cache;
private final int maxSize;
public StringTransformCache(Function<String, T> transformer, int maxSize) {
this.transformer = transformer;
this.maxSize = maxSize;
// 使用LRU缓存防止无限增长
this.cache = Collections.synchronizedMap(
new LinkedHashMap<String, T>(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<String, T> eldest) {
return size() > maxSize;
}
});
}
public T transform(String input) {
if (input == null) return null;
return cache.computeIfAbsent(input, transformer);
}
public void clear() {
cache.clear();
}
// 示例用法
public static void main(String[] args) {
// 创建一个缓存常用的正则表达式验证结果
StringTransformCache<Boolean> emailValidator =
new StringTransformCache<>(
input -> Pattern.compile("^[\\w.-]+@[\\w.-]+\\.[a-z]{2,}$")
.matcher(input)
.matches(),
100
);
// 重复验证相同的邮箱,只计算一次
String email = "[email protected]";
for (int i = 0; i < 1000; i++) {
boolean valid = emailValidator.transform(email);
// 使用验证结果...
}
}
}
构建器模式用于复杂字符串构建
为复杂字符串构建场景提供更优雅的 API:
java
public class SqlBuilder {
private static final Logger logger = LoggerFactory.getLogger(SqlBuilder.class);
private final StringBuilder builder = new StringBuilder(200);
private boolean whereStarted = false;
private boolean orderByStarted = false;
private final List<String> parameters = new ArrayList<>();
public SqlBuilder select(String... columns) {
builder.append("SELECT ");
if (columns.length == 0) {
builder.append("*");
} else {
builder.append(String.join(", ", columns));
}
return this;
}
public SqlBuilder from(String table) {
builder.append(" FROM ").append(table);
return this;
}
public SqlBuilder where(String condition) {
if (!whereStarted) {
builder.append(" WHERE ");
whereStarted = true;
} else {
builder.append(" AND ");
}
builder.append(condition);
return this;
}
public SqlBuilder whereWithParam(String field, String operator) {
where(field + " " + operator + " ?");
return this;
}
public SqlBuilder orderBy(String... columns) {
if (!orderByStarted) {
builder.append(" ORDER BY ");
orderByStarted = true;
} else {
builder.append(", ");
}
builder.append(String.join(", ", columns));
return this;
}
public SqlBuilder addParameter(Object param) {
parameters.add(String.valueOf(param));
return this;
}
public String getSql() {
return builder.toString();
}
public List<String> getParameters() {
return new ArrayList<>(parameters);
}
@Override
public String toString() {
return builder.toString();
}
// 示例用法
public static void main(String[] args) {
SqlBuilder builder = new SqlBuilder()
.select("id", "name", "email")
.from("users")
.whereWithParam("status", "=")
.addParameter("active")
.whereWithParam("age", ">")
.addParameter(18)
.orderBy("name");
logger.info("生成SQL: {}", builder.getSql());
logger.info("参数: {}", builder.getParameters());
}
}
字符集检测与处理
增强国际化处理,添加字符集检测逻辑:
java
public class CharsetUtils {
private static final Logger logger = LoggerFactory.getLogger(CharsetUtils.class);
private static final Charset UTF8 = StandardCharsets.UTF_8;
private static final Charset ISO_8859_1 = StandardCharsets.ISO_8859_1;
/**
* 尝试检测字节数组的字符集
*/
public static Charset detectCharset(byte[] data) {
if (data == null || data.length == 0) {
return UTF8;
}
// 检查BOM标记
if (data.length >= 3) {
if (data[0] == (byte)0xEF && data[1] == (byte)0xBB && data[2] == (byte)0xBF) {
return UTF8;
}
}
// 尝试各种编码,看哪个不会产生解码错误
for (Charset charset : Arrays.asList(UTF8, ISO_8859_1)) {
try {
CharsetDecoder decoder = charset.newDecoder()
.onMalformedInput(CodingErrorAction.REPORT)
.onUnmappableCharacter(CodingErrorAction.REPORT);
decoder.decode(ByteBuffer.wrap(data));
return charset;
} catch (CharacterCodingException e) {
// 这个编码不适用,尝试下一个
}
}
// 默认返回UTF-8
logger.warn("无法确定字符集,默认使用UTF-8");
return UTF8;
}
/**
* 安全地转换字符串编码
*/
public static String convertEncoding(String text,
Charset sourceCharset,
Charset targetCharset) {
if (text == null) return null;
try {
byte[] bytes = text.getBytes(sourceCharset);
return new String(bytes, targetCharset);
} catch (Exception e) {
logger.error("编码转换失败: {} -> {}", sourceCharset, targetCharset, e);
return text;
}
}
/**
* 自动检测并修复编码问题
*/
public static String fixEncoding(String text) {
if (text == null || text.isEmpty()) {
return text;
}
// 检查是否有乱码特征
if (text.contains("�") || hasEncodingIssues(text)) {
// 尝试修复常见的UTF-8被错误解读为ISO-8859-1的问题
return convertEncoding(text, ISO_8859_1, UTF8);
}
return text;
}
private static boolean hasEncodingIssues(String text) {
// 检测典型的编码问题特征
return text.matches(".*[\\ufffd\\ufeff].*");
}
}
完整的 StringUtils 工具类
将常用字符串处理方法整合到一个工具类:
java
public final class StringUtils {
private static final Logger logger = LoggerFactory.getLogger(StringUtils.class);
private StringUtils() {
// 私有构造函数防止实例化
}
/**
* 安全地连接多个字符串,忽略null值
*/
public static String safeConcat(String... strings) {
if (strings == null || strings.length == 0) {
return "";
}
StringBuilder sb = new StringBuilder();
for (String s : strings) {
if (s != null) {
sb.append(s);
}
}
return sb.toString();
}
/**
* 安全地获取字符串的字符,避免索引越界
*/
public static char getCharSafely(String text, int index, char defaultChar) {
if (text == null || index < 0 || index >= text.length()) {
if (logger.isDebugEnabled()) {
logger.debug("尝试访问无效索引: {}, 文本长度: {}",
index, text == null ? "null" : text.length());
}
return defaultChar;
}
return text.charAt(index);
}
/**
* 安全地截取子字符串,避免索引越界
*/
public static String safeSubstring(String text, int start, int end) {
if (text == null) {
return "";
}
int length = text.length();
if (start < 0) start = 0;
if (end > length) end = length;
if (start >= end) return "";
return text.substring(start, end);
}
/**
* 安全地使用intern(),只对短字符串使用
*/
public static String internSafely(String text, int maxLength) {
if (text == null || text.length() > maxLength) {
return text;
}
return text.intern();
}
/**
* 检测字符串是否为空或仅包含空白字符
*/
public static boolean isBlank(String text) {
if (text == null || text.isEmpty()) {
return true;
}
for (int i = 0; i < text.length(); i++) {
if (!Character.isWhitespace(text.charAt(i))) {
return false;
}
}
return true;
}
/**
* 从文本中删除所有HTML标签
*/
public static String stripHtmlTags(String html) {
if (html == null) {
return "";
}
return html.replaceAll("<[^>]*>", "");
}
/**
* 截断文本到指定长度,添加省略号
*/
public static String truncate(String text, int maxLength) {
if (text == null || text.length() <= maxLength) {
return text;
}
return text.substring(0, maxLength) + "...";
}
}
StringUtils 单元测试示例
java
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class StringUtilsTest {
@Test
public void testSafeConcat() {
assertEquals("", StringUtils.safeConcat());
assertEquals("", StringUtils.safeConcat((String[])null));
assertEquals("abc", StringUtils.safeConcat("a", "b", "c"));
assertEquals("ac", StringUtils.safeConcat("a", null, "c"));
}
@Test
public void testGetCharSafely() {
assertEquals('b', StringUtils.getCharSafely("abc", 1, 'x'));
assertEquals('x', StringUtils.getCharSafely("abc", 5, 'x'));
assertEquals('x', StringUtils.getCharSafely(null, 0, 'x'));
assertEquals('x', StringUtils.getCharSafely("abc", -1, 'x'));
}
@Test
public void testSafeSubstring() {
assertEquals("", StringUtils.safeSubstring(null, 0, 5));
assertEquals("abc", StringUtils.safeSubstring("abcdef", 0, 3));
assertEquals("", StringUtils.safeSubstring("abc", 5, 10));
assertEquals("", StringUtils.safeSubstring("abc", 3, 2));
assertEquals("c", StringUtils.safeSubstring("abc", 2, 10));
}
@Test
public void testIsBlank() {
assertTrue(StringUtils.isBlank(null));
assertTrue(StringUtils.isBlank(""));
assertTrue(StringUtils.isBlank(" "));
assertTrue(StringUtils.isBlank("\t \n"));
assertFalse(StringUtils.isBlank("a"));
assertFalse(StringUtils.isBlank(" a "));
}
@Test
public void testTruncate() {
assertNull(StringUtils.truncate(null, 5));
assertEquals("Hello", StringUtils.truncate("Hello", 5));
assertEquals("Hello...", StringUtils.truncate("Hello World", 5));
}
}
不可变对象设计模式
String 类是不可变对象设计模式的典型实现:
java
// 自定义不可变类示例
public final class ImmutablePerson {
private final String firstName;
private final String lastName;
private final int age;
private final Map<String, String> attributes;
public ImmutablePerson(String firstName, String lastName, int age,
Map<String, String> attributes) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
// 防御性复制,确保真正不可变
this.attributes = attributes != null ?
Collections.unmodifiableMap(new HashMap<>(attributes)) :
Collections.emptyMap();
}
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
public int getAge() { return age; }
public Map<String, String> getAttributes() { return attributes; }
// 修改操作返回新对象
public ImmutablePerson withAge(int newAge) {
return new ImmutablePerson(firstName, lastName, newAge, attributes);
}
public ImmutablePerson withAttribute(String key, String value) {
Map<String, String> newAttributes = new HashMap<>(attributes);
newAttributes.put(key, value);
return new ImmutablePerson(firstName, lastName, age, newAttributes);
}
@Override
public String toString() {
return firstName + " " + lastName + ", " + age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ImmutablePerson that = (ImmutablePerson) o;
if (age != that.age) return false;
if (!Objects.equals(firstName, that.firstName)) return false;
if (!Objects.equals(lastName, that.lastName)) return false;
return Objects.equals(attributes, that.attributes);
}
@Override
public int hashCode() {
int result = firstName != null ? firstName.hashCode() : 0;
result = 31 * result + (lastName != null ? lastName.hashCode() : 0);
result = 31 * result + age;
result = 31 * result + (attributes != null ? attributes.hashCode() : 0);
return result;
}
}
字符串处理类对比

总结
特性 | 优势 | 应用场景 | 注意事项 |
---|---|---|---|
不可变性 | 安全可靠 | 敏感数据处理 | 修改操作创建新对象 |
不可变性 | 线程安全 | 多线程共享 | 无需额外同步 |
不可变性 | 哈希缓存 | 集合键值 | 提高 HashMap 性能 |
不可变性 | 字符串池 | 内存优化 | 字面量共享内存 |
final 类 | 防止子类破坏 | 安全框架 | 确保整体一致性 |
final 类 | JVM 优化 | 性能关键应用 | 允许更多编译优化 |
Latin-1 编码 | 内存节省 | ASCII 文本处理 | Java 9+新特性 |
文本块 | 可读性提升 | 多行文本 | Java 15+特性 |