第12步 从文件读取文本行
那些执行小的日常任务的脚本通常需要处理文件中的文本行。在本节,你将构建一个脚本,从文件读取文本行,并将它们打印出来,在每一行前面带上当前行的字符数。脚本的第一版如示例3.10所示:
示例3.10 从文件读取文本行
这段脚本首先引入scala.io包的名为Source的类。然后检查是不是命令行至少给出了一个参数。如果是,第一个参数将被当作需要打开并处理的文件名。表达式Source.fromFile(args(0))尝试打开指定的文件并返回一个Source对象,在这个对象上,继续调用getLines方法。getLines方法返回一个Iterator[String],它每次迭代都给出一行内容,并去掉了最后的换行符。for表达式遍历这些文本行,对于每一行,都打印出它的长度、一个空格和这一行本身的内容。如果在命令行没有给出参数,那么最后的else子句将会向标准错误流(standard error stream)打印一段消息。如果将这段代码放在名为countchars1.scala的文件中并对该文件本身执行:
应该会看到:
尽管这段脚本,在当前的这个版本,已经能打印出需要的信息,可能还希望(右)对齐这些数字并加上一个管道符号(|),这样输出就可以是:
要做到这一点,可以对这些文本行遍历两次。第一次遍历,将决定每一行的字符数所需要的最大宽度。第二次遍历,将利用前一次遍历算出来的最大宽度,打印输出结果。由于要遍历两次,完全可以将文本行赋值给一个变量:
最后的toList是必需的,因为getLines方法返回的是一个迭代器(iterator)。一旦完成遍历,迭代器就会被消耗掉。通过toList将它转换成列表,就可以随便遍历这些文本行,多少次都可以,但相应的代价是需要在内存中同时存储所有行。因此,变量lines指向一个包含了命令行指定的文件内容的字符串列表。接下来,由于需要用到两次计算字符数的逻辑(每次迭代都会做一遍),可以将这个表达式抽取出为一个函数,专用来计算传入字符串的长度:
有了这个函数,就可以像这样计算最大宽度:
这里用一个for表达式来遍历每一行,计算该行的长度,如果比当前已知的最大值更大,则赋值给maxWidth,这个被初始化成0的var(max方法可以被用于任何Int,返回被调用的和被传入的两个Int值中更大的那一个)。或者,如果你更喜欢不用var来找出最大值,可以用如下代码找到最长的文本行:
reduceLeft方法将传入的函数应用到lines的头两个元素,然后继续将这个传入的函数应用到前一步得到的值和lines中的下一个元素,直到遍历完整个列表。在每一步,结果都是截止于当前最长的行,因为传入的函数(a, b) => if (a.length > b.length) a else b返回两个字符串中较长的那一个。“reduceLeft”将返回传入函数的最后一次执行的结果,在本例中就是lines所包含的元素中最长的那个字符串。
有了这个结果,就可以计算出需要的最大宽度,方法是将最长的行传入widthOfLength:
剩下的事情就是用正确的格式打印出这些行了。可以这样做:
在这个for表达式里,再次遍历这些行。对于每一行,首先计算出需要放在行长度之前的空格数,赋值给numSpaces。然后创建一个包含了数量为numSpaces的空格的字符串。最后,打印出按要求格式化好的信息。整个脚本如示例3.11所示:
示例3.11 打印格式化的文件文本行字数