for-comprehension, guard 和 RandomAccessFile.readLine

for-comprehension, guard and RandomAccessFile.readLine

考虑以下几点:

有一个包含一定行数的文本文件,例如:

test.txt: a b c d e f g h

(各占一行)

然后就是下面的class用于解析:

class MyAwesomeParser
{
    def parse(fileName: String, readLines: Int): IndexedSeq[String] =
    {
        val randomAccessFile = new RandomAccessFile(fileName, "r")

        val x: IndexedSeq[String] = for
        {
            x <- 0 until readLines
            r = randomAccessFile.readLine()
        } yield r

        x
    }
}

测试来了:

class MyAwesomeParserTest extends WordSpec
{
    "MyAwesomeParser" when {
    "read" should {
      "parse only specified number of lines" in {
        val parser = new EdgeParser("")
        val x = parser.parse("test.txt", 5)

        assert(x.size == 5)
      }
    }

    "MyAwesomeParser" when {
    "read" should {
      "parse only until end of file" in {
        val parser = new EdgeParser("")
        val x = parser.parse("test.txt", 10)

        assert(x.size == 8)
      }
    }
  }
}

第二个测试有问题。现在你当然会说,你这里少了一个守卫……好吧,好吧,如果我加上

x <- 0 until readLines if randomAccessFile.readLine != null

到实现然后它跳过几行,因为 readLine 已经消耗了该行。

  r = randomAccessFile.readLine
  x <- 0 until readLines if r != null

可悲的是不会工作,因为第一行必须是分配理解。

现在我想知道,是否有可能使用 for 理解循环直到给定次数或根据 readLine != null 条件停止?

我的语法有问题吗?

你可以把randomAccessFile.readLine封装在一个Option中,这样null就会变成Nonevalue就会变成Some(value)

另外,Option可以看作是一个集合,所以可以和IndexedSeq放在一起理解:

for {
  x <- 0 until readLines
  r <- Option(randomAccessFile.readLine())
} yield r

如果你想坚持你的 parse 方法,你可以只使用 getFilePointerlength

def parse(fileName: String, readLines: Int): IndexedSeq[String] =
{
    val randomAccessFile = new RandomAccessFile(fileName, "r")

    val x: IndexedSeq[String] = for
    {
        x <- 0 until readLines if randomAccessFile.getFilePointer < randomAccessFile.length
        r = randomAccessFile.readLine()
    } yield r

    x
}

但是,与其重新发明轮子,我建议你直接使用 scala.io.Source:

def parse(fileName: String, readLines: Int): Iterator[String] =
  Source.fromFile(fileName).getLines.take(readLines)