scala.io.Source.fromInputStream 不会进一步抛出 MalformedInputException

scala.io.Source.fromInputStream does not throw the MalformedInputException further

我有以下 scala 代码,它接受一个字符串,弄乱了 UTF-8 字符,然后尝试通过 Source.fromInputStream 读取它:

import java.io.ByteArrayInputStream
import java.nio.charset.StandardCharsets.UTF_8

import scala.io.Source

val stringSourceAsBytes = "hellö wörld".getBytes(UTF_8)

val messedUpUTF8 = 128.toByte +: stringSourceAsBytes

val linesIterator : Iterator[String] =
try {
  val input = new ByteArrayInputStream(messedUpUTF8)
  Source.fromInputStream(input).getLines()
}catch{
  case exc: Throwable => println(" This is an exception !")
  Iterator()
}

linesIterator.mkString("\n")

我不应该看到 "This is an exception !" 消息吗?因为我看不到它。 事实上,我得到了一个打印的堆栈跟踪,但我无法捕获异常并正确处理它......

顺便说一句:我的控制台显示:

java.nio.charset.MalformedInputException: Input length = 1
    at java.nio.charset.CoderResult.throwException(IO.sc:277)
   at sun.nio.cs.StreamDecoder.implRead(IO.sc:335)
   at sun.nio.cs.StreamDecoder.read(IO.sc:174)
   at java.io.InputStreamReader.read(IO.sc:181)
   at java.io.BufferedReader.fill(IO.sc:157)
   at java.io.BufferedReader.readLine(IO.sc:322)
   at java.io.BufferedReader.readLine(IO.sc:388)
   at scala.io.BufferedSource$BufferedLineIterator.hasNext(IO.sc:66)
   at scala.collection.Iterator.toString(IO.sc:1409)
   at scala.collection.Iterator.toString$(IO.sc:1409)
   at scala.collection.AbstractIterator.toString(IO.sc:1413)
   at #worksheet#.#worksheet#(IO.sc:53)

这里同时发生了两件有趣的事情:

  • 惰性迭代器和 try-catch-块的经典错误
  • REPL 的奇怪行为会在试图对用户友好时自爆。

您看不到 "This is an exception !" 消息,因为实例化惰性迭代器不会尝试从流中读取单个字节。这个 try-catch 块成功并且愉快地 returns 滴答作响 time-bomb,实际错误稍后发生,在 try-catch.

之外

但是,如果您强制迭代器获取所有字节,例如通过附加 .mkString:

import java.io.ByteArrayInputStream
import java.nio.charset.StandardCharsets.UTF_8

import scala.io.Source

val stringSourceAsBytes = "hellö wörld".getBytes(UTF_8)
val messedUpUTF8 = 128.toByte +: stringSourceAsBytes

val streamContent =
try {
  val input = new ByteArrayInputStream(messedUpUTF8)
  Source.fromInputStream(input).getLines().mkString("\n")
}catch{
  case exc: Throwable => println(" This is an exception !")
}

然后你得到输出:

 This is an exception !

符合预期。您的堆栈跟踪似乎来自其他地方,请再次检查确切的行号。


问题编辑后更新

要在更新的代码中看到 " This is an exception !" 消息,您必须在抛出异常的位置捕获,而不是在定义惰性迭代器的位置捕获:

import java.io.ByteArrayInputStream 
import java.nio.charset.StandardCharsets.UTF_8 
import scala.io.Source 

val stringSourceAsBytes = "hellö wörld".getBytes(UTF_8) 
val messedUpUTF8 = 128.toByte +: stringSourceAsBytes

// building the exception-bomb is harmless
val linesIterator: Iterator[String] = { 
  val input = new ByteArrayInputStream(messedUpUTF8) 
  Source.fromInputStream(input).getLines() 
} 


val combinedLines: String = try {
  // detonating the exception-bomb should be surrounded by try-catch
  linesIterator.mkString("\n")
} catch { 
  case exc: Throwable => {
    println(" This is an exception !") 
    ""
  }
} 

这将再次打印 This is an exception ! 并将 combinedLines 设置为空字符串。


EDIT-2:REPL

如果你出于某种原因坚持在 repl 中 运行 它,那么你不能让 "poisoned" 迭代器逃逸到范围内,因为 Repl 出于某种原因无法处理它,并自爆向上。

这在repl中有效,但这与第一个解决方案基本相同:

import java.io.ByteArrayInputStream 
import java.nio.charset.StandardCharsets.UTF_8 
import scala.io.Source 

val stringSourceAsBytes = "hellö wörld".getBytes(UTF_8) 
val messedUpUTF8 = 128.toByte +: stringSourceAsBytes 

val combinedLines: String = try {
  val linesIterator: Iterator[String] = { 
    val input = new ByteArrayInputStream(messedUpUTF8) 
    Source.fromInputStream(input).getLines() 
  } 
  linesIterator.mkString("\n")
} catch { 
  case exc: Throwable => {
    println(" This is an exception !") 
    ""
  }
}

REPL 无法处理即将抛出异常的迭代器。原因是它在迭代器上调用了 hasNext(它打印有效迭代器的 non-empty iterator 描述,所以它必须调用一次 hasNext)。但是当调用 hasNext 时,您的流会抛出异常,如以下代码片段所示:

scala> val it = try { 
  Source.fromInputStream(
    new ByteArrayInputStream(messedUpUTF8)).getLines().hasNext 
} catch { case t: Throwable => 
  println("yes, hasNext blows up the REPL")  
}

结果:

yes, hasNext blows up the REPL

运行 作为脚本(或者尝试 paste-mode),然后它按预期工作。