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),然后它按预期工作。
我有以下 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),然后它按预期工作。