Scala文件操作
1. 读取数据
在Scala语言的 Source单例对象
中, 提供了一些非常便捷的方法, 从而使开发者可以快速的从指定数据源(文本文件, URL地址等)中获取数据, 在使用 Source单例对象
之前, 需要先导包, 即 import scala.io.Source.
1.1 按行读取
我们可以以 行
为单位, 来读取数据源中的数据, 返回值是一个 迭代器类型的对象
. 然后通过 toArray, toList
方法, 将这些数据放到数组或者列表中即可.
注意: Source类扩展自Iterator[Char]
格式
//1. 获取数据源文件对象.
val source:BufferedSource = Source.fromFile("数据源文件的路径","编码表")
//2. 以行为单位读取数据.
val lines:Iterator[String] = source.getLines()
//3. 将读取到的数据封装到列表中.
val list1:List[String] = lines.toList
//4. 千万别忘记关闭Source对象.
source.close()
需求
- 在当前项目下创建data文件夹, 并在其中创建1.txt文本文件, 文件内容如下:
好好学习, 天天向上!
Hadoop, Zookeeper, Flume, Spark
Flink, Sqoop, HBase
- 以行为单位读取该文本文件中的数据, 并打印结果.
参考代码
import scala.io.Source
object ClassDemo01 {
def main(args: Array[String]): Unit = {
//1. 获取数据源对象.
val source = Source.fromFile("./data/1.txt")
//2.通过getLines()方法, 逐行获取文件中的数据.
var lines: Iterator[String] = source.getLines()
//3. 将获取到的每一条数据都封装到列表中.
val list1 = lines.toList
//4. 打印结果
for(s <- list1) println(s)
//5. 记得关闭source对象.
source.close()
}
}
1.2 按字符读取
Scala还提供了 以字符为单位读取数据
这种方式, 这种用法类似于迭代器, 读取数据之后, 我们可以通过 hasNext(),next()方法
, 灵活的获取数据.
Scala使用source.buffered方法按字符读取文件
什么是source.buffered方法
source.buffered方法是scala.io.Source类的一个成员方法,它可以将一个Source对象转换为一个BufferedSource对象。BufferedSource对象是一个实现了BufferedIterator特质的对象,它可以提供一个缓冲区,用来存储未被消费的元素。这样可以提高读取文件的效率,也可以方便地查看下一个元素而不消费它。
如何使用source.buffered方法
要使用source.buffered方法,首先需要导入scala.io.Source类,并创建一个Source对象,然后调用其buffered方法,得到一个BufferedSource对象。例如,以下代码创建了一个从文件中获取的Source对象,并转换为一个BufferedSource对象:
import scala.io.Source
// 创建一个从文件中获取的Source对象
val source = Source.fromFile("demo.txt")
// 调用buffered方法,得到一个BufferedSource对象
val buffered = source.buffered
然后,可以使用BufferedSource对象的各种方法来按字符读取文件。例如,以下代码使用head方法来查看下一个字符,使用next方法来消费下一个字符,并打印出来:
// 使用head方法查看下一个字符
println(buffered.head)
// 使用next方法消费下一个字符
println(buffered.next())
// 继续使用head方法查看下一个字符
println(buffered.head)
// 继续使用next方法消费下一个字符
println(buffered.next())
最后,需要关闭Source对象,释放资源:
// 关闭Source对象
source.close()
一个示例
下面给出一个完整的示例,演示如何使用source.buffered方法来按字符读取文件,并打印出来。
首先,准备一个文本文件demo.txt,内容如下:
Hello, Scala!
你好,Scala!
然后,编写以下代码:
import scala.io.Source
// 创建一个从文件中获取的Source对象,并转换为一个BufferedSource对象
val buffered = Source.fromFile("demo.txt").buffered
// 循环判断是否有下一个元素
while (buffered.hasNext) {
// 打印出当前元素,并消费掉
print(buffered.next())
}
// 关闭Source对象
buffered.close()
最后,运行程序,可以看到输出结果如下:
Hello, Scala!
你好,Scala!
1.3 读取词法单元和数字
所谓的词法单元指的是 以特定符号间隔开的字符串
, 如果数据源文件中的数据都是 数字形式的字符串
, 我们可以很方便的从文件中直接获取这些数据, 例如:
10 2 5
11 2
5 1 3 2
格式
//1. 获取数据源文件对象.
val source:BufferedSource = Source.fromFile("数据源文件的路径","编码表")
//2. 读取词法单元.
// \s表示空白字符(空格, \t, \r, \n等)
val arr:Array[String] = source.mkString.split("\\s+")
//3. 将字符串转成对应的整数
val num = strNumber.map(_.toInt)
//4. 千万别忘记关闭Source对象.
source.close()
参考代码
将上面的数字存入2.txt
import scala.io.Source
object ClassDemo03 {
def main(args: Array[String]): Unit = {
val source = Source.fromFile("./data/2.txt")
// \s表示空白字符(空格, \t, \r, \n等)
val strNumber = source.mkString.split("\\s+")
//将字符串转成对应的整数
val num = strNumber.map(_.toInt)
for(a <- num) println(a + 1)
}
}
1.4 从URL或者其他源读取数据
Scala中提供了一种方式, 可以让我们直接从指定的URL路径, 或者其他源(例如: 特定的字符串)中直接读取数据。
格式
- 从URL地址中读取数据
//1. 获取数据源文件对象.
val source = Source.fromURL("https://www.csdn.net")
//2. 将数据封装到字符串中并打印.
println(source.mkString)
- 从其他源读取数据
//1. 获取数据源文件对象.
val str = Source.fromString("CSDN")
println(str.getLines())
需求
- 读取
https://www.csdn.net
页面的数据, 并打印结果. - 直接读取字符串
CSDN
, 并打印结果.
参考代码
import scala.io.Source
object ClassDemo04 {
def main(args: Array[String]): Unit = {
val source = Source.fromURL("https://www.csdn.net")
println(source.mkString)
val str = Source.fromString("CSDN")
println(str.getLines())
}
}
1.5 读取二进制文件
Scala没有提供读取二进制文件的方法, 我们需要通过Java类库来实现.
需求
已知项目的data文件夹下有 05.png
这张图片, 请读取该图片数据, 并将读取到的字节数打印到控制台上.
参考代码
object ClassDemo05 {
def main(args: Array[String]): Unit = {
val file = new File("./data/04.png")
val fis = new FileInputStream(file)
val bys = new Array[Byte](file.length().toInt)
fis.read(bys)
fis.close()
println(bys.length)
}
}
2. 写入文件
2.1 使用java.io.PrintWriter类
java.io.PrintWriter类是Java提供的一个用于写入文本文件的类。它可以接受一个文件、一个输出流或一个字符串作为参数,然后通过调用其write方法来向文件中写入数据。例如,以下代码可以向文件hello.txt中写入一行文本:
import java.io._
val pw = new PrintWriter(new File("hello.txt"))
pw.write("Hello, world")
pw.close()
使用PrintWriter类写入文件时,有两个需要注意的问题:
- PrintWriter类不会抛出异常,而是设置一个布尔标志来表示是否发生了错误。可以通过调用其checkError方法来检查是否有错误发生。
- PrintWriter类默认使用平台的字符编码,这可能导致不同平台之间的不一致。可以通过指定字符编码来避免这个问题。例如,以下代码使用了UTF-8编码:
val pw = new PrintWriter(new File("hello.txt"), "UTF-8")
2.2 使用java.io.FileWriter类
java.io.FileWriter类是Java提供的另一个用于写入文本文件的类。它是OutputStreamWriter的子类,可以接受一个文件或一个字符串作为参数,然后通过调用其write方法来向文件中写入数据。例如,以下代码可以向文件test.txt中写入一行文本:
import java.io._
val file = new File("test.txt")
val bw = new BufferedWriter(new FileWriter(file))
bw.write("We are learning Scala programming language")
bw.close()
使用FileWriter类写入文件时,有两个需要注意的问题:
- FileWriter类会抛出IOExceptions异常,所以需要使用try-catch-finally语句来处理异常和关闭文件。
- FileWriter类默认使用平台的字符编码,这可能导致不同平台之间的不一致。FileWriter类没有提供指定字符编码的构造器,所以如果需要指定字符编码,可以使用OutputStreamWriter类代替。例如,以下代码使用了UTF-8编码:
val file = new File("test.txt")
val bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF-8"))
bw.write("We are learning Scala programming language")
bw.close()
2.3 使用java.io.FileOutputStream类
FileOutputStream是Java提供的一个用于写入二进制文件的类,它是OutputStream的子类,可以接受一个文件或一个字符串作为参数,然后通过调用其write方法来向文件中写入字节数据。例如,以下代码可以向文件data.bin中写入一个整数:
import java.io._
val file = new File("data.bin")
val fos = new FileOutputStream(file)
fos.write(42)
fos.close()
FileOutputStream类会抛出IOExceptions异常,所以需要使用try-catch-finally语句来处理异常和关闭文件。
已收到消息. FileOutputStream是Java提供的一个用于写入二进制文件的类,它是OutputStream的子类,可以接受一个文件或一个字符串作为参数,然后通过调用其write方法来向文件中写入字节数据。例如,以下代码可以向文件data.bin中写入一个整数: scala import java.io._ val file = new File("data.bin") val fos = new FileOutputStream(file) fos.write(42) fos.close()
FileOutputStream类会抛出IOExceptions异常,所以需要使用try-catch-finally语句来处理异常和关闭文件。
2.4 几种写入的区别
因为FileOutputStream类有两种构造器,一种接受一个文件对象作为参数,一种接受一个字符串作为参数。如果接受一个字符串作为参数,那么它会自动创建一个文件对象。所以,以下两种写法是等价的:
val pw = new FileOutputStream(new File("./data/3.txt"))
val pw = new FileOutputStream("./data/3.txt")
但是,如果使用PrintWriter类或FileWriter类,那么就必须使用一个文件对象作为参数,不能使用一个字符串。所以,以下两种写法是不等价的:
val pw = new PrintWriter(new File("./data/3.txt"))
val pw = new PrintWriter("./data/3.txt") // 错误
2.5 使用第三方库
除了Java提供的类之外,还有一些第三方库可以用来写入文件,例如Apache Commons IO1和os-lib2等。这些库通常提供了更简洁和高效的API,也支持更多的功能和格式。例如,以下代码使用了Apache Commons IO库的FileUtils类来写入文件,并指定了字符编码:
import org.apache.commons.io.FileUtils
FileUtils.writeStringToFile(new File("test.txt"), "Hello, world", "UTF-8")
以下代码使用了os-lib库的os.File对象来写入文件,并返回一个字节长度:
import os._
os.write(os.pwd / "test.txt", "Hello, world")
3. Scala序列化和反序列化
3.1 什么是序列化和反序列化
在Scala中,如果想将对象传输到其他虚拟机,或者临时存储,就可以通过序列化和反序列化来实现了。
- 序列化:把对象写到文件中的过程。
- 反序列化:从文件中加载对象的过程。
3.2 如何实现序列化和反序列化
一个类的对象要想实现序列化和反序列化操作,则该类必须继承Serializable特质。Serializable特质是一个空特质,它没有任何方法和字段,只是用来标记一个类是否可以被序列化和反序列化。
要实现序列化操作,可以使用java.io.ObjectOutputStream类,它是一个用于写入对象的输出流。它可以接受一个输出流作为参数,然后通过调用其writeObject方法来写入对象。
要实现反序列化操作,可以使用java.io.ObjectInputStream类,它是一个用于读取对象的输入流。它可以接受一个输入流作为参数,然后通过调用其readObject方法来读取对象。
3.3 一个示例
下面给出一个简单的示例,演示如何使用case class和object来实现序列化和反序列化操作。
首先,定义一个case class Person,属性为姓名和年龄。注意,case class会自动继承Serializable特质,所以不需要显式地写出来。
// 定义一个case class Person
case class Person(var name: String, var age: Int)
然后,创建Person样例类的对象p,并通过序列化操作将对象p写入到项目下的data文件夹下的4.txt文本文件中。
// 创建Person样例类的对象p
val p = Person("张三", 23)
// 创建一个ObjectOutputStream对象,传入一个输出流作为参数
val oos = new ObjectOutputStream(new FileOutputStream("./data/4.txt"))
// 调用writeObject方法,将对象p写入到输出流中
oos.writeObject(p)
// 关闭输出流
oos.close()
最后,通过反序列化操作从项目下的data文件夹下的4.txt文件中,读取对象p,并打印出来。
// 创建一个ObjectInputStream对象,传入一个输入流作为参数
val ois = new ObjectInputStream(new FileInputStream("./data/4.txt"))
// 调用readObject方法,从输入流中读取对象,并转换为Person类型
var p: Person = ois.readObject().asInstanceOf[Person]
// 打印对象p
println(p)
// 关闭输入流
ois.close()
3.4 使用注解
除了使用Serializable特质之外,还可以使用@SerialVersionUID注解来指定一个版本号。这样可以避免不同版本的类之间的兼容性问题。例如,以下代码定义了一个普通的类,并添加了注解:
import java.io._
// 使用@SerialVersionUID注解指定版本号为100L
@SerialVersionUID(100L)
// 定义一个普通的类,并继承Serializable特质
class Person(var name: String, var age: Int) extends Serializable
然后,可以使用相同的方式进行序列化和反序列化操作:
val p = new Person("张三", 23)
val oos = new ObjectOutputStream(new FileOutputStream("./data/4.txt"))
oos.writeObject(p)
oos.close()
val ois = new ObjectInputStream(new FileInputStream("./data/4.txt"))
var p: Person = ois.readObject().asInstanceOf[Person]
println(p)
案例: 学员成绩表
概述
- 已知项目下的data文件夹的student.txt文本文件中, 记录了一些学员的成绩, 如下:
格式为: 姓名 语文成绩 数学成绩 英语成绩
张三 37 90 100
李四 90 73 81
王五 60 90 76
赵六 89 21 72
田七 100 100 100
- 按照学员的总成绩降序排列后, 按照 姓名 语文成绩 数学成绩 英语成绩 总成绩 的格式, 将数据写到项目下的
data文件夹的stu.txt文件中.
** 步骤**
- 定义样例类Person, 属性为: 姓名, 语文成绩, 数学成绩, 英语成绩, 且该类中有一个获取总成绩的方法.
- 读取指定文件(./data/student.txt)中所有的数据, 并将其封装到List列表中.
- 定义可变的列表ListBuffer[Student], 用来记录所有学生的信息.
- 遍历第二步获取到的数据, 将其封装成Person类的对象后, 并添加到ListBuffer中.
- 对第4步获取到的数据进行排序操作, 并将其转换成List列表.
- 按照指定格式, 通过BufferWriter将排序后的数据写入到目的地文件中(./data/stu.txt)
- 关闭流对象.
参考代码
object ClassDemo08 {
//1. 定义样例类Person, 属性: 姓名, 语文成绩, 数学成绩, 英语成绩, 且该类中有一个获取总成绩的方法.
case class Student(name:String, chinese:Int, math:Int, english:Int) {
def getSum() = chinese + math + english
}
def main(args: Array[String]): Unit = {
//2. 获取数据源文件对象.
val source = Source.fromFile("./data/student.txt")
//3. 读取指定文件(./data/student.txt)中所有的数据, 并将其封装到List列表中.
var studentList: Iterator[List[String]] = source.getLines().map(_.split(" ")).map(_.toList)
//4. 定义可变的列表ListBuffer[Student], 用来记录所有学生的信息.
val list = new ListBuffer[Student]()
//5. 遍历第二步获取到的数据, 将其封装成Person类的对象后, 并添加到ListBuffer中.
for(s <- studentList) {list += Student(s(0), s(1).toInt, s(2).toInt, s(3).toInt)}
//6. 对第5步获取到的数据进行排序操作, 并将其转换成List列表.
val sortList = list.sortBy(_.getSum()).reverse.toList
//7. 按照指定格式, 通过BufferWriter将排序后的数据写入到目的地文件(./data/stu.txt)
val bw = new BufferedWriter(new FileWriter("./data/stu.txt"))
for(s <- sortList) bw.write(s"${s.name} ${s.chinese} ${s.math} ${s.english}${s.getSum()}\r\n")
//8. 关闭流对象
bw.close()
}
}