1. String类的基本概念与特性
String是Java中最基础也最常用的类之一,它代表不可变的字符序列。在Java中,String不是基本数据类型,而是一个真正的类,位于java.lang包中,因此无需显式导入即可使用。
1.1 String的不可变性
String对象一旦创建,其内容就不可更改。这个特性看似简单,却影响着String的方方面面:
java复制String s = "hello";
s = s + " world"; // 实际上创建了新的String对象
这段代码中,第二行并不是修改了原有的"hello"字符串,而是创建了一个全新的String对象。这种不可变性带来了几个重要影响:
- 线程安全:由于不可变,String对象可以被多个线程安全共享
- 缓存哈希值:String的hashCode()方法会缓存计算结果,提高作为HashMap键的性能
- 安全性:常用于网络连接、文件路径等场景,避免被意外修改
1.2 String的创建方式
Java中创建String对象主要有两种方式:
- 字面量方式:
java复制String s1 = "hello"; // 存储在字符串常量池
- new关键字方式:
java复制String s2 = new String("hello"); // 强制在堆中创建新对象
这两种方式在内存分配上有本质区别。字面量方式会先检查字符串常量池,如果存在则直接引用,不存在则创建并放入池中;而new方式则直接在堆中创建新对象,无论常量池中是否存在。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. String类的内存机制
2.1 字符串常量池
JVM为了优化字符串存储,设计了字符串常量池(String Pool),它是方法区的一部分。当使用字面量创建字符串时,JVM会先检查常量池:
java复制String a = "hello";
String b = "hello";
System.out.println(a == b); // true,指向同一对象
这种机制可以节省大量内存空间,特别是对于重复出现的字符串。
2.2 intern()方法
String的intern()方法可以将字符串对象主动放入常量池:
java复制String s1 = new String("hello");
String s2 = s1.intern();
System.out.println(s1 == s2); // false
System.out.println(s2 == "hello"); // true
这个方法在需要大量重复字符串的场景下非常有用,可以显著减少内存占用。
3. String类的常用方法
3.1 字符串操作
String类提供了丰富的方法来操作字符串:
- 长度和判空:
java复制String s = "hello";
int len = s.length(); // 5
boolean empty = s.isEmpty(); // false
- 获取字符:
java复制char c = s.charAt(1); // 'e'
- 比较:
java复制boolean eq = s.equals("hello"); // true
int cmp = s.compareTo("world"); // 负数,表示字典序在前
- 查找:
java复制int index = s.indexOf('l'); // 2
int lastIndex = s.lastIndexOf('l'); // 3
3.2 字符串转换
- 大小写转换:
java复制String upper = s.toUpperCase(); // "HELLO"
String lower = "WORLD".toLowerCase(); // "world"
- 去除空白:
java复制String trimmed = " hello ".trim(); // "hello"
- 替换:
java复制String replaced = s.replace('l', 'L'); // "heLLo"
- 分割:
java复制String[] parts = "a,b,c".split(","); // ["a", "b", "c"]
3.3 格式化与连接
- 格式化:
java复制String formatted = String.format("Hi, %s! You have %d messages.", "Alice", 5);
- 连接:
java复制String joined = String.join("-", "2023", "05", "20"); // "2023-05-20"
4. String与性能优化
4.1 字符串拼接的陷阱
由于String的不可变性,频繁拼接字符串会导致性能问题:
java复制// 低效写法
String result = "";
for (int i = 0; i < 1000; i++) {
result += i; // 每次循环都创建新String对象
}
应该改用StringBuilder:
java复制// 高效写法
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append(i);
}
String result = sb.toString();
4.2 StringBuffer与StringBuilder
两者都提供了可变的字符串操作能力,主要区别在于:
- StringBuffer:线程安全,方法都是synchronized的
- StringBuilder:非线程安全,但性能更好
在单线程环境下,优先使用StringBuilder;多线程环境下,考虑使用StringBuffer。
5. String在Java面试中的常见问题
5.1 经典面试题解析
- ==与equals的区别:
java复制String s1 = "hello";
String s2 = new String("hello");
System.out.println(s1 == s2); // false
System.out.println(s1.equals(s2)); // true
- String为什么设计为不可变:
- 安全性考虑
- 线程安全
- 缓存哈希值
- 字符串常量池优化
- String、StringBuffer、StringBuilder的区别:
- String:不可变,线程安全
- StringBuffer:可变,线程安全
- StringBuilder:可变,非线程安全,性能最好
5.2 实际应用中的注意事项
- 大量字符串拼接时,避免使用+操作符
- 注意字符串编码问题,特别是涉及IO操作时
- 合理使用intern()方法,但不要滥用
- 注意字符串比较时的大小写敏感问题
6. String在Java开发中的实际应用
6.1 文件路径处理
java复制String path = "data/files/config.txt";
String dir = path.substring(0, path.lastIndexOf('/')); // "data/files"
String filename = path.substring(path.lastIndexOf('/') + 1); // "config.txt"
6.2 网络请求参数处理
java复制String query = "name=John&age=30&city=NY";
String[] params = query.split("&");
Map<String, String> paramMap = new HashMap<>();
for (String param : params) {
String[] kv = param.split("=");
paramMap.put(kv[0], kv.length > 1 ? kv[1] : "");
}
6.3 数据验证与清洗
java复制public static String cleanInput(String input) {
if (input == null) return "";
return input.trim()
.replaceAll("<script>.*?</script>", "") // 移除脚本
.replaceAll("\\s+", " "); // 合并多个空格
}
7. String相关的常见异常与处理
7.1 NullPointerException
java复制String s = null;
int length = s.length(); // 抛出NullPointerException
防御性编程:
java复制int length = (s == null) ? 0 : s.length();
7.2 StringIndexOutOfBoundsException
java复制String s = "hello";
char c = s.charAt(10); // 抛出StringIndexOutOfBoundsException
安全访问:
java复制char c = (index >= 0 && index < s.length()) ? s.charAt(index) : '\0';
7.3 编码相关异常
java复制byte[] bytes = "你好".getBytes("UTF-8");
String s = new String(bytes, "UTF-8"); // 明确指定编码
8. Java 8及以后版本对String的增强
8.1 String.join()方法
java复制List<String> list = Arrays.asList("a", "b", "c");
String joined = String.join(", ", list); // "a, b, c"
8.2 String的chars()方法
java复制"hello".chars().forEach(c -> System.out.print((char)c)); // 输出hello
8.3 文本块(Text Blocks)
Java 15引入的文本块特性:
java复制String html = """
<html>
<body>
<p>Hello, world!</p>
</body>
</html>
""";
9. 字符串性能优化实践
9.1 预编译正则表达式
java复制// 低效
boolean matches = "123".matches("\\d+");
// 高效
private static final Pattern DIGITS = Pattern.compile("\\d+");
boolean matches = DIGITS.matcher("123").matches();
9.2 避免不必要的字符串创建
java复制// 低效
for (int i = 0; i < 1000; i++) {
String s = new String("constant"); // 每次都创建新对象
}
// 高效
String constant = "constant";
for (int i = 0; i < 1000; i++) {
String s = constant; // 重用同一对象
}
9.3 合理使用字符串缓存
对于频繁使用的字符串,可以考虑使用缓存:
java复制private static final Map<String, String> CACHE = new ConcurrentHashMap<>();
public static String getCachedString(String key) {
return CACHE.computeIfAbsent(key, k -> expensiveStringCreation(k));
}
10. 字符串与其他数据类型的转换
10.1 基本数据类型转换
java复制// String转int
int num = Integer.parseInt("123");
// int转String
String s = Integer.toString(123);
// String转boolean
boolean flag = Boolean.parseBoolean("true");
10.2 日期时间转换
java复制// String转Date
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf.parse("2023-05-20");
// Date转String
String dateStr = sdf.format(new Date());
10.3 集合转换
java复制// List转String
List<String> list = Arrays.asList("a", "b", "c");
String joined = String.join(",", list);
// String转List
List<String> newList = Arrays.asList(joined.split(","));
11. 字符串编码与国际化
11.1 字符编码处理
java复制// 指定编码转换
String str = "你好";
byte[] utf8Bytes = str.getBytes(StandardCharsets.UTF_8);
String newStr = new String(utf8Bytes, StandardCharsets.UTF_8);
11.2 国际化字符串处理
使用ResourceBundle处理多语言:
java复制ResourceBundle bundle = ResourceBundle.getBundle("Messages", Locale.US);
String greeting = bundle.getString("greeting"); // "Hello"
12. 字符串安全注意事项
12.1 SQL注入防护
java复制// 不安全
String query = "SELECT * FROM users WHERE name = '" + name + "'";
// 安全
String query = "SELECT * FROM users WHERE name = ?";
PreparedStatement stmt = connection.prepareStatement(query);
stmt.setString(1, name);
12.2 XSS防护
java复制public static String escapeHtml(String input) {
if (input == null) return "";
return input.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace("\"", """)
.replace("'", "'");
}
13. 字符串工具类推荐
13.1 Apache Commons Lang
java复制// 判空
StringUtils.isEmpty(null); // true
StringUtils.isBlank(" "); // true
// 缩写
StringUtils.abbreviate("Hello world", 8); // "Hello..."
13.2 Guava
java复制// 连接字符串
String joined = Joiner.on(", ").skipNulls().join("a", null, "b"); // "a, b"
// 分割字符串
Iterable<String> split = Splitter.on(',')
.trimResults()
.omitEmptyStrings()
.split("a,,b, c"); // ["a", "b", "c"]
14. 字符串性能测试与对比
14.1 拼接性能测试
java复制// 测试代码框架
long start = System.nanoTime();
// 测试的字符串操作
long duration = System.nanoTime() - start;
测试结果通常显示:
- StringBuilder最快
- StringBuffer稍慢(由于同步开销)
- +操作符最慢(特别是循环中)
14.2 内存占用测试
使用JVM工具或手动测试不同字符串创建方式的内存占用:
- 字面量方式占用最少(共享常量池)
- new String()方式占用较多
- 大量小字符串可以考虑使用flyweight模式优化
15. 字符串在Java新版本中的演进
15.1 Java 11新方法
java复制// 判断空白
" ".isBlank(); // true
// 去除首尾空白
" hello ".strip(); // "hello"
// 重复字符串
"abc".repeat(3); // "abcabcabc"
// 行流
"a\nb\nc".lines().count(); // 3
15.2 Java 12新方法
java复制// 缩进处理
String text = "hello\nworld";
String indented = text.indent(4); // 每行缩进4个空格
// 转换
String s = "foo";
String transformed = s.transform(str -> str + "bar"); // "foobar"
16. 字符串与Java集合框架
16.1 字符串在集合中的使用
java复制// 作为Map的键
Map<String, Integer> wordCount = new HashMap<>();
wordCount.put("hello", 1);
// 注意:字符串比较影响集合性能
Map<String, String> caseInsensitiveMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
16.2 字符串集合操作
java复制List<String> list = Arrays.asList("a", "b", "c");
// 过滤
List<String> filtered = list.stream()
.filter(s -> s.startsWith("a"))
.collect(Collectors.toList());
// 映射
List<String> upper = list.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
17. 字符串与IO操作
17.1 文件读写
java复制// 读取文件为字符串
String content = Files.readString(Path.of("file.txt"));
// 写入字符串到文件
Files.writeString(Path.of("output.txt"), "Hello world");
17.2 网络通信
java复制// 读取网络数据
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(socket.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
// 处理每行数据
}
}
18. 字符串与正则表达式
18.1 基本匹配
java复制String regex = "\\d+"; // 匹配一个或多个数字
boolean matches = "123".matches(regex); // true
18.2 分组提取
java复制Pattern pattern = Pattern.compile("(\\d{3})-(\\d{4})");
Matcher matcher = pattern.matcher("123-4567");
if (matcher.matches()) {
String area = matcher.group(1); // "123"
String number = matcher.group(2); // "4567"
}
19. 字符串与Java反射
19.1 通过类名字符串创建对象
java复制String className = "java.util.ArrayList";
Class<?> clazz = Class.forName(className);
Object instance = clazz.getDeclaredConstructor().newInstance();
19.2 方法名字符串调用方法
java复制Method method = obj.getClass().getMethod("methodName", String.class);
Object result = method.invoke(obj, "argument");
20. 字符串最佳实践总结
- 优先使用字面量方式创建字符串
- 避免使用+操作符在循环中拼接字符串
- 注意字符串编码问题,特别是涉及IO时
- 合理使用StringBuilder/StringBuffer
- 注意字符串比较时使用equals()而非==
- 对于大量重复字符串,考虑使用intern()
- 处理用户输入时注意安全防护
- 关注Java新版本中的字符串新特性
- 性能敏感场景下进行专门的优化
- 编写清晰可读的字符串操作代码
