将java.util.StringTokenizer转为Scala Iterator的错误排查
将java.util.StringTokenizer转换为Scala Iterator的问题排查
我尝试把java.util.StringTokenizer转换成Scala的Iterator[String],第一种实现方法不符合预期:
def toIterator(st: StringTokenizer): Iterator[String] = Iterator.continually(st.nextToken()).takeWhile(_ => st.hasMoreTokens())
第二种方法则能得到正确结果:
def toIterator(st: StringTokenizer): Iterator[String] = Iterator.fill(st.countTokens())(st.nextToken())
Scala控制台测试结果如下:
scala> Iterator("a b", "c d").map(new java.util.StringTokenizer(_)).flatMap(st => Iterator.continually(st.nextToken()).takeWhile(_ => st.hasMoreTokens())).toList res1: List[String] = List(a, c) scala> Iterator("a b", "c d").map(new java.util.StringTokenizer(_)).flatMap(st => Iterator.fill(st.countTokens())(st.nextToken())).toList res2: List[String] = List(a, b, c, d)
错误原因分析
第一种实现的问题出在调用顺序:
Iterator.continually(st.nextToken())会先调用nextToken()生成元素,再通过takeWhile判断st.hasMoreTokens()是否为真来决定是否保留该元素。- 以包含两个元素的
StringTokenizer为例:- 第一次生成元素
"a",此时hasMoreTokens()返回true,"a"被保留; - 第二次生成元素
"b",此时hasMoreTokens()返回false(因为已经取完所有元素),"b"被过滤,迭代直接停止。
- 第一次生成元素
- 最终每个
StringTokenizer只会输出第一个元素,导致结果缺失。
性能更优的正确实现
如果想要避免countTokens()带来的两次扫描,同时保证结果正确,可以直接实现符合Scala迭代器规范的自定义Iterator:
def toIterator(st: StringTokenizer): Iterator[String] = new Iterator[String] { override def hasNext: Boolean = st.hasMoreTokens() override def next(): String = st.nextToken() }
或者使用Scala 2.13+的Iterator.unfold(更简洁的函数式写法):
def toIterator(st: StringTokenizer): Iterator[String] = Iterator.unfold(st) { st => if (st.hasMoreTokens()) Some(st.nextToken(), st) else None }
这两种实现都会先判断hasNext()(对应st.hasMoreTokens()),再调用next()获取元素,完全符合迭代器的工作逻辑,既不会漏掉元素,也无需提前扫描字符串统计token数量。
内容的提问来源于stack exchange,提问作者pathikrit
相关产品推荐
相关产品推荐

