1. Java字符串基础概念
在Java编程语言中,字符串是最常用的数据类型之一,也是面试中经常被问到的核心知识点。字符串本质上是一个字符序列,在Java中通过String类来表示。与基本数据类型不同,String是一个不可变(immutable)的类,这意味着一旦创建了String对象,它的值就不能被改变。
Java中的字符串有几种创建方式:
- 直接使用双引号创建:
String str = "Hello World"; - 通过new关键字创建:
String str = new String("Hello World"); - 通过字符数组创建:
char[] arr = {'H','e','l','l','o'}; String str = new String(arr);
注意:在Java中,字符串常量存储在字符串常量池中,使用双引号创建字符串时,JVM会先在常量池中查找是否已存在相同内容的字符串,如果存在则直接返回引用,否则创建新对象。而使用new关键字创建字符串时,无论常量池中是否存在,都会在堆内存中创建新对象。
2. Java字符串的内存模型
理解Java字符串的内存模型对于编写高效代码至关重要。Java字符串主要涉及三种内存区域:
- 字符串常量池(String Pool):位于方法区(Method Area)中,存储所有字面量字符串
- 堆内存(Heap):存储通过new关键字创建的字符串对象
- 栈内存(Stack):存储字符串变量的引用
java复制String s1 = "Java"; // 存储在字符串常量池
String s2 = "Java"; // 复用常量池中的"Java"
String s3 = new String("Java"); // 在堆中创建新对象
System.out.println(s1 == s2); // true,引用相同
System.out.println(s1 == s3); // false,引用不同
System.out.println(s1.equals(s3)); // true,内容相同
2.1 字符串的不可变性
字符串的不可变性带来了几个重要特性:
- 线程安全:不可变对象天生是线程安全的
- 缓存哈希值:String类缓存了hashCode()的计算结果
- 安全性:适合用作Map的key和敏感信息存储
但同时也带来了一些性能问题,比如频繁的字符串拼接操作会产生大量中间对象。这时应该使用StringBuilder或StringBuffer。
3. Java字符串常用操作
3.1 字符串比较
Java提供了多种字符串比较方式:
==:比较引用是否指向同一对象equals():比较内容是否相同equalsIgnoreCase():忽略大小写比较内容compareTo():按字典顺序比较
java复制String a = "Java";
String b = "java";
System.out.println(a.equals(b)); // false
System.out.println(a.equalsIgnoreCase(b)); // true
System.out.println(a.compareTo(b)); // 负数,因为'J'比'j'小
3.2 字符串查找与截取
常用查找和截取方法:
length():获取字符串长度charAt(int index):获取指定位置字符indexOf(String str):查找子串位置substring(int begin, int end):截取子串
java复制String str = "Hello World";
System.out.println(str.length()); // 11
System.out.println(str.charAt(1)); // 'e'
System.out.println(str.indexOf("World")); // 6
System.out.println(str.substring(6)); // "World"
3.3 字符串转换
常用转换方法:
toLowerCase()/toUpperCase():大小写转换trim():去除首尾空白split(String regex):按正则分割字符串valueOf():将其他类型转为字符串
java复制String str = " Java ";
System.out.println(str.trim().toUpperCase()); // "JAVA"
String[] parts = "a,b,c".split(","); // ["a","b","c"]
String numStr = String.valueOf(123); // "123"
4. Java字符串性能优化
4.1 字符串拼接
在循环中进行字符串拼接时,应避免使用+操作符:
java复制// 不推荐 - 每次循环都会创建新String对象
String result = "";
for (int i = 0; i < 100; i++) {
result += i;
}
// 推荐 - 使用StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
sb.append(i);
}
String result = sb.toString();
4.2 字符串常量池优化
可以通过intern()方法将字符串显式放入常量池:
java复制String s1 = new String("Java").intern();
String s2 = "Java";
System.out.println(s1 == s2); // true
但要注意,过度使用intern()可能导致方法区内存溢出。
4.3 字符串编码处理
处理不同编码时,应明确指定字符集:
java复制String str = "你好";
byte[] utf8Bytes = str.getBytes(StandardCharsets.UTF_8);
String newStr = new String(utf8Bytes, StandardCharsets.UTF_8);
5. Java字符串面试常见问题
5.1 String、StringBuilder和StringBuffer的区别
| 特性 | String | StringBuilder | StringBuffer |
|---|---|---|---|
| 可变性 | 不可变 | 可变 | 可变 |
| 线程安全 | 是(天生) | 否 | 是 |
| 性能 | 低(拼接) | 高 | 中等 |
| 使用场景 | 常量字符串 | 单线程字符串操作 | 多线程字符串操作 |
5.2 字符串常量池的工作机制
字符串常量池是JVM为了提高性能而设计的一种特殊存储区域。当使用字面量创建字符串时:
- JVM首先检查字符串常量池中是否存在相同内容的字符串
- 如果存在,则返回池中对象的引用
- 如果不存在,则在池中创建新字符串并返回引用
5.3 如何判断两个字符串相等
正确的字符串相等判断应该使用equals()方法而非==,除非你明确想比较引用是否相同。对于可能为null的字符串,推荐使用:
java复制String str1 = ...;
String str2 = ...;
if (str1 == null ? str2 == null : str1.equals(str2)) {
// 相等处理
}
或者使用Java 7引入的Objects.equals():
java复制if (Objects.equals(str1, str2)) {
// 相等处理
}
6. Java字符串高级特性
6.1 字符串格式化
Java提供了多种字符串格式化方式:
String.format()System.out.printf()MessageFormat
java复制String name = "Alice";
int age = 25;
String formatted = String.format("Name: %s, Age: %d", name, age);
// 输出:Name: Alice, Age: 25
6.2 正则表达式
String类提供了正则表达式支持:
matches(String regex):判断是否匹配replaceAll(String regex, String replacement):替换所有匹配项split(String regex):按正则分割
java复制String phone = "123-456-7890";
boolean isValid = phone.matches("\\d{3}-\\d{3}-\\d{4}");
String noDash = phone.replaceAll("-", "");
String[] parts = phone.split("-");
6.3 Java 8新增的字符串方法
Java 8引入了几个有用的字符串方法:
join(CharSequence delimiter, CharSequence... elements):拼接字符串chars():获取IntStream流codePoints():获取Unicode码点流
java复制String joined = String.join(", ", "Java", "Python", "C++");
// 输出:Java, Python, C++
"Java".chars().forEach(System.out::println);
// 输出:74 97 118 97
7. Java字符串最佳实践
-
优先使用字面量创建字符串:除非有特殊需求,否则应使用双引号语法创建字符串,以利用常量池优势。
-
处理用户输入时总是trim():用户输入经常包含多余空格,处理前应先调用trim()。
-
注意字符串编码:在字节和字符串转换时,总是明确指定字符集,避免依赖平台默认编码。
-
大文本处理使用流式API:处理大文本时,考虑使用BufferedReader按行读取,而不是一次性加载整个字符串。
-
敏感信息处理:处理密码等敏感信息后,应立即清除字符数组,而不是依赖垃圾回收。
java复制char[] password = getPassword();
// 处理密码...
Arrays.fill(password, '0'); // 使用后立即清除
- 国际化考虑:处理多语言文本时,注意使用正确的Locale进行大小写转换和比较。
java复制String str = "i";
System.out.println(str.toUpperCase(Locale.US)); // "I"
System.out.println(str.toUpperCase(Locale.TURKISH)); // "İ"
- 避免字符串常量滥用:虽然字符串常量池有优势,但过多不同的字符串常量会占用方法区内存,可能导致OutOfMemoryError。
