一、 流的概述
1、什么是流
流根据方向不同分为输入流与输出流,参照点为当前程序。输入流用来读取数据,输出流用来写数据。
2、流的分类(2类)
- 节点流(也叫低级流)
是真实负责读写数据的流,读写操作中必须要有低级流。数据源明确。 - 处理流(也叫高级流)
读写可以没有高级流,高级流也不能独立存在,必须用于处理其他流,处理其他流的目的是简化读写数据中的操作。
3、低级流和高级流的关系
低级流可以类比为水管,而高级流则可以类比为加在水管上的热水器和净水器。所以会得出以下的结论:
- 低级流可以单独存在;而高级流必须依赖于低级流而存在。因为低级流是真实读写数据的流,高级流都是处理数据的。
- 高级流是用于简化数据读写的,所以我们用高级流的时候,需要想一下,这个高级流有什么样的功能。
- 高级流处理其他流就形成了流的连接。并且有效的组合不同的高级流可以得到叠加的效果。
- 有高级流的时候,直接关闭高级流就可以了,它会自动把所依赖的低级流关闭(就好像你只需要关闭热水器的开关就可以了)。
- 下面会分别介绍几个低级流和高级流
4、两个抽象类
InputStream
抽象类,是所有字节输入流的父类,定义了输入流的读取字节的方法,常用的方法如下:int read()
读取 一个字节,以int形式返回,该int值得“低八位”有效,若返回值为-1,则表示EOFint read(byte[])
尝试最多读取给定数组的length个字节并存入该数组,返回值为实际读取到的字节量
OutputStream
抽象类,是所有字节输出流的父类,定义了输出流的写入字节的方法,常用的方法如下:void write(int d)
写出一个字节,写的是给定的int的“低八位”void write(byte[] d)
将给定的字节数组中的所有字节全部写出
二、低级流
1、文件流
FileInputStream
文件的字节输入流,是一个低级流,该流可以以字节为单位从文件中读取数据。构造方法如下:FileInputStream(File file)
创建一个从指定File对象表示的文件中读取数据的文件输入流FileInputStream(String name)
创建用于读取给定的文件系统中的路径名name所指定的文件的文件输入流
FileOutputStream
文件的字节输出流,是一个低级流,该流可以以字节为单位将数据写入文件中。构造方法如下:FileOutputStream(File file)
创建一个向指定File对象表示的文件中写出数据的文件输出流。(如果指定的文件已经包含内容,会将原有数据全部清除)FileOutputStream(String name)
创建一个向具有指定名称的文件中写出数据的文件输出流。(如果指定的文件已经包含内容,会将原有数据全部清除)FileOutputStream(File file, boolean append)
创建一个向指定File对象表示的文件中写出数据的文件输出流。(如果第二个参数为true,则不会清除原数据,而是在文件末尾追加)FileOutputStream(String name, boolean append)
创建一个向具有指定名称的文件中写出数据的文件输出流。(如果第二个参数为true,则不会清除原数据,而是在文件末尾追加)
基本操作:
int read()
读取 一个字节,以int形式返回,该int值得“低八位”有效,若返回值为-1,则表示EOF
int read(byte[] b)
尝试最多读取给定数组的length个字节并存入该数组,返回值为实际读取到的字节量
void write(int d)
写出一个字节,写的是给定的int的“低八位”
void write(byte[] d)
将给定的字节数组中的所有字节全部写出
void write(byte[] d, int offset, int len)
将指定byte数组中从偏移量offset开始的len个字节写入此文件输出流
示例代码:
输入流
public class FISDemo {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("fis.txt");
byte[] data = new byte[100];
int len = fis.read(data);
//String str = new String(data, "UTF-8").trim();
String str = new String(data, 0, len, "UTF-8");
System.out.println(str);
fis.close();
}
}
输出流
public class FOSDemo {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("fos.txt");
String str = "我爱java";
/*
* String -> byte[]
* byte getBytes()
* 将当前字符串按照系统默认字符集转换为一组字节
*
* byte getBytes(String csn)
* 按照给定的字符集将当前字符串转换为一组字节
*/
byte[] data = str.getBytes("UTF-8");
fos.write(data);
fos.close();
System.out.println("写出完毕");
}
}
复制文件
public class CopyDemo1 {
public static void main(String[] args) throws IOException {
/*
* 使用文件输入流读取原文件,再使用文件输出流向目标文件中写。
* 顺序从原文件中读取每个字节并写入到目标文件即可完成复制
*/
FileInputStream src = new FileInputStream("1.jpg");
FileOutputStream desc = new FileOutputStream("1_cp_cp.jpg");
byte[] buf = new byte[1024*10];
int len = -1;
while((len=src.read(buf))!=-1){
desc.write(buf, 0, len);
}
System.out.println("复制完毕");
src.close();
desc.close();
}
三、高级流
1、缓冲流
- BufferedInputStream
在读取数据时,若以字节为单位读取数据,会导致读取次数过于频繁,从而大大的降低读取效率。为此我们可以通过提高一次读取的字节数量减少读写次数来提高读取的效率。(不用再自己去维护一个buffer,像上述代码中的那样)。
BufferedInputStream是缓冲字节输入流。其内部维护着一个缓冲区(字节数组,默认为8192个字节),使用该流在读取一个字节时,该流会尽可能多的一次性读取若干字节并存入缓冲区,然后逐一的将字节返回,直到缓冲区中的数据被全部读取完毕,会再次读取若干字节从而反复。这样就减少了读取的次数,从而提高了读取效率。
示例代码:
public class CopyDemo2 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("1.ts");
BufferedInputStream bis = new BufferedInputStream(fis);
FileOutputStream fos = new FileOutputStream("1_cp_cp.ts");
BufferedOutputStream bos = new BufferedOutputStream(fos);
int d = -1;
/*
* 缓冲流内部有一个缓冲区
* 当bis.read方法读取第一个字节时,
* 实际上BIS会一次性读取一组字节 并存入内部的字节数组中,
* 然后将第一个字节返回,当再次调用read方法时,BIS直接
* 从字节数组中将第二个字节返回,直到字节数组中所有字节全部
* 返回后,才会再次读取一组字节。
* 所以缓冲流也是依靠提高一次读写的数据量减少读写次数来达到提高读写的效率的
*/
while((d = bis.read())!=-1){
bos.write(d);
}
bis.close();
bos.close();
System.out.println("复制完毕");
}
}
- BufferedOutputStream
在做写出操作时,增大写出次数无疑会降低写出效率。为此我们可以使用缓冲输出流来一次性批量写出若干数据减少写出次数来提高写出效率。
BufferedOutputStream缓冲输出流内部维护着一个缓冲区,每当我们向该流写数据时,都会先将数据存入缓冲区,当缓冲区已满时,缓冲流会将数据一次性全部写出。
使用缓冲输出流可以提高写出效率,但是也存在一个问题,就是写出数据缺乏即时性,有时候我们需要在执行完某些操作后,就希望将这些数据缺失写出,而非在缓冲区中保存直到缓冲区满后才写出。这时,我们就可以使用缓冲流的一个方法 void flush(),该方法会清空缓冲区,将缓冲区中的数据强制写出。
示例代码:
public class BOSDemo {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("box.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
bos.write("你好".getBytes("utf-8"));
//强制将缓冲区中的字节一次性写出
bos.flush();
System.out.println("写出完毕!");
bos.close();
}
}
2、对象流
对象是存在于内存中的。有时候我们需要将对象保存到硬盘上,或者需要将对象传输到另一台计算机上等等这样的操作。这时我们需要将对象转换为一个字节序列,而这个过程就称为对象的序列化。相反,我们有这样一个字节序列需要将其转换为对应的对象,这个过程就称为对象的反序列化。
ObjectOutputStream
是用来对对象进行序列化的输出流。- 其实现对象序列化的方法为: void writeObject(Object o)
该方法可以将给定的对象转换为一个字节序列后写出。
- 其实现对象序列化的方法为: void writeObject(Object o)
ObjectInputStream
是用来对对象进行反序列化的输入流。- 其实现对象反序列化的方法为: Object readObject()
该方法可以从流中读取字节并转换为对应的对象。
- 其实现对象反序列化的方法为: Object readObject()
示例代码:
/**
* 该类用于测试作为对象流读写对象使用
*
* 当一个类需要被对象流读写,那么该类必须实现java.io.Serializable接口
*/
public class Person implements Serializable{
/**
* 当一个类实现了Serializable接口后
* 应当添加一个常量:serialVersionUID
* 该常量为当前类的序列化版本号,若不定义,系统会根据当前类的结构生成,
* 但是只要类的结构发生改变,版本号也会相应发生改变.
*
* 版本号影响着反序列化的结果。即:
* 当OIS对一个对象进行反序列化时,会检查该对象与类的版本是否一致:
* 若一致:反序列化成功,但是若该对象与类的结构不一致,则采用兼容模式,能还原的属性都还原。
* 若不一致:反序列化直接抛出版本不一致异常( InvalidClassException )
*/
private static final long serialVersionUID = 1L;
private String name;
private int age;
private String gender;
/*
* transient 关键字用来修饰属性
* 当被修饰后,该类实例在使用OOS进行对象序列化时,该属性值被忽略。
* 从而达到对象“瘦身”的目的
*/
private transient List<String> otherInfo;
public Person(){
}
public Person(String name, int age, String gender, List<String> otherInfo) {
super();
this.name = name;
this.age = age;
this.gender = gender;
this.otherInfo = otherInfo;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public List<String> getOtherInfo() {
return otherInfo;
}
public void setOtherInfo(List<String> otherInfo) {
this.otherInfo = otherInfo;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", gender=" + gender + ", otherInfo=" + otherInfo + "]";
}
}
对象序列化
/**
* 对象流
* 对象流是一对高级流,作用是方便读写java中的对象。
* java.io.ObjectOutputStream
* 对象输出流,可以将给定的对象转换为一组字节后写出.
*/
public class OOSDemo {
public static void main(String[] args) throws IOException {
Person p = new Person();
p.setName("小明");
p.setAge(20);
p.setGender("男");
List<String> otherInfo = new ArrayList<String>();
otherInfo.add("是一名演员");
otherInfo.add("喜欢运动");
otherInfo.add("喜欢看电影");
p.setOtherInfo(otherInfo);
FileOutputStream fos = new FileOutputStream("person.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
/*
* ObjectOutputStream的writeObject方法可以将给定对象转换为一组字节
* 后写出.这些字节比该对象实际内容要大,因为除了数据外还有结构等描述信息。
*
* 下面的代码实际上经历了两个操作:
* 1:将Person对象转换为一组字节
* 将一个对象转换一组字节的过程称为:对象序列化
* 2:再通过fos将这组字节写入到硬盘
* 将该对象转换的字节写入到硬盘做长久保存的过程称为:对象持久化
*/
oos.writeObject(p);
oos.close();
System.out.println("写出对象完毕");
}
}
反序列化
/**
* java.io.ObjectInputStream
* 对象输入流,作用是可以进行对象反序列化,读取一组字节并还原为对象
* OIS读取的字节必须是由OOS将对象序列化得到的字节,否则会抛出异常
*/
public class OISDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream("person.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
//对象反序列化
Person p = (Person)ois.readObject();
ois.close();
System.out.println(p);
}
}