重点知识——高级流
流连接示意图
缓冲流
java.io.BufferedOutputStream和BufferedInputStream.
缓冲流是一对高级流,作用是提高读写数据的效率.
缓冲流内部有一个字节数组,默认长度是8K.缓冲流读写数据时一定是将数据的读写方式转换为块读写来保证读写效率.
使用缓冲流完成文件复制操作
package io;
import java.io.*;
/**
* java将流分为节点流与处理流两类
* 节点流:也称为低级流,是真实连接程序与另一端的"管道",负责实际读写数据的流。
* 读写一定是建立在节点流的基础上进行的。
* 节点流好比家里的"自来水管"。连接我们的家庭与自来水厂,负责搬运水。
* 处理流:也称为高级流,不能独立存在,必须连接在其他流上,目的是当数据经过当前流时
* 对其进行某种加工处理,简化我们对数据的同等操作。
* 高级流好比家里常见的对水做加工的设备,比如"净水器","热水器"。
* 有了它们我们就不必再自己对水进行加工了。
* 实际开发中我们经常会串联一组高级流最终连接到低级流上,在读写操作时以流水线式的加工
* 完成复杂IO操作。这个过程也称为"流的连接"。
*
* 缓冲流,是一对高级流,作用是加快读写效率。
* java.io.BufferedInputStream和java.io.BufferedOutputStream
*
*/
public class CopyDemo3 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("ppt.pptx");
BufferedInputStream bis = new BufferedInputStream(fis);
FileOutputStream fos = new FileOutputStream("ppt_cp.pptx");
BufferedOutputStream bos = new BufferedOutputStream(fos);
int d;
long start = System.currentTimeMillis();
while((d = bis.read())!=-1){//使用缓冲流读取字节
bos.write(d);//使用缓冲流写出字节
}
long end = System.currentTimeMillis();
System.out.println("耗时:"+(end-start)+"ms");
bis.close();//关闭流时只需要关闭高级流即可,它会自动关闭它连接的流
bos.close();
}
}
缓冲输出流写出数据时的缓冲区问题
通过缓冲流写出的数据会被临时存入缓冲流内部的字节数组,直到数组存满数据才会真实写出一次
package io;
import java.io.BufferedOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* 缓冲输出流写出数据的缓冲区问题
*/
public class BOS_FlushDemo {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("bos.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
String line = "奥里给!";
byte[] data = line.getBytes(StandardCharsets.UTF_8);
bos.write(data);
System.out.println("写出完毕!");
/*
缓冲流的flush方法用于强制将缓冲区中已经缓存的数据一次性写出。
注:该方法实际上实在字节输出流的超类OutputStream上定义的,并非只有缓冲
输出流有这个方法。但是实际上只有缓冲输出流的该方法有实际意义,其他的流实现
该方法的目的仅仅是为了在流连接过程中传递flush动作给缓冲输出流。
*/
bos.flush();//冲
bos.close();
}
}
对象流
java.io.ObjectOutputStream和ObjectInputSteam
对象流是一对高级流,在流连接中的作用是进行对象的序列化与反序列化。
对象序列化:将一个java对象按照其结构转换为一组字节的过程
对象反序列化:将一组字节还原为java对象(前提是这组字节是一个对象序列化得到的字节)
对象序列化的流连接操作原理图:
package io;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
/**
* 对象流
* java.io.ObjectInputStream和ObjectOutputStream
*
* 对象流的功能:进行对象序列化与反序列化
* 对象序列化由对象输出流完成:将一个java对象按照其结构转换为一组字节的过程
* 对象反序列化由对象输入流完成:将一组字节还原为一个java对象的过程
*
*/
public class OOSDemo {
public static void main(String[] args) throws IOException {
//将一个Person对象写入文件person.obj中
String name = "刘桑";
int age = 55;
String gender = "男";
String[] otherInfo = {"技术好","拍片儿一流","大家的启蒙老师"};
Person p = new Person(name,age,gender,otherInfo);
//文件输出流(低级流):将字节写入指定文件中
FileOutputStream fos = new FileOutputStream("person.obj");
//对象输出流(高级流):将一个java对象进行序列化
ObjectOutputStream oos = new ObjectOutputStream(fos);
/*
序列化时抛出异常:
java.io.NotSerializableException
说明序列化的类没有实现可序列化接口:java.io.Serializable
这里将对象写出涉及到两个操作:
对象经对象输出流时,会被文件输出流转换为一组字节,这个过程称为对象序列化
序列化后的字节再经过文件流被写入文件中(硬盘中),这个过程称为数据持久化
所谓持久化就是可以长久保存了
*/
oos.writeObject(p);
System.out.println("写出完毕!");
oos.close();
}
}
对象反序列化
package io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
/**
* 使用对象输入流完成对象的反序列化
*/
public class OISDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//从person.obj文件中将对象反序列化回来
FileInputStream fis = new FileInputStream("person.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
/*
Object readObject()
该方法会进行对象的反序列化,如果对象流通过其连接的流读取的字节分析并非
是一个java对象时,会抛出异常:ClassNotFoundException
*/
Person p = (Person)ois.readObject();
System.out.println(p);
}
}
需要进行序列化的类必须实现接口:java.io.Serializable 实现序列化接口后最好主动定义序列化版本号这个常量。 这样一来对象序列化时就不会根据类的结构生成一个版本号,而是使用该固定值。 那么反序列化时,只要还原的对象和当前类的版本号一致就可以进行还原。
transient关键字可以修饰属性,用于在进行对象序列化时忽略不必要的属性,达到对象瘦身的目的
package io;
import java.io.Serializable;
import java.util.Arrays;
/**
* 使用当前类实例测试对象流的读写操作
*/
public class Person implements Serializable {
public static final long serialVersionUID = 1L;
private String name;//姓名
private int age;//年龄
private String gender;//性别
private String[] otherInfo;//其他信息
public Person(String name, int age, String gender, String[] otherInfo) {
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 String[] getOtherInfo() {
return otherInfo;
}
public void setOtherInfo(String[] otherInfo) {
this.otherInfo = otherInfo;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
", otherInfo=" + Arrays.toString(otherInfo) +
'}';
}
}
缓冲字符流
缓冲字符输出流:java.io.PrintWriter
java.io.BufferedWriter和BufferedReader
缓冲字符流内部也有一个缓冲区,读写文本数据以块读写形式加快效率.并且缓冲流有一个特别的功能:可以按行读写文本数据.
java.io.PrintWriter具有自动行刷新的缓冲字符输出流,实际开发中更常用.它内部总是会自动连接BufferedWriter作为块写加速使用.
package io;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
/**
* 缓冲字符流(是一对高级流)
* java.io.BufferedWriter和BufferedReader
* 缓冲流内部维护一个char数组,默认长度8k.以块读写方式读写字符数据保证效率
*
* java.io.PrintWriter则是具有自动行刷新的换成字符输出流(实际缓冲功能是靠BufferedWriter
* 实现的,它内部总是连接着这个流。)
*
* 使用缓冲字符流后就可以实现按行读写字符串,并且读写效率高。
*/
public class PWDemo1 {
public static void main(String[] args) throws FileNotFoundException, UnsupportedEncodingException {
//按行向文件pw.txt中写入字符串
/*
PrintWriter继承自Writer.
它提供很多构造方法,其中就有可以直接对文件进行写操作的构造器
PrintWriter(File file)
PrintWriter(String filename)
*/
// PrintWriter pw = new PrintWriter("pw.txt");
/*
这里可以按照指定的字符集写出字符串到文本文件中。但是字符集只能以字符串形式
表达。因此注意拼写。字符集不区分大小写。
但是如果字符集名字拼写错误,会抛出异常:
UnsupportedEncodingException
不支持的 字符集 异常
*/
PrintWriter pw = new PrintWriter("pw.txt","UTF-8");
/*
println()方法是输出字符出后带上换行符
print()方法输出字符串后不带换行符
*/
pw.println("夜空中最亮的星,能否听清。");
pw.println("那仰望的人,心底的孤独和叹息。");
System.out.println("写出完毕!");
pw.close();
}
}
在流链接中使用PW
package io;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
/**
* 练习PrintWriter的流连接操作
*/
public class PWDemo2 {
public static void main(String[] args) throws FileNotFoundException {
//文件输出流(低级流,字节流) 作用:向文件中写出字节
FileOutputStream fos = new FileOutputStream("pw2.txt");
//转换输出流(高级流,字符流) 作用:1衔接字符与字节流的 2:将写出的字符转换为字节
OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
//缓冲字符输出流(高级流,字符流) 作用:块写文本数据加速的(内部有一个8k的char数组)
BufferedWriter bw = new BufferedWriter(osw);
//具有自动行刷新功能(高级流,字符流) 作用:1按行写出字符串(println) 2:自动行刷新
PrintWriter pw = new PrintWriter(bw);
/*
完成一个简易记事本工具
将控制台上输入的每一行字符串按行写入到该文件中
如果单独输入exit,则程序退出。
思路:
用一个死循环,重复做下面的工作
1:在控制台上输入一行字符串
2:判断输入的字符串是否为"exit"
若是:则break掉循环退出程序
若不是:则将输入的字符串通过println方法写入文件
*/
Scanner scanner = new Scanner(System.in);
while(true) {
String line = scanner.nextLine();
if("exit".equals(line)){
break;
}
pw.println(line);
}
System.out.println("写出完毕!");
pw.close();
}
}
PrintWriter的自动行刷新功能
如果实例化PW时第一个参数传入的是一个流,则此时可以再传入一个boolean型的参数,此值为true时就打开了自动行刷新功能。 即: 每当我们用PW的println方法写出一行字符串后会自动flush.
package io;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
/**
* 练习PrintWriter的流连接操作
*/
public class PWDemo2 {
public static void main(String[] args) throws FileNotFoundException {
//文件输出流(低级流,字节流) 作用:向文件中写出字节
FileOutputStream fos = new FileOutputStream("pw2.txt");
//转换输出流(高级流,字符流) 作用:1衔接字符与字节流的 2:将写出的字符转换为字节
OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
//缓冲字符输出流(高级流,字符流) 作用:块写文本数据加速的(内部有一个8k的char数组)
BufferedWriter bw = new BufferedWriter(osw);
//具有自动行刷新功能(高级流,字符流) 作用:1按行写出字符串(println) 2:自动行刷新
/*
当我们创建PrintWriter时,构造方法里第一个参数为一个流,那么就支持再传入
一个boolean值参数表示是否打开自动行刷新功能,传入true则打开。
此时每当我们调用它的println方法写出一行字符串后就会自动flush()一次。
注意:print方法和write方法写出字符串时并不会自动flush()!!!!
*/
PrintWriter pw = new PrintWriter(bw,true);
/*
完成一个简易记事本工具
将控制台上输入的每一行字符串按行写入到该文件中
如果单独输入exit,则程序退出。
思路:
用一个死循环,重复做下面的工作
1:在控制台上输入一行字符串
2:判断输入的字符串是否为"exit"
若是:则break掉循环退出程序
若不是:则将输入的字符串通过println方法写入文件
*/
Scanner scanner = new Scanner(System.in);
while(true) {
String line = scanner.nextLine();
if("exit".equals(line)){
break;
}
pw.println(line);
// pw.print("");//不会自动flush
// pw.write("");//不会自动flush
}
System.out.println("写出完毕!");
pw.close();
}
}
缓冲字符流
缓冲字符输入流:java.io.BufferedReader
是一个高级的字符流,特点是块读文本数据,并且可以按行读取字符串。
package io;
import java.io.*;
/**
* 使用缓冲字符输入流按行读取字符串
* 该高级流的主要作用:
* 1:块读文本数据加速(内部有一个默认8k的char数组)
* 2:可以按行读取字符串
*/
public class BRDemo {
public static void main(String[] args) throws IOException {
//将当前源代码输出到控制台上
/*
思路:
读取当前源代码文件,按行读取,并且将读取到的每一行字符串都输出到控制台上即可
*/
//文件输入流(低级流,字节流) 作用:从文件中读取字节
FileInputStream fis = new FileInputStream("./src/io/BRDemo.java");
//转换输入流(字符流,高级流) 作用:1衔接字节与字符流 2将读取的字节转换为字符
InputStreamReader isr = new InputStreamReader(fis);
//缓冲字符输入流(字符流,高级流) 作用:1块读字符数据加速 2按行读取字符串
BufferedReader br = new BufferedReader(isr);
/*
BufferedReader缓冲字符输入流
提供了一个独有的方法:readLine()
作用:读取一行字符串。连续读取若干字符直到遇到了换行符位置,并将换行符之前的
内容返回。注意:返回的字符串里不包含最后的换行符。
特殊情况:
如果这一行只有一个换行符,那么返回值为空字符串:""
如果读取到了流的末尾,那么返回值为null。
实际运行时:
当我们第一次调用readLine()方法时,缓冲字符输入流实际会一次性读取8k的char
回来并存入内部的char数组中(块读文本操作)。readLine方法只将char数组中从头
开始一直到第一个换行符位置的内容以一个字符串形式返回。
*/
String line;
while((line = br.readLine()) != null){
System.out.println(line);
}
br.close();
}
}
IO总结
总结
java将流分为两类:节点流与处理流:
-
节点流:也称为低级流.
节点流的另一端是明确的,是实际读写数据的流,读写一定是建立在节点流基础上进行的.
-
处理流:也称为高级流.
处理流不能独立存在,必须连接在其他流上,目的是当数据流经当前流时对数据进行加工处理来简化我们对数据的该操作.
实际应用中,我们可以通过串联一组高级流到某个低级流上以流水线式的加工处理对某设备的数据进行读写,这个过程也成为流的连接,这也是IO的精髓所在.
缓冲流
缓冲流是一对高级流,在流链接中链接它的目的是加快读写效率。缓冲流内部默认缓冲区为8kb,缓冲流总是块读写数据来提高读写效率。
java.io.BufferedOutputStream缓冲字节输出流,继承自java.io.OutputStream
常用构造器
- BufferedOutputStream(OutputStream out):创建一个默认8kb大小缓冲区的缓冲字节输出流,并连接到参数指定的字节输出流上。
- BufferedOutputStream(OutputStream out,int size):创建一个size指定大小(单位是字节)缓冲区的缓冲字节输出流,并连接到参数指定的字节输出流上。
常用方法
flush():强制将缓冲区中已经缓存的数据一次性写出
缓冲流的写出方法功能与OutputStream上一致,需要知道的时write方法调用后并非实际写出,而是先将数据存入缓冲区(内部的字节数组中),当缓冲区满了时会自动写出一次。
java.io.BufferedInputStream缓冲字节输出流,继承自java.io.InputStream
常用构造器
- BufferedInputStream(InputStream in):创建一个默认8kb大小缓冲区的缓冲字节输入流,并连接到参数指定的字节输入流上。
- BufferedInputStream(InputStream in,int size):创建一个size指定大小(单位是字节)缓冲区的缓冲字节输入流,并连接到参数指定的字节输入流上。
常用方法
缓冲流的读取方法功能与InputStream上一致,需要知道的时read方法调用后缓冲流会一次性读取缓冲区大小的字节数据并存入缓冲区,然后再根据我们调用read方法读取的字节数进行返回,直到缓冲区所有数据都已经通过read方法返回后会再次读取一组数据进缓冲区。即:块读取操作
对象流
对象流是一对高级流,在流链接中的作用是完成对象的序列化与反序列化
序列化:是对象输出流的工作,将一个对象按照其结构转换为一组字节的过程。
反序列化:是对象输入流的工作,将一组字节还原为对象的过程。
java.io.ObjectInputStream对象输入流,继承自java.io.InputStream
常用构造器
ObjectInputStream(InputStream in):创建一个对象输入流并连接到参数in这个输入流上。
常用方法
Object readObject():进行对象反序列化,将读取的字节转换为一个对象并以Object形式返回(多态)。
如果读取的字节表示的不是一个java对象会抛出异常:java.io.ClassNotFoundException
java.io.ObjectOutputStream对象输出流,继承自java.io.OutputStream
常用构造器
ObjectOutputStream(OutputStream out):创建一个对象输出流并连接到参数out这个输出流上
常用方法
void writeObject(Object obj):进行对象的序列化,将一个java对象序列化成一组字节后再通过连接的输出流将这组字节写出。
如果序列化的对象没有实现可序列化接口:java.io.Serializable就会抛出异常:java.io.NotSerializableException
序列化接口java.io.Serrializable
该接口没有任何抽象方法,但是只有实现了该接口的类的实例才能进行序列化与反序列化。
实现了序列化接口的类建议显示的定义常量:static final long serialVersionUID = 1L;
可以为属性添加关键字transient,被该关键字修饰的属性在序列化是会被忽略,达到对象序列化瘦身的目的。
缓冲字符输出流
缓冲字符输出流需要记住的是PrintWriter和BufferedReader
作用:
1:块写或块读文本数据加速
2:可以按行写或读字符串
java.io.PrintWriter 具有自动行刷新的缓冲字符输出流
常用构造器
PrintWriter(String filename) :可以直接对给定路径的文件进行写操作
PrintWriter(File file):可以直接对File表示的文件进行写操作
上述两种构造器内部会自动完成流连接操作。
PrintWriter(OutputStream out):将PW链接在给定的字节流上(构造方法内部会自行完成转换流等流连接)
PrintWriter(Writer writer):将PW链接在其它字符流上
PrintWriter(OutputStream out,boolean autoflush)
PrintWriter(Writer writer,boolean autoflush)
上述两个构造器可以在链接到流上的同时传入第二个参数,如果该值为true则开启了自动行刷新功能。
常用方法
void println(String line):按行写出一行字符串
特点
自动行刷新,当打开了该功能后,每当使用println方法写出一行字符串后就会自动flush一次