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