SCX String 是一个字符串工具库。
它提供 ScxString,用于补充 JDK String 中一些常用但写起来比较重复的操作,例如忽略大小写的前缀判断、后缀判断、查找、包含判断,以及按照 Unicode code point 处理字符串。
SCX String 本身不是模板引擎,也不是文本解析框架。它只是对常见字符串操作做了一层简单封装,让代码更直观、更容易复用。
当前版本为 0.1.0。
<dependency>
<groupId>dev.scx</groupId>
<artifactId>scx-string</artifactId>
<version>0.1.0</version>
</dependency>
SCX String 当前只有一个核心类:
ScxString 字符串工具类
它提供的能力可以分为两类:
IgnoreCase 忽略大小写的字符串判断和查找
CodePoint 按 Unicode code point 处理字符串
IgnoreCase 相关方法包括:
startsWithIgnoreCase
endsWithIgnoreCase
indexOfIgnoreCase
lastIndexOfIgnoreCase
containsIgnoreCase
CodePoint 相关方法包括:
splitByCodePoint
lengthByCodePoint
charAtByCodePoint
substringByCodePoint
判断字符串是否以某个前缀开始,忽略大小写:
import dev.scx.string.ScxString;
var result = ScxString.startsWithIgnoreCase("Hello World", "hello");
System.out.println(result);
输出:
true
查找字符串,忽略大小写:
var index = ScxString.indexOfIgnoreCase("xxAbCxx", "abc");
System.out.println(index);
输出:
2
判断是否包含某个字符串,忽略大小写:
var result = ScxString.containsIgnoreCase("你好ABC", "abc");
System.out.println(result);
输出:
true
按照 code point 计算长度:
var length = ScxString.lengthByCodePoint("a😀b");
System.out.println(length);
输出:
3
按照 code point 拆分字符串:
var array = ScxString.splitByCodePoint("a😀b");
System.out.println(java.util.Arrays.toString(array));
输出:
[a, 😀, b]
SCX String 中的 IgnoreCase 方法用于补充 String 自带方法。
JDK 中已经有:
string.startsWith(prefix)
string.endsWith(suffix)
string.indexOf(target)
string.lastIndexOf(target)
string.contains(target)
但是这些方法默认都是区分大小写的。
SCX String 提供了对应的忽略大小写版本:
ScxString.startsWithIgnoreCase(string, prefix)
ScxString.endsWithIgnoreCase(string, suffix)
ScxString.indexOfIgnoreCase(string, target)
ScxString.lastIndexOfIgnoreCase(string, target)
ScxString.containsIgnoreCase(string, target)
例如:
ScxString.containsIgnoreCase("Hello World", "hello")
结果为:
true
startsWithIgnoreCase(...) 用于判断字符串是否以指定前缀开始,忽略大小写。
boolean startsWithIgnoreCase(String string, String prefix)
示例:
var result = ScxString.startsWithIgnoreCase("aBc123", "ABC");
System.out.println(result);
输出:
true
空前缀会返回 true:
var result = ScxString.startsWithIgnoreCase("abc123", "");
System.out.println(result);
输出:
true
如果前缀比原字符串更长,会返回 false:
var result = ScxString.startsWithIgnoreCase("abc", "abcd");
System.out.println(result);
输出:
false
也可以从指定偏移量开始判断:
boolean startsWithIgnoreCase(String string, String prefix, int toffset)
示例:
var result = ScxString.startsWithIgnoreCase("xxxAbC123", "abc", 3);
System.out.println(result);
输出:
true
这里表示从 "xxxAbC123" 的索引 3 开始判断是否匹配 "abc"。
如果偏移量不匹配,会返回 false:
var result = ScxString.startsWithIgnoreCase("xxxAbC123", "abc", 2);
System.out.println(result);
输出:
false
endsWithIgnoreCase(...) 用于判断字符串是否以指定后缀结束,忽略大小写。
boolean endsWithIgnoreCase(String string, String suffix)
示例:
var result = ScxString.endsWithIgnoreCase("123aBc", "ABC");
System.out.println(result);
输出:
true
空后缀会返回 true:
var result = ScxString.endsWithIgnoreCase("abc123", "");
System.out.println(result);
输出:
true
如果后缀比原字符串更长,会返回 false:
var result = ScxString.endsWithIgnoreCase("abc", "zabc");
System.out.println(result);
输出:
false
indexOfIgnoreCase(...) 用于从前往后查找目标字符串,忽略大小写。
int indexOfIgnoreCase(String string, String target)
示例:
var index = ScxString.indexOfIgnoreCase("xxAbCxx", "abc");
System.out.println(index);
输出:
2
如果找不到,会返回 -1:
var index = ScxString.indexOfIgnoreCase("abc", "d");
System.out.println(index);
输出:
-1
如果目标字符串为空字符串,会返回开始查找的位置。
var index = ScxString.indexOfIgnoreCase("abc", "");
System.out.println(index);
输出:
0
int indexOfIgnoreCase(String string, String target, int fromIndex)
示例:
var s = "abcABCabc";
System.out.println(ScxString.indexOfIgnoreCase(s, "abc", 0));
System.out.println(ScxString.indexOfIgnoreCase(s, "abc", 1));
System.out.println(ScxString.indexOfIgnoreCase(s, "abc", 4));
输出:
0
3
6
fromIndex 的处理方式和 String#indexOf 类似:
fromIndex < 0 按 0 处理
fromIndex > string.length 按 string.length 处理
例如:
var s = "abcABCabc";
System.out.println(ScxString.indexOfIgnoreCase(s, "abc", -99));
System.out.println(ScxString.indexOfIgnoreCase(s, "abc", 99));
输出:
0
-1
查找空字符串时,会返回处理后的 fromIndex:
var s = "abcABCabc";
System.out.println(ScxString.indexOfIgnoreCase(s, "", -99));
System.out.println(ScxString.indexOfIgnoreCase(s, "", 2));
System.out.println(ScxString.indexOfIgnoreCase(s, "", 99));
输出:
0
2
9
int indexOfIgnoreCase(String string, String target, int beginIndex, int endIndex)
这里的范围是:
[beginIndex, endIndex)
也就是包含 beginIndex,不包含 endIndex。
示例:
var s = "xxAbCxxABCxx";
System.out.println(ScxString.indexOfIgnoreCase(s, "abc", 0, s.length()));
System.out.println(ScxString.indexOfIgnoreCase(s, "abc", 3, s.length()));
System.out.println(ScxString.indexOfIgnoreCase(s, "abc", 0, 5));
System.out.println(ScxString.indexOfIgnoreCase(s, "abc", 0, 4));
输出:
2
7
2
-1
如果目标字符串为空,会返回 beginIndex:
var s = "xxAbCxxABCxx";
System.out.println(ScxString.indexOfIgnoreCase(s, "", 4, 4));
System.out.println(ScxString.indexOfIgnoreCase(s, "", 4, 8));
输出:
4
4
范围必须合法:
beginIndex >= 0
beginIndex <= endIndex
endIndex <= string.length()
否则会抛出 StringIndexOutOfBoundsException。
lastIndexOfIgnoreCase(...) 用于从后往前查找目标字符串,忽略大小写。
int lastIndexOfIgnoreCase(String string, String target)
示例:
var index = ScxString.lastIndexOfIgnoreCase("abcABCabc", "abc");
System.out.println(index);
输出:
6
如果找不到,会返回 -1:
var index = ScxString.lastIndexOfIgnoreCase("abc", "d");
System.out.println(index);
输出:
-1
如果目标字符串为空字符串,会返回字符串长度:
var index = ScxString.lastIndexOfIgnoreCase("abc", "");
System.out.println(index);
输出:
3
int lastIndexOfIgnoreCase(String string, String target, int fromIndex)
示例:
var s = "abcABCabc";
System.out.println(ScxString.lastIndexOfIgnoreCase(s, "abc", 99));
System.out.println(ScxString.lastIndexOfIgnoreCase(s, "abc", 6));
System.out.println(ScxString.lastIndexOfIgnoreCase(s, "abc", 5));
System.out.println(ScxString.lastIndexOfIgnoreCase(s, "abc", 2));
System.out.println(ScxString.lastIndexOfIgnoreCase(s, "abc", -1));
输出:
6
6
3
0
-1
fromIndex 的处理方式如下:
fromIndex < 0 返回 -1
fromIndex > 最大可匹配位置 按最大可匹配位置处理
查找空字符串时,会返回处理后的 fromIndex:
var s = "abcABCabc";
System.out.println(ScxString.lastIndexOfIgnoreCase(s, "", 99));
System.out.println(ScxString.lastIndexOfIgnoreCase(s, "", 1));
System.out.println(ScxString.lastIndexOfIgnoreCase(s, "", -1));
输出:
9
1
-1
containsIgnoreCase(...) 用于判断字符串中是否包含目标字符串,忽略大小写。
boolean containsIgnoreCase(String string, String target)
它内部等价于:
ScxString.indexOfIgnoreCase(string, target) >= 0
示例:
var result = ScxString.containsIgnoreCase("xxAbCxx", "abc");
System.out.println(result);
输出:
true
中文、数字、符号等不涉及大小写的字符会保持正常比较:
var result = ScxString.containsIgnoreCase("你好ABC", "abc");
System.out.println(result);
输出:
true
空字符串总是可以被包含:
var result = ScxString.containsIgnoreCase("abc", "");
System.out.println(result);
输出:
true
如果找不到目标字符串,会返回 false:
var result = ScxString.containsIgnoreCase("xxAbCxx", "777");
System.out.println(result);
输出:
false
Java 的 String 底层使用 UTF-16。
这意味着 String#length() 返回的是 UTF-16 code unit 的数量,而不一定是日常理解中的“字符数量”。
例如:
System.out.println("a😀b".length());
输出:
4
因为 😀 在 UTF-16 中会占用两个 char。
如果希望把 😀 这类字符按一个单位处理,可以使用 SCX String 中的 CodePoint 相关方法:
splitByCodePoint
lengthByCodePoint
charAtByCodePoint
substringByCodePoint
需要注意:
Code point 不完全等同于用户感知上的字符
例如带组合符号的字符可能由多个 code point 组成。SCX String 这里处理的是 Unicode code point,不是完整的 grapheme cluster。
splitByCodePoint(...) 用于把字符串按 code point 拆成 String[]。
String[] splitByCodePoint(String string)
示例:
import dev.scx.string.ScxString;
import java.util.Arrays;
var array = ScxString.splitByCodePoint("a😀b");
System.out.println(Arrays.toString(array));
输出:
[a, 😀, b]
更复杂的例子:
var array = ScxString.splitByCodePoint("🐷😂🤣😅😍😡123你好");
System.out.println(Arrays.toString(array));
输出:
[🐷, 😂, 🤣, 😅, 😍, 😡, 1, 2, 3, 你, 好]
空字符串会返回空数组:
var array = ScxString.splitByCodePoint("");
System.out.println(array.length);
输出:
0
lengthByCodePoint(...) 用于按 code point 计算字符串长度。
int lengthByCodePoint(String string)
示例:
System.out.println(ScxString.lengthByCodePoint(""));
System.out.println(ScxString.lengthByCodePoint("abc"));
System.out.println(ScxString.lengthByCodePoint("a😀b"));
System.out.println(ScxString.lengthByCodePoint("🐷😂🤣😅😍😡123你好"));
输出:
0
3
3
11
对比 String#length():
System.out.println("a😀b".length());
System.out.println(ScxString.lengthByCodePoint("a😀b"));
输出:
4
3
charAtByCodePoint(...) 用于按照 code point 索引获取指定位置的字符。
String charAtByCodePoint(String string, int index)
返回值是 String,不是 char。
这是因为一个 code point 不一定能用一个 Java char 表示,例如 emoji。
示例:
var s = "a😀b你";
System.out.println(ScxString.charAtByCodePoint(s, 0));
System.out.println(ScxString.charAtByCodePoint(s, 1));
System.out.println(ScxString.charAtByCodePoint(s, 2));
System.out.println(ScxString.charAtByCodePoint(s, 3));
输出:
a
😀
b
你
如果索引越界,会抛出 IndexOutOfBoundsException:
ScxString.charAtByCodePoint("a😀b你", -1);
ScxString.charAtByCodePoint("a😀b你", 4);
ScxString.charAtByCodePoint("", 0);
substringByCodePoint(...) 用于按照 code point 索引截取字符串。
String substringByCodePoint(String string, int beginIndex)
示例:
var s = "a😀b你c";
System.out.println(ScxString.substringByCodePoint(s, 0));
System.out.println(ScxString.substringByCodePoint(s, 1));
System.out.println(ScxString.substringByCodePoint(s, 3));
System.out.println(ScxString.substringByCodePoint(s, 5));
输出:
a😀b你c
😀b你c
你c
也可以指定结束索引:
String substringByCodePoint(String string, int beginIndex, int endIndex)
这里的范围是:
[beginIndex, endIndex)
示例:
var s = "a😀b你c";
System.out.println(ScxString.substringByCodePoint(s, 0, 2));
System.out.println(ScxString.substringByCodePoint(s, 1, 3));
System.out.println(ScxString.substringByCodePoint(s, 3, 5));
System.out.println(ScxString.substringByCodePoint(s, 2, 2));
输出:
a😀
😀b
你c
如果索引越界,或者范围不合法,会抛出 IndexOutOfBoundsException:
ScxString.substringByCodePoint("a😀b你c", -1);
ScxString.substringByCodePoint("a😀b你c", 6);
ScxString.substringByCodePoint("a😀b你c", 3, 2);
ScxString.substringByCodePoint("a😀b你c", 0, 6);
SCX String 不做 null 安全处理。
也就是说,传入 null 时通常会直接抛出 NullPointerException。
例如:
ScxString.startsWithIgnoreCase(null, "abc");
ScxString.startsWithIgnoreCase("abc", null);
ScxString.containsIgnoreCase(null, "abc");
ScxString.lengthByCodePoint(null);
这和 JDK 中很多字符串方法的行为保持一致。
如果业务中允许 null,应该在调用前自行处理:
if (value != null && ScxString.containsIgnoreCase(value, "abc")) {
// ...
}
或者:
var result = value != null && ScxString.startsWithIgnoreCase(value, "abc");
SCX String 对空字符串的处理遵循 JDK 字符串方法的常见语义。
常见规则如下:
startsWithIgnoreCase("abc", "") true
endsWithIgnoreCase("abc", "") true
indexOfIgnoreCase("abc", "") 0
indexOfIgnoreCase("abc", "", 2) 2
lastIndexOfIgnoreCase("abc", "") 3
containsIgnoreCase("abc", "") true
splitByCodePoint("") []
lengthByCodePoint("") 0
public static boolean startsWithIgnoreCase(String string, String prefix)
public static boolean startsWithIgnoreCase(String string, String prefix, int toffset)
public static boolean endsWithIgnoreCase(String string, String suffix)
public static int indexOfIgnoreCase(String string, String target)
public static int indexOfIgnoreCase(String string, String target, int fromIndex)
public static int indexOfIgnoreCase(String string, String target, int beginIndex, int endIndex)
public static int lastIndexOfIgnoreCase(String string, String target)
public static int lastIndexOfIgnoreCase(String string, String target, int fromIndex)
public static boolean containsIgnoreCase(String string, String target)
public static String[] splitByCodePoint(String string)
public static int lengthByCodePoint(String string)
public static String charAtByCodePoint(String string, int index)
public static String substringByCodePoint(String string, int beginIndex)
public static String substringByCodePoint(String string, int beginIndex, int endIndex)
SCX String 适合用于:
不区分大小写地判断前缀
不区分大小写地判断后缀
不区分大小写地查找字符串
不区分大小写地判断包含关系
处理包含 emoji 的字符串长度
按 code point 拆分字符串
按 code point 截取字符串
按 code point 获取指定位置字符
例如,在处理 HTTP Header、配置项、命令参数、文件扩展名、用户输入搜索关键字时,经常需要忽略大小写:
if (ScxString.endsWithIgnoreCase(fileName, ".jpg")) {
// 处理 jpg 文件
}
在处理 emoji 或其它非 BMP 字符时,可以使用 code point 相关方法避免直接按 UTF-16 char 切分:
var text = "a😀b";
var chars = ScxString.splitByCodePoint(text);
结果是:
[a, 😀, b]
而不是把 😀 拆成两个 Java char。