Java 如何获取字符串中的首个可打印字符
Java 获取字符串首个可打印字符方案
核心实现思路
你需要的「首个可打印字符」本质是Unicode标准定义的字形簇(Grapheme Cluster),即视觉上呈现为单个字符的单元,不管它底层占用多少码点、多少字节。Java 内置的BreakIterator工具类已经实现了字形簇的拆分规则,可以直接适配单字节普通字符、多字节Emoji、多码点组合国旗Emoji等所有场景。
完整实现代码
import java.text.BreakIterator; import java.util.Locale; public class PrintableCharUtils { public static String getFirstPrintableGrapheme(String input) { // 空值提前返回 if (input == null || input.isEmpty()) { return ""; } // 初始化字形簇迭代器,使用ROOT locale适配全球所有字符规则 BreakIterator graphemeBreaker = BreakIterator.getCharacterInstance(Locale.ROOT); graphemeBreaker.setText(input); int start = graphemeBreaker.first(); int end = graphemeBreaker.next(); while (end != BreakIterator.DONE) { String currentGrapheme = input.substring(start, end); // 过滤非打印字符:控制符、格式字符、单独代理对,可根据需求调整过滤规则 boolean isPrintable = currentGrapheme.codePoints().noneMatch(cp -> Character.isISOControl(cp) || Character.getType(cp) == Character.FORMAT || Character.getType(cp) == Character.CONTROL || Character.getType(cp) == Character.SURROGATE ); // 不需要跳过空白字符可删除 !currentGrapheme.isBlank() 判断 if (isPrintable && !currentGrapheme.isBlank()) { return currentGrapheme; } start = end; end = graphemeBreaker.next(); } // 全为非打印字符时返回空 return ""; } public static void main(String[] args) { // 测试示例 System.out.println(getFirstPrintableGrapheme("abcd")); // 输出a System.out.println(getFirstPrintableGrapheme("😀😀")); // 输出😀 System.out.println(getFirstPrintableGrapheme("☠😀☠")); // 输出☠ System.out.println(getFirstPrintableGrapheme("🇮🇳😀")); // 输出🇮🇳 System.out.println(getFirstPrintableGrapheme("🇮🇳abcd")); // 输出🇮🇳 // 验证length特性 System.out.println("😀".length()); // 输出2,对应UTF-16编码的2个char单元 System.out.println("😀".codePointCount(0, "😀".length())); // 输出1,对应1个Unicode码点 } }
原理解释
你观察到的"😀".length() == 2是因为Java的String#length()返回的是字符串在UTF-16编码下的char单元数量,而Emoji😀对应的Unicode码点是U+1F600,属于超出BMP平面的增补字符,需要用2个char(高代理+低代理)存储,所以返回2。如果要获取字符串的实际码点数量,需要调用codePointCount(0, str.length())方法。
内容的提问来源于stack exchange,提问作者Pankaj Singhal
相关产品推荐
相关产品推荐

