没有理想的人不伤心

Java - 字符串常见处理

2025/08/12
4
0

image.png

在字符串类题目中,操作的核心是围绕 String StringBuilderStringBuffer 三个核心类展开的。由于字符串操作在算法题中高频出现(如处理子串、拼接、反转、匹配等),掌握它们的特性和常用方法是关键。以下从「常用类特性」「核心方法」「遍历方式」「构造与修改」「常见场景」五个方面详细说明:

一、先明确:三个核心类的特性(决定操作方式)

可变性线程安全适用场景效率(修改操作)
String不可变(Immutable)安全(无修改方法)字符串常量、无需修改的场景低(修改即创建新对象)
StringBuilder可变(Mutable)不安全单线程下频繁修改(拼接/删除)高(原地修改)
StringBuffer可变(Mutable)安全(方法加锁)多线程下频繁修改中(锁开销)

核心结论

  • 若字符串无需修改(如判断相等、截取子串),用 String
  • 若需要频繁修改(如拼接、反转、插入),优先用 StringBuilder(单线程,算法题几乎都是单线程场景)。

二、常用核心方法(按功能分类)

1. 基础属性与判断

方法功能描述示例( s = "abcabc"
int length()返回字符串长度(字符个数) s.length() → 6
boolean isEmpty()判断字符串是否为空(长度为 0)""true"a"false
boolean equals(Object obj)比较两个字符串的内容是否完全相同(区分大小写)"abc".equals("ABC")false
boolean equalsIgnoreCase(String s)比较内容是否相同(忽略大小写)"abc".equalsIgnoreCase("ABC")true
int compareTo(String s)按字典序比较(返回差值,0 表示相等,正数表示当前字符串大)"a".compareTo("b") → -1,"c".compareTo("a") → 2

2. 字符与子串操作

方法功能描述示例( s = "abcabc"
char charAt(int index)获取指定索引(0-based)的字符(索引越界会抛异常) s.charAt(2)'c'
String substring(int begin)截取子串:从 begin 索引(含)到末尾 s.substring(2)"cabc"
String substring(int begin,int end)截取子串:从 begin(含)到 end(不含),即 [begin,end) s.substring(1,4)"bca"(索引 1-3 的字符)
int indexOf(String str)返回子串 str 第一次出现的索引,未找到返回-1 s.indexOf("ab") → 0
int indexOf(String str,int from)from 索引开始,找子串 str 第一次出现的索引 s.indexOf("ab",1) → 3
int lastIndexOf(String str)返回子串 str 最后一次出现的索引,未找到返回-1 s.lastIndexOf("ab") → 3

3. 字符串修改(String 不可变,实际返回新对象;StringBuilder 可变,原地修改)

方法(String方法(StringBuilder功能描述示例
String concat(String str) append(String str)拼接字符串(String 返回新对象,StringBuilder 原地修改)"a".concat("b")"ab" sb.append("b")
String replace(char old,char new) replace(int start,int end,String str)替换字符/子串(String 替换所有匹配字符;StringBuilder 替换指定范围)"aba".replace('a','x')"xbx"
String toLowerCase() toLowerCase()转为小写"ABC".toLowerCase()"abc"
String toUpperCase() toUpperCase()转为大写"abc".toUpperCase()"ABC"
String trim()-去除首尾空白字符(空格、换行等,中间不变)"a b".trim()"a b"
- insert(int offset,String str)offset 索引处插入字符串 sb.insert(1, "xy")(原"abc"→"axybc")
- delete(int start,int end)删除 [start,end) 范围内的字符 sb.delete(1,3)(原"abcde"→"ade")
- reverse()反转字符串 sb.reverse()(原"abc"→"cba")

4. 转换与拆分

方法功能描述示例
char[] toCharArray()将字符串转为字符数组(便于遍历单个字符)"abc".toCharArray(){'a','b','c'}
static String valueOf(类型 obj)将其他类型(如 int、char[])转为字符串(静态方法) String.valueOf(123)"123"
String[] split(String regex)按正则表达式拆分字符串为数组(注意:. `` 等需转义)"a,b,c".split(",")["a","b","c"]
byte[] getBytes()将字符串按默认编码转为字节数组(IO 操作常用)"abc".getBytes() → 字节数组 [97,98,99]

三、字符串的遍历方式(高频操作)

遍历字符串的核心是访问每个字符,常用以下 3 种方式:

1. 基于 charAt(index) 遍历(适合 String

String s = "abc";
for(int i = 0;i < s.length();i++) {
    char c = s.charAt(i); // 通过索引获取字符
    System.out.print(c + " "); // 输出:a b c
}

2. 转为字符数组遍历( toCharArray(),适合频繁访问字符)

String s = "abc";
char[] chars = s.toCharArray(); // 转为字符数组
for(int i = 0;i < chars.length;i++) {
    System.out.print(chars[i] + " "); // a b c
}
// 增强 for 循环
for(char c: chars) {
    System.out.print(c + " "); // a b c
}

3. 基于 StringBuilder 的遍历(同字符数组,因内部是字符数组实现)

StringBuilder sb = new StringBuilder("abc");
for(int i = 0;i < sb.length();i++) {
    char c = sb.charAt(i); // 同 String 的 charAt
    System.out.print(c + " "); // a b c
}

四、字符串的构造与初始化

根据场景选择合适的构造方式,影响效率和内存:

1. String 的构造(不可变)

  • 直接赋值(推荐):利用字符串常量池,避免重复创建相同对象。
    String s = "abc";(若常量池已有"abc",直接复用引用)

  • 构造方法:创建新对象(即使内容相同),不推荐除非必要。
    String s = new String("abc");(一定创建新对象,存在于堆中)
    String s = new String(new char[]{'a','b','c'});(从字符数组构造)

2. StringBuilder 的构造(可变,适合拼接)

  • 初始化空对象,再拼接:

    StringBuilder sb = new StringBuilder();
    sb.append("a").append("b").append("c"); // 链式调用,高效
    String result = sb.toString(); // 转为 String:"abc"
    
  • 初始指定容量(避免动态扩容,更高效):
    StringBuilder sb = new StringBuilder(100);(初始容量 100,适合已知大致长度的场景)

五、常见场景与操作技巧

1. 频繁拼接字符串(必须用 StringBuilder

反例(低效):

String s = "";
for(int i = 0;i < 1000;i++) {
    s += i; // 每次都会创建新 String 对象,总耗时 O(n²)
}

正例(高效):

StringBuilder sb = new StringBuilder();
for(int i = 0;i < 1000;i++) {
    sb.append(i); // 原地修改,总耗时 O(n)
}
String s = sb.toString();

2. 判断回文串(对称字符串)

思路:双指针从两端向中间遍历,比较字符是否相等。

public boolean isPalindrome(String s) {
    int left = 0,right = s.length() - 1;
    while(left < right) {
        if(s.charAt(left) != s.charAt(right)) {
            return false;
        }
        left++;
        right--;
    }
    return true;
}

3. 字符串反转

  • StringBuilder reverse() 方法(最简单):
    new StringBuilder(s).reverse().toString();

  • 手动反转(双指针交换字符数组):

    char[] chars = s.toCharArray();
    int left = 0,right = chars.length - 1;
    while(left < right) {
        char temp = chars[left];
        chars[left] = chars[right];
        chars[right] = temp;
        left++;
        right--;
    }
    return new String(chars);
    

4. 统计字符出现次数

思路:用数组(针对 ASCII 字符)或哈希表(针对 Unicode 字符)计数。

public int countChar(String s,char target) {
    int count = 0;
    for(char c: s.toCharArray()) {
        if(c == target) {
            count++;
        }
    }
    return count;
}

5. 处理子串匹配(如判断 s 是否包含 t

  • indexOf()s.indexOf(t) != -1 表示包含。
  • 复杂场景(如 KMP 算法):针对长字符串的高效匹配,避免暴力遍历的 O(n*m)复杂度。

六、注意事项(避坑指南)

  1. String 的 == 与 equals 区别

    • == 比较引用地址(是否为同一对象);
    • equals 比较内容(String 重写了 equals 方法)。
      例:"abc" == new String("abc")false(不同对象);"abc".equals(new String("abc"))true(内容相同)。
  2. substring 的索引范围:是 [begin,end),即包含 begin,不包含 end,超出范围会抛 StringIndexOutOfBoundsException

  3. split 方法的坑

    • . 拆分需转义:"a.b.c".split("\\.")(因 . 在正则中表示任意字符);
    • 连续分隔符会产生空字符串:"a,,b".split(",")["a", "", "b"]
  4. StringBuilder 不是线程安全的:多线程场景用 StringBuffer,但算法题几乎不用考虑线程安全。

总结

字符串操作的核心是:

  • 优先用 String 处理静态字符串,用 StringBuilder 处理动态修改;
  • 熟练掌握 charAtsubstringindexOf 等基础方法,以及遍历、拼接、反转等高频操作;
  • 注意 String 的不可变性带来的效率问题,避免在循环中直接修改 String

掌握这些,就能应对绝大多数字符串类算法题。