scanner用usedelimiter()设正则分隔符时,若正则匹配空字符串(如"s,s")会导致next()卡死;安全写法是确保至少匹配一个字符(如"s,s+"),或改用findinline()、skip()、string.split()等更可控方式。

Scanner用useDelimiter()设正则分隔符,但空字符串匹配会卡死
Java的Scanner默认用空白字符切分,想按自定义规则(比如连续多个逗号、带括号的分隔)就得用正则。但直接传useDelimiter("regex")容易踩坑——如果正则能匹配空字符串(比如"*"、"?"、"\s*"),next()可能无限阻塞或抛NoSuchElementException。
根本原因是Scanner内部在跳过分隔符时,若匹配到长度为0的串,就原地打转。实际场景中,常见于想“忽略任意空白+逗号混合分隔”却写了useDelimiter("\s*,\s*")——当开头/结尾/连续出现分隔符时,中间可能产生零宽匹配。
- 安全写法是确保正则至少匹配一个字符:
useDelimiter("\s*,\s*+")(注意末尾+)或更明确地写成"\s*,\s*+" - 调试时加一句
System.out.println(scanner.delimiter())确认正则是否如预期编译 - 如果要支持行首行尾的灵活空白,建议先
trim()输入再扫描,比在正则里反复试探更可靠
用findInLine()和skip()绕过next()的分词限制
Scanner的next()系列方法强依赖分隔符,一旦文本结构不规整(比如某字段含未转义的分隔符),就容易错位。这时别硬调useDelimiter(),改用findInLine()配合正则捕获,或skip()跳过固定模式更可控。
典型场景:解析日志行"[2024-01-01 10:23:45] INFO user=alice action=login",想抽user和action值。用next()切空格会把user=alice拆开;而findInLine("user=(\w+)")直接命中。
立即学习“Java免费学习笔记(深入)”;
-
findInLine()只在当前行找,返回String(匹配内容),失败返回null,不影响后续读取位置 -
skip("\s*INFO\s+")可跳过固定前缀,比设全局分隔符更轻量 - 注意
findInLine()内部用Matcher.find(),所以正则要避免贪婪过度(比如".*action=(\w+)"可能吞掉整行,改用"action=(\w+)"更稳
中文、emoji或特殊符号做分隔符时,正则要加Pattern.UNICODE_CHARACTER_CLASS
直接写useDelimiter(",|\p{Punct}")处理中文顿号或全角标点,很可能漏匹配——因为\p{Punct}默认不包含中文标点,且JDK 8之前Scanner构造正则时没开Unicode标志。
例如输入"苹果,香蕉,橙子",用useDelimiter(",")能切,但换成useDelimiter("\p{Punct}")就切不动,因为,不属于ASCII标点。
- 显式编译带标志的Pattern:
Pattern.compile(",|\p{Punct}", Pattern.UNICODE_CHARACTER_CLASS),再传给useDelimiter() - 更简单:直接用字面量枚举,比如
",|,|;|;|:|:",避免正则引擎差异 - emoji分隔(如
"?|?")同理,必须用Pattern.compile("?|?", Pattern.LITERAL)防止被当作转义序列
Scanner不是万能文本解析器,嵌套结构或变长字段优先考虑String.split()或StreamTokenizer
当文本含引号包裹字段("a,b","c,d",e)、括号嵌套(func(x, y, z))或不定长数组时,Scanner的单一分隔逻辑很快崩坏。它本质是流式token读取器,不是语法分析器。
比如解析CSV片段"name","age","city",用useDelimiter(",")会把引号内的逗号也切开;而"abc".split(",")配合String.strip()预处理更直白。
- 纯分隔符场景(无引号/转义):用
String.split()+Arrays.stream(),性能更高,行为确定 - 需逐行处理且有简单跳过逻辑:用
StreamTokenizer(它原生支持引号和注释,但API老旧) - 真要解析复杂格式(JSON、INI、自定义DSL),别硬撑——上
Jackson、Apache Commons CSV或手写状态机
最常被忽略的一点:Scanner的缓冲区默认8192字节,遇到超长行(比如单行几MB的日志)可能OOM或行为异常,而split()操作的是已加载的String,内存可见可控。











