0. 前言
由于最近线上连续出现跟字符串处理相关的故障,实属不应该。也从这些故障中看到大家对常见的字符串处理API有一些淡忘,希望通过收集整体并总结常见的处理方法,大家温故而知新。
1. 创建和初始化:
a. 使用双引号直接创建字符串字面量,如:
String str = "Hello, World!";
- 存储位置: 这种方式创建的字符串被称为字符串字面量,它们在编译时会被放入字符串常量池(通常位于方法区或元空间)。这意味着所有相同内容的字符串字面量在内存中只有一份拷贝,多个引用可以共享同一份数据。
- 内存效率: 由于内存共享,这种方式有利于节省内存空间,尤其当多个变量引用相同的字符串内容时。
- 不变性: 字符串字面量是不可变的,对它们的任何修改操作都会创建新的字符串对象。
- 性能: 对于已存在于字符串常量池的字符串,直接赋值的查找和引用操作通常比new String()更快。
b. 使用构造函数创建,如:
String str = new String("Hello, World!");
- 存储位置: 这种方式会创建一个新的 String 对象,该对象被分配在堆内存中。即使内容与某个字面量相同,也会产生一个新的对象实例。
- 内存效率: 与直接赋值相比,每次使用 new String() 都会新建对象,可能导致内存占用增加,特别是当重复创建相同内容的字符串时。
- 不变性: 通过构造器创建的字符串同样是不可变的。
- 性能: 相较于直接赋值,使用构造器创建字符串通常涉及更多步骤(如检查字符串池、分配堆内存、复制字符数组等),可能影响性能。
- 行为差异: 使用 new String() 可以显式控制对象的创建,确保每次得到的是一个全新的对象,这对于某些特定场景(如需要独立生命周期的字符串)可能有意义。
c. 使用字符数组初始化,如:
char[] chars = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!'};
String str = new String(chars);
- 字符数组作为源: 这种方式允许从现有的字符数组中创建字符串,适用于从底层字符数据构建字符串的场景。
- 内存布局: 同样在堆上创建新的 String 对象,但内容来源于提供的字符数组。
- 字符数组的后续修改不影响字符串: 尽管字符串基于字符数组创建,但它仍然是不可变的,后续对字符数组的修改不会影响已创建的字符串。
d. 字符串连接,如:
String str1 = "Hello";String str2 = ", World!";String str = str1 + str2;
- 编译期优化: 对于编译期能确定结果的简单字符串拼接(如上述示例),编译器会将其优化为单个字面量赋值。
- 运行期动态拼接: 对于动态生成或运行期才能确定的字符串拼接,JVM会使用StringBuilder(或StringBuffer)在运行时进行拼接操作,最终结果也是一个新的字符串对象,位于堆上。
- 性能: 频繁的字符串拼接可能导致性能下降,因为每次拼接都会创建新的字符串对象。在这种情况下,建议显式使用StringBuilder来提高效率。
总得来说,不同字符串初始化方法的主要区别在于存储位置(常量池 vs. 堆)、内存效率(共享 vs. 新建)、性能(编译优化 vs. 运行时处理)以及是否显式控制对象创建。在实际编程中,应根据具体需求选择合适的初始化方式,兼顾代码可读性、性能和资源利用率。
2. 拼接(连接):
a. 使用 + 或 += 运算符进行字符串连接,如:
String str1 = "Hello";
String str2 = "World";
String result = str1 + " " + str2; // 结果为 "Hello World"
- 优点:
- 简洁易用: 语法直观,对开发者友好,尤其适合简单的字符串拼接场景。
- 编译期优化: 对于编译期就能确定结果的字符串字面量拼接,编译器会自动优化为单个字符串字面量,避免运行时额外开销。
- 缺点:
- 性能问题: 对于动态拼接或者在循环中多次拼接,每次使用+运算符都会创建新的 String 对象,导致大量临时对象的产生,消耗内存并可能影响性能。对于大规模或频繁的字符串拼接操作,+运算符的性能劣势更为明显。
b. String.concat() 方法, 如:
String str1 = "Hello";
String str2 = "World";
String result = str1.concat(" ").concat(str2); // 结果为 "Hello World"
- 优点:
- API 明确: 提供了专门用于字符串拼接的 API,表达意图清晰。
- 性能: 在单次拼接时,性能与使用+运算符相似。
- 缺点:
- 性能: 对于多次或大规模拼接,同样会产生不必要的临时对象,影响性能。
- 简洁: 相比直接使用+运算符,代码略显冗长。
c. StringJoiner 或 String.join() 方法(Java 8及以上版本),如:
String[] parts = {"Hello", "World"};
String result = String.join(" ", parts); // 结果为 "Hello World"// 或者使用 StringJoiner
StringJoiner joiner = new StringJoiner(" ");
joiner.add("Hello").add("World");
String result = joiner.toString(); // 结果为 "Hello World"
- 优点:
- 灵活: 支持多元素拼接,特别适合处理集合或数组中的元素,提供统一的分隔符设置。
- 高效: 内部使用StringBuilder实现,避免了多次创建临时对象,性能优于单纯使用+或concat()。
- 缺点:
- 较新特性: 旧版Java环境中不可用。 仅在Java 8及以上版本可用。 较复杂场景下代码稍显繁琐:相比于简单的一两处拼接,使用StringJoiner或String.join()在代码编写上可能稍显复杂。
d. StringBuilder 或 StringBuffer 类的append方法:如:
StringBuilder sb = new StringBuilder();
sb.append("Hello").append(" ").append("World");
String result1 = sb.toString(); // 结果为 "Hello World"
StringBuffer sbf = new StringBuffer(); // StringBuilder 类似,线程安全版本
sbf.append("Hello").append(" ").append("World");
String result2 = sbf.toString(); // 结果为 "Hello World"
- 优点:
- 高效: 提供动态扩容能力,适用于大量或频繁的字符串拼接操作,避免了创建不必要的临时对象,性能优异。
- 线程安全性: StringBuffer 是线程安全的,适用于多线程环境下的字符串拼接。
- 缺点:
- 代码相对复杂: 相比直接使用+运算符,需要创建对象并调用方法,代码稍微繁琐。
- 线程安全的开销: StringBuffer虽然线程安全,但在单线程环境下其同步机制可能会带来额外性能开销,此时应优先使用非同步的StringBuilder。
综上所述,选择字符串拼接方法时应考虑以下因素:
- 操作简单度:对于简单、偶尔的拼接,+运算符最为简洁。
- 性能要求:对于大量或频繁的拼接,尤其是循环内拼接,应使用StringBuilder或StringJoiner以提高性能。
- 线程安全:在多线程环境下,使用StringBuffer确保线程安全;单线程环境下优先使用StringBuilder。
- Java 版本兼容性:若需兼容旧版Java环境,不能使用String.join()或StringJoiner。
3. 比较:
a. 使用 == 运算符,如:
String str1 = "Hello";
String str2 = "Hello";
boolean equal = (str1 == str2); // 结果为 true
- 优点:
- 快速:如果比较的对象是字符串字面量或指向同一块内存区域的字符串对象引用,== 比较非常快速。
- 适用简单场景:对于已知的字符串字面量之间的比较,或是确保比较的是同一个对象引用时,使用 == 是合理的。
- 缺点:
- 仅比较引用:== 比较的是对象引用而非字符串内容,因此当比较的是两个不同的 String 对象(即使内容相同),结果通常为 false。
- 易引发错误:在大多数情况下,使用 == 来比较字符串内容容易导致逻辑错误,除非非常明确知道比较的是同一个对象引用。
b. 使用 equals() 或 equalsIgnoreCase() 方法进行内容相等性比较。
String str1 = "Hello";
String str2 = new String("Hello");
boolean equal = str1.equals(str2); // 结果为 true
String str1 = "Hello";
String str2 = "hello";
boolean equal = str1.equalsIgnoreCase(str2); // 结果为 true
- 优点:
- 内容比较:正确比较字符串的内容是否相同,不受对象引用的影响。
- 通用性:适用于所有 Object 类及其子类,是标准的相等性检查手段。
- 区分大小写:默认情况下,按照字符的字典序精确比较,区分大小写。使用equalsIgnoreCase方法忽略字符的大小写进行比较,适用于对大小写不敏感的场景
- 缺点:
- 空指针异常:如果一方为 null,调用 equals() 会抛出 NullPointerException。需要在调用前做空值检查。
- 不适用于 null 和空字符串的等价比较:equals() 不认为 null 和空字符串(“”)相等,需要额外处理。
c. 使用 compareTo() 或 compareToIgnoreCase() 方法进行字典顺序比较。
String str1 = "Hello";
String str2 = "World";
int comparison = str1.compareTo(str2); // 结果为负数,表示 str1 小于 str2
String str3 = "hello";
String str4 = "world";
int caseInsensitiveComparison = str3.compareToIgnoreCase(str4); // 结果为 0,表示忽略大小写后两者相等
- 优点:
- 比较顺序:不仅判断是否相等,还能得到两个字符串的字典序关系(大于、小于或等于)。
- 区分/忽略大小写:compareTo() 区分大小写,compareToIgnoreCase() 则忽略大小写。
- 缺点:
- 返回类型:返回的是整数而非布尔值,需要根据返回值判断相等性或顺序关系。
- 空指针异常:与 equals() 一样,需要预防空指针异常。
总结:
- ==:仅适用于比较对象引用是否相同,快速但容易出错,不适用于内容比较。
- equals():标准的内容比较方法,区分大小写,但需注意空指针异常。
- equalsIgnoreCase():在 equals() 基础上忽略大小写,适用于大小写不敏感的场景。
- compareTo() / compareToIgnoreCase():提供字符串间的排序信息,适用于需要比较字符串顺序的场景,同样需要注意空指针异常。
在实际编程中,通常优先使用 equals() 或 equalsIgnoreCase() 进行字符串内容的相等性检查,只有在需要排序信息时才使用 compareTo() 系列方法。务必避免使用 == 进行字符串内容比较,除非确信比较的是同一个对象引用。同时,对可能为 null 的字符串进行比较时,应先进行空值检查。
d. 使用 startsWith(), endsWith() 方法判断字符串是否以特定子串开头或结尾。
- 这两个方法在实现上通常采用索引来快速定位字符串首尾,然后进行相应长度的字符比较。对于长字符串,这种方法的时间复杂度为 O(k),其中 k 是子串的长度,比遍历整个字符串要高效得多。
- 与使用正则表达式或其他更通用的匹配方法相比,startsWith() 和 endsWith() 不会意外匹配到包含目标子串但位置不符合要求的字符串,避免了误判。
4. 查找与索引:
a. 使用indexOf() 或者lastIndexOf() 方法查找子串首次或者最后一次出现的位置,返回指定子字符串在原字符串中首/末次出现的索引位置,如果不存在则返回 -1。可选地,提供一个起始索引 fromIndex,从该位置开始向后/前查找。
int index = str.indexOf(substring);
int indexFromIndex = str.indexOf(substring, fromIndex);
int index = str.lastIndexOf(substring);
int indexFromIndex = str.lastIndexOf(substring, fromIndex);
- 如果提供的 fromIndex 超出原字符串范围,可能抛出 StringIndexOutOfBoundsException。需要程序员自行确保参数合法性。
b. 使用 contains() 方法检查字符串是否包含指定子串,返回布尔值。
boolean contains = str.contains(substring);
- 优点:
- 简化逻辑:简化了只需要判断是否存在子字符串的场景,无需关注具体的索引位置。
- 易于理解:方法名称直接表明意图,代码可读性强。
- 缺点:
- 功能受限:仅能判断是否存在,不能获取子字符串的具体位置信息。
5. 切片(子串提取):
a. 使用 substring() 方法获取指定索引范围内的子字符串。根据指定的起始索引 beginIndex 返回从该位置开始到字符串末尾的子串;或者根据起始索引 beginIndex 和结束索引 endIndex 返回二者之间的子串(不包括 endIndex 处的字符)。
String subStr = str.substring(beginIndex);
String subStrFromTo = str.substring(beginIndex, endIndex);
- 优点:
- 简单直观:语法简洁,直接根据索引来提取子串,易于理解和使用。
- 广泛支持:Java标准库内置方法,无需引入额外库。
- 缺点:
- 索引越界检查:如果提供的索引超出字符串范围,会抛出 StringIndexOutOfBoundsException。需要确保索引的有效性。
- 不支持负索引:无法像某些编程语言那样使用负索引来从字符串末尾开始计数。
- 不支持切片步长:不能通过一次调用指定切片的步长(如每隔n个字符提取一个字符)。
b. 使用第三方库 如Apache Commons Lang的StringUtils实现子串提取
String subStr = StringUtils.substring(str, beginIndex, endIndex);
- 优点:
- 额外功能:某些库可能提供更丰富的切片选项,如支持负索引、步长等。
- 异常处理:库可能会对异常情况进行更优雅的处理,如返回空字符串而不是抛出异常。
- 缺点:
- 额外依赖:需要引入对应的库作为项目依赖,增加项目复杂性。
- 学习成本:需要熟悉库的API和用法,对于团队内没有使用经验的成员可能存在学习曲线。
6. 长度与字符访问:
a. 使用 length() 方法获取字符串长度。
int length = str.length();
- 优点:
- 直接高效:直接返回字符串的字符数,执行速度快,时间复杂度为O(1)。
- 易于理解:方法名称明确,使用简单。
- 缺点:
- 无异常处理:对于空字符串或null字符串,返回值为0或抛出NullPointerException,需要在使用前确保字符串非空。
b. 使用 charAt() 方法访问指定索引处的字符。
char ch = str.charAt(index);
- 优点:
- 直接访问:提供了对字符串中任意位置字符的直接访问能力,方便进行单字符操作。
- 索引范围检查:如果索引超出字符串范围,会抛出StringIndexOutOfBoundsException,有助于及时发现并修复程序错误。
- 缺点:
- 无异常处理:对于空字符串或null字符串,抛出NullPointerException。使用前需确保字符串非空且索引有效。
- 只读操作:仅能获取字符,不能修改字符串(Java字符串是不可变的)。
b. 使用 codePointAt() 方法访问指定索引处的Unicode代码点。 与charAt()的区别在于,它可以正确处理代理对(surrogate pairs),即Unicode中那些需要两个Java字符(char)表示的单个字符。
String myStr = "Hello";
int result = myStr.codePointAt(0);
System.out.println(result);
- 优点:
- 完整Unicode支持:对于包含扩展字符集(如表情符号、特殊字符)的字符串,codePointAt()能准确获取完整的Unicode代码点,避免因代理对导致的字符识别错误。
- 国际化友好:对于处理多语言文本,特别是包含复杂字符集的语言(如中文、日文、韩文),codePointAt()提供了更准确的字符访问方式。
- 缺点:
- 复杂度稍高:相比charAt(),处理代理对时需要额外的逻辑,理解起来稍显复杂。
- 索引调整:如果连续调用此方法遍历字符串,需要注意索引递增时可能需要加2(当遇到代理对时)。
7. 字符串替换:
a. 使用 replace() 方法替换指定字符部分。
String replace(char oldChar, char newChar)
String replace(CharSequence target, CharSequence replacement)
String original = "Hello, World! This is a test string with repeated chars: eee.";// 使用 replace() 方法将所有 'e' 替换为 '*'
String replaced = original.replace('e', '*');System.out.println(replaced);String text = "The quick brown fox jumps over the lazy dog.";// 使用 replace() 方法将子串 "fox" 替换为 "cat"
String modified = text.replace("fox", "cat");System.out.println(modified);
- 功能:
- 替换字符:第一个重载版本接受两个字符参数,将字符串中所有出现的旧字符 oldChar 替换为新字符 newChar。
- 替换子串:第二个重载版本接受两个字符串参数,将字符串中所有出现的旧子串 target 替换为新子串 replacement。
- 特点:
- 非正则表达式:replace() 方法不使用正则表达式进行匹配,而是直接按照字面意义上的字符或子串进行替换。
- 区分大小写:替换时不区分大小写,除非替换的目标本身就是大小写敏感的字符串。
- 替换次数:替换所有匹配项,没有次数限制。
b. 使用 replaceAll() 方法替换正则表达式匹配的部分。
String replaceAll(String regex, String replacement)
String input = "The year is 2024, and we have 8 planets in our solar system.";// 使用 replaceAll() 方法,正则表达式匹配所有数字,将其替换为 "*"
String replaced = input.replaceAll("\\d+", "*");System.out.println(replaced);String sentence = "Java is a high-level, class-based, object-oriented programming language.";// 使用 replaceAll() 方法,正则表达式匹配所有以大写字母开头的单词,将其替换为 "[Uppercase]"
String modified = sentence.replaceAll("\\b[A-Z]\\w*", "[Uppercase]");System.out.println(modified);
- 功能:
- 基于正则表达式替换:接受一个正则表达式 regex 和一个替换字符串 replacement,将字符串中所有与正则表达式匹配的部分替换为指定的新字符串。
- 特点:
- 正则表达式支持:允许使用复杂的匹配模式,如通配符、重复、分组等,极大地增强了替换的灵活性。
- 大小写敏感:正则表达式匹配默认区分大小写,除非正则表达式中包含相应的大小写不敏感标志(如 (?i))。
- 替换次数:替换所有匹配项,没有次数限制。
c. 使用 trim() 方法移除字符串两端的空白字符。
d. 使用 toLowerCase() 或 toUpperCase() 方法转换字符串为全小写或全大写。
8. 分隔与拆分:
a. 使用 split() 方法根据指定分隔符将字符串切割为子字符串数组。根据指定的分隔符将源字符串拆分成一个字符串数组。分隔符可以是单个字符、固定字符串,甚至是正则表达式。
public class StringSplitExample {public static void main(String[] args) {String input = "Spring,Summer,Fall,Winter";// 使用逗号作为分隔符,进行简单分割String[] seasonsSimple = input.split(",");System.out.println("Simple split:");for (String season : seasonsSimple) {System.out.println(season);}System.out.println("\nUsing regular expression as delimiter:");// 使用正则表达式 "[, ]+" 作为分隔符,匹配逗号和空格的组合String complexInput = "Spring, Summer, Fall, Winter";String[] seasonsRegEx = complexInput.split("[, ]+");for (String season : seasonsRegEx) {System.out.println(season);}System.out.println("\nLimiting the number of splits:");// 使用逗号作为分隔符,限制最多分割出两个子串String limitedInput = "January,February,March,April";String[] limitedSplit = limitedInput.split(",", 2);System.out.println(Arrays.toString(limitedSplit));}
}
- 特点:
- 正则表达式支持:除了简单的字符分隔符外,还可以使用复杂的正则表达式进行分割,增加了分割的灵活性。
- 空值安全:对空字符串或null字符串调用split()方法不会抛出异常,而是返回空数组或根据正则表达式匹配规则返回预期结果。
- 结果数组长度:返回的数组长度取决于分割后的子串数量,可能小于分隔符在源字符串中出现的次数,因为连续分隔符和限制最大长度都会影响结果数组的长度。
- 连续分隔符处理:连续的分隔符会被视为一个,不会产生空字符串元素。例如,使用逗号作为分隔符,"a,b,c"会被分割为[“a”, “b”, “c”],中间没有空字符串。
- 限制分裂数量:可选地,可以通过传递第二个整数参数来限制返回数组的最大长度。如果达到限制,剩余部分将合并为最后一个数组元素。
b. 使用第三方类库Apache Common Lang StringUtils split() 方法根据指定分隔符将字符串切割为子字符串数组。
import org.apache.commons.lang3.StringUtils;public class StringUtilsSplitExample {public static void main(String[] args) {String input = "Spring, Summer, Fall, Winter";// 使用逗号作为分隔符,进行简单分割String[] seasons = StringUtils.split(input, ",");System.out.println("Simple split:");for (String season : seasons) {System.out.println(season);}System.out.println("\nRemoving whitespace from results:");// 使用逗号作为分隔符,同时去除结果中的空白元素String spacedInput = "Spring, Summer,\tFall, Winter";String[] noWhitespace = StringUtils.split(spacedInput, ",", true, true);for (String season : noWhitespace) {System.out.println(season);}System.out.println("\nLimiting the number of splits:");// 使用逗号作为分隔符,限制最多分割出两个子串String limitedInput = "January,February,March,April";String[] limitedSplit = StringUtils.split(limitedInput, ",", 2);System.out.println(Arrays.toString(limitedSplit));}
}
- 功能与特点:
- 字符串分割:根据指定的分隔符将源字符串拆分成一个字符串数组。
- 空值处理:如果源字符串为 null,split() 方法将返回一个空数组。
- 去除空白:StringUtils.split() 提供了可选的参数来控制是否去除结果数组中的空白元素。这在处理含有连续空格或混合空格(如空格、制表符、换行符)的字符串时非常有用。
- 限制分裂数量:与 Java String.split() 类似,也可以通过传递第二个整数参数来限制返回数组的最大长度。如果达到限制,剩余部分将合并为最后一个数组元素。
- 参数顺序:StringUtils.split() 的参数顺序略有不同,且多了一个是否去除前导空白的布尔参数。其方法签名通常为 split(String str, String separatorChars, boolean trimTokens, boolean ignoreEmptyTokens),而 Java String.split() 方法签名是 split(String regex, int limit)。
- 兼容性:StringUtils.split() 方法设计时考虑了与 Java String.split() 的兼容性,对于不使用去除空白选项的常规调用,行为与 String.split() 相似。
9. 编码与解码:
a. 使用 getBytes() 方法将字符串转换为字节数组(特定字符集编码)。
import java.io.UnsupportedEncodingException;public class GetBytesExample {public static void main(String[] args) {String inputString = "你好,世界!";try {// 使用UTF-8编码将字符串转换为字节数组byte[] utf8Bytes = inputString.getBytes("UTF-8");System.out.println("UTF-8 encoded bytes: " + Arrays.toString(utf8Bytes));// 使用GBK编码将字符串转换为字节数组byte[] gbkBytes = inputString.getBytes("GBK");System.out.println("GBK encoded bytes: " + Arrays.toString(gbkBytes));} catch (UnsupportedEncodingException e) {System.err.println("Unsupported character encoding encountered.");e.printStackTrace();}}
}
- 特点:
- 字符编码转换:将字符串按照指定的字符集编码转换为对应的字节序列。不同的字符集编码(如 UTF-8、ISO-8859-1、GBK 等)对同一字符串会有不同的字节表示。
- 跨平台兼容性:通过指定字符集编码,可以在不同平台间进行数据交换,确保字符串在不同系统、不同应用程序之间保持一致的解读。
- 字节流处理:转换后的字节数组可以直接用于网络传输、文件存储、加密解密等涉及字节流的操作。
- 内存占用:字节数组的大小通常大于原始字符串的长度,因为不同的字符在特定字符集中可能占用1个或多个字节。
- 错误检查:如果字符串包含无法在指定字符集中表示的字符,可能会抛出 UnsupportedEncodingException 异常。
b. 使用 new String(byte[], charset) 构造函数将字节数组(特定字符集编码)还原为字符串。
import java.nio.charset.StandardCharsets;public class NewStringExample {public static void main(String[] args) {// 已知字节数组采用UTF-8编码byte[] utf8Bytes = {228, 184, 173, 229, 1.jpg, 171, 229, 1.jpg, 185, 32, .png, 115, 104, 105, 109, 101};// 已知字节数组采用GBK编码byte[] gbkBytes = {60, -¼, -½, -¾, -¿, -¼, -¾, -¿, 32, 67, 104, 105, 110, 97};// 使用UTF-8编码将字节数组还原为字符串String utf8String = new String(utf8Bytes, StandardCharsets.UTF_8);System.out.println("UTF-8 decoded string: " + utf8String);// 使用GBK编码将字节数组还原为字符串String gbkString = new String(gbkBytes, StandardCharsets.GBK);System.out.println("GBK decoded string: " + gbkString);}
}
- 特点:
- 字符解码:将字节数组按照指定的字符集编码解码为对应的字符串。与 getBytes() 方法相反,这个过程是从字节到字符的逆向转换。
- 数据还原:确保接收到的字节数组(如来自网络传输、文件读取、加密解密等场景)能够准确地按照其原始编码还原为正确的字符串形式。
- 跨平台兼容性:通过指定相同的字符集编码,可以确保在不同平台间传输的字节数组能够被正确解码为一致的字符串。
- 错误检查:如果字节数组的内容无法按照指定的字符集正确解码,可能会导致乱码或抛出 UnsupportedEncodingException 异常(尽管在 new String() 构造函数中不会抛出此异常,但在底层字符集查找和解码过程中可能出现问题)。
c. 使用 StringCoding.decode() 和 StringCoding.encode() 进行更底层的编码与解码操作。
StringCoding.decode() 和 StringCoding.encode() 是 Java 核心库内部使用的两个方法,分别负责字符串编码和解码的底层实现。这些方法通常不直接暴露给开发者使用,而是作为 String 类和 StringBuffer 类的内部逻辑的一部分。不过,通过反射或内部类访问,理论上可以调用它们。尽管如此,直接使用这些方法并不推荐,因为它们的设计目的是为了内部实现,而非对外提供服务,且可能随JDK版本更新而发生变化。
- 特点:
- 低级别操作:StringCoding.decode() 和 StringCoding.encode() 提供了比 String.getBytes() 和 new String(byte[], charset) 更底层的编码与解码操作。它们直接与 CharsetDecoder 和 CharsetEncoder 类交互,更接近于JVM内部的字符编码机制。
- 性能优化:这些方法可能包含了一些针对特定场景的性能优化,比如缓存常用字符集的解码器和编码器、批量处理字节缓冲等,以提高编码和解码的效率。
- 异常处理:由于它们是内部实现,异常处理通常更为直接和严格。编码或解码过程中遇到的问题可能会直接触发 IllegalArgumentException 或 CharacterCodingException,而不是像 String.getBytes() 那样仅在字符集不支持时抛出 UnsupportedEncodingException。
10. 遍历与迭代:
a. 使用索引循环,直接通过索引来访问字符串中的每个字符,是最基础的遍历方式。
String str = "Hello, World!";
for (int i = 0; i < str.length(); i++) {char c = str.charAt(i);System.out.println(c);
}
b. 使用 for-each 循环, 字符串本身并不直接实现 Iterable,但可以间接使用 Character 类的 chars() 或 codePoints() 方法来配合 for-each 循环。
String str = "Hello, World!";// 使用 chars() 方法遍历字符
for (char c : str.chars().toArray()) {System.out.println(c);
}// 使用 codePoints() 方法遍历 Unicode 码点
for (int codePoint : str.codePoints().toArray()) {System.out.println((char) codePoint);
}
c. 使用 StringReader 和 BufferedReader, 将字符串当作文本流处理,适用于需要按行或按一定读取策略遍历字符串的场景。
String str = "Line 1\nLine 2\nLine 3";try (BufferedReader reader = new BufferedReader(new StringReader(str))) {String line;while ((line = reader.readLine()) != null) {System.out.println(line);}
} catch (IOException e) {// In this case, IOException should not occur since we're using a StringReader.e.printStackTrace();
}
d. 使用 Pattern 和 Matcher(正则表达式), 适用于需要基于某种模式(正则表达式)查找和迭代字符串中特定部分的场景。
String str = "The quick brown fox jumps over the lazy dog.";
Pattern pattern = Pattern.compile("\\b\\w+\\b"); // Match words
Matcher matcher = pattern.matcher(str);while (matcher.find()) {System.out.println(matcher.group());
}
11. 参考文档
- https://docs.oracle.com/javase/8/docs/api/java/lang/String.html
- https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html
- https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/StringUtils.java
- https://github.com/vipshop/vjtools/blob/master/vjkit/src/main/java/com/vip/vjtools/vjkit/text/MoreStringUtil.java
- https://github.com/spring-projects/spring-framework/blob/main/spring-core/src/main/java/org/springframework/util/StringUtils.java