第18章 Stream(流)类
1. 流Stream的概念和分类
一个流(Stream)可以理解为一个数据的序列,是从某种介质流向其他介质的数据序列,比如从程序(内存)保存数据至硬盘,从硬盘读取数据至程序(内存)。从程序向网络发送消息,从网络接收消息至程序等。
- 按照流向分为输入流和输出流
(1)输入流:从其他介质进入程序(内存)
(2)输出流:从程序(内存)离开进入其他介质中 - 按照处理单位分为字节流和字符流
(1)字节流:处理单位为字节(byte),主要用于处理二进制数据。比如,计算机上的所有文件中的内容基本都可以看做是二进制数据。
(2)字符流:处理单位为字符(char),主要用于处理文本数据。比如计算机上的txt文档中的内容可以看做是文本数据。 - 按照功能分为原始流和处理流
(1)原始流:提供基础功能的流
(2)处理流:提供对基础功能进行功能加强的流
按照流向和单位,流的父类如下
输入 | 输出 | |
---|---|---|
字节 | InputStream | OutputStream |
字符 | Reader | Writer |
这些流的子类以上述的名字作为类名后缀。
比如:
- FileInputStream: 关于文件操作的字节输入流
- FileInputOutputStream: 关于文件操作的字节输出流
- FileReader: 关于文件操作的字符输入流
- FileWriter: 关于文件操作的字符输出流
原始流在使用后要进行关闭
2. 字节流(文件流为例)
以下是代码的功能是复制c:/1.jpg至e:/1.jpg。复制的过程仅仅操作内容复制,无需更改内容,直接使用字节流完成。
可以理解为:
(1)将c:/1.jpg的数据输入至内存
(2)将输入至内存的数据输出至e:/1.jpg
public class Test1 {
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("c:/1.jpg");// 创建输入流读取源文件内容
fos = new FileOutputStream("e://1.jpg");// 创建输出流写出文件内容
byte[] b = new byte[2048]; //2KB区域,一次读取源文件中的2KB数据
// 从源文件中读取2KB数据至b数组中
int len = fis.read(b);
/* len表示的是在源文件中读取字节长度
len是-1表示读取结束。最后一次读取不一定读了2KB数据,
需要用len确定读了多少数据,写出时以免写出冗余
*/
while(len != -1) {
//将读取到b数组中的数据写出至目标文件
//第二个参数表示偏移量,从数组的下标几开始输出
//第三个参数表示输出字节个数
fos.write(b, 0, len);
fos.flush(); //将数据真正刷出至目标文件中
//再次读入下一组数据至b中
len = fis.read(b);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流
try {
if(fis != null) {
fis.close();
}
if(fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3. 字符流(文件流为例)
读取c:/1.txt文档中的内容,将其中所有的字符'哈'替换为'*',并输出至e:/1.txt中。其原理与文件复制一致,不过操作涉及对内容的修改,需要在内存中完成对字符的替换,所以使用字符流进行操作
public class Test2 {
public static void main(String[] args) {
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader("D://1.txt");
fw = new FileWriter("E://1.txt");
char[] c = new char[1024]; //一次读取1024个字符,字符流一次处理一个char,所以用char数组不使用byte数组
int len = fr.read(c); //将文本文件中的文字读入的c数组中
while(len != -1) {
String temp = new String(c); //利用字符数组c构造成一个字符串
temp = temp.replaceAll("哈", "*"); //将字符串中的“哈”替换为“*”
c = temp.toCharArray();//将替换后的字符串变回字符数组
fw.write(c, 0, len);//将替换后的字符数组写出至目标位置
fw.flush();
len = fr.read(c); //继续读取下一组字符
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(fr != null) {
fr.close();
}
if(fw != null) {
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
4. 处理流(缓冲流为例)
处理流本质上不提供任何功能,它提供了对原始流功能的加强。
以字符缓冲输入流为例,原始的字符输入流只能以字符数组 (char[])读取数据,如果现在提出要读取txt文件中整行数据,原始的字符输入流没有相应的方法完成这个功能,但利用缓冲流提供的方法,可以容易的达到目的。
示例:以行(line)为单位,操作txt文档的复制
public class Test1 {
public static void main(String[] args) {
FileReader fr = null; //原始的字符输入流
BufferedReader br = null; //缓冲字符输入流
FileWriter fw = null; //原始的字符输出流
BufferedWriter bw = null; //缓冲字符输出流
try {
fr = new FileReader("d://1.txt");
br = new BufferedReader(fr);
fw = new FileWriter("d://2.txt");
bw = new BufferedWriter(fw);
String line = br.readLine(); //读取源文件中的一行文本
while(line != null) { //如果不为null则认为读取到了文本
System.out.println(line); //在控制台上显示文本
bw.write(line); //在目标文件中写该行文本
bw.newLine(); //目标文件新起一行
bw.flush(); //刷入目标文件
line = br.readLine(); //读取源文件的下一行文本
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(fr != null) {
fr.close();
}
if(fw != null) {
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
5. 序列化
序列化是将对象以二进制数据的形式转储。比如将系统中的二进制数据暂时保存。
5.1 可被序列化的类
一个类如果可以被序列化,它需要实现java.io.Serializable接口,并生成一个常量值的序列码,这个序列码可以理解为是某个类的身份证号。
public class Student implements Serializable{
private static final long serialVersionUID = 4188262972381648549L;
private int sno;
private String sname;
private Date birthday;
public Student() {}
public Student(int sno, String sname, Date birthday) {
this.sno = sno;
this.sname = sname;
this.birthday = birthday;
}
public int getSno() {
return sno;
}
public void setSno(int sno) {
this.sno = sno;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
如果类中的某个属性需要是其他类的对象并且也需要序列化,那么这个所谓的“其他类”也需要实现java.io.Serializable接口。比如上面这个类中的Date birthday属性,java.util.Date类也实现了java.io.Serializable接口,系统中大部分的常用类都实现了java.io.Serializable接口,比如包装类,集合类等。
5.2 序列化
将对象从内存序列化至其他介质,本章示例将其序列化至c:/1.abc文件中
public class Test1 {
/**
* 序列化操作 将对象从内存转入其他介质中
* @param args
*/
public static void main(String[] args) {
Student s1 = new Student(101, "赵四", new Date());
Student s2 = new Student(102, "哈哈", new Date());
//序列化操作: 将s1对象保存在c:/1.abc文件中
FileOutputStream fos = null;
ObjectOutputStream oos = null;
try {
fos = new FileOutputStream("c:/1.abc");
oos = new ObjectOutputStream(fos);//典型的处理流 操作oos相当于操作了fos
oos.writeObject(s1); //将s1对象写入到fos对应的文件中
oos.writeObject(s2); //将s2对象写入到fos对应的文件中
oos.flush();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
if(fos != null) {
fos.close();//关闭原始流即可
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
5.3 反序列化
将对象从文件中读取回内存
public class Test2 {
/**
* 反序列化:将对象从其他介质转入到内存中
* @param args
*/
public static void main(String[] args) {
FileInputStream fis = null;
ObjectInputStream ois = null;
try {
fis = new FileInputStream("c:/1.abc");
ois = new ObjectInputStream(fis);
Student s1 = (Student)ois.readObject();
Student s2 = (Student)ois.readObject();
System.out.println(s1.getSno()+"\t"+s1.getSname()+"\t"+s1.getBirthday());
System.out.println(s2.getSno()+"\t"+s2.getSname()+"\t"+s2.getBirthday());
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
if(fis != null) {
fis.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
5.4 transient关键字
该关键字用于修饰属性,被这个关键字修饰的属性不参与序列化和反序列化。比如
将Student类的birthday属性加上transient关键字修饰
public class Student implements Serializable{
private static final long serialVersionUID = 4188262972381648549L;
private int sno;
private String sname;
private transient Date birthday;
public Student() {}
public Student(int sno, String sname, Date birthday) {
this.sno = sno;
this.sname = sname;
this.birthday = birthday;
}
public int getSno() {
return sno;
}
public void setSno(int sno) {
this.sno = sno;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
再次进行测验会发现序列化后,反序列化回来的数据中birthday都是null。相当于birthday没有参与序列化过程。