什么是流?流表示任何有能力产生数据的数据源对象或者是有能力接收数据的接收端对象,它屏蔽了实际的I/O设备中处理数据的细节。 IO流是实现输入输出的基础,它可以很方便地实现数据的输入输出操作,即读写操作。
输入流 | 输出流 | |
---|---|---|
字符流 | Reader | Writer |
字节流 | InputStream | OutputStream |
InputStream
,与输出有关的所有类继承OutputStream
,用以操作二进制数据。ObjectInputStream
和ObjectOutputStream
。OutputStreamWriter
和InputStreamReader
。FileWriter
:自带缓冲区,数据先写到到缓冲区上,然后从缓冲区写入文件。FileReader
:没有缓冲区,可以单个字符的读取,也可以自定义数组缓冲区。在实际应用中,异常处理的方式都需要按照下面的结构进行,本篇为了节约篇幅,之后都将采用向上抛出的方式处理异常。
//将流对象放在try之外声明,并附为null,保证编译,可以调用close
FileWriter writer = null;
try {
//将流对象放在里面初始化
writer = new FileWriter("D:\\b.txt");
writer.write("abc");
//防止关流失败,没有自动冲刷,导致数据丢失
writer.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
//判断writer对象是否成功初始化
if(writer!=null) {
//关流,无论成功与否
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}finally {
//无论关流成功与否,都是有意义的:标为垃圾对象,强制回收
writer = null;
}
}
}
writer.flush();
。close()
方法,值得注意的是,在close执行之前,流会自动进行一次flush的操作以避免数据还残存在缓冲区中,但这并不意味着flush操作是多余的。writer!=null
时才执行关流操作。JDK1.7提出了对流进行异常处理的新方式,任何AutoClosable
类型的对象都可以用于try-with-resourses
语法,实现自动关闭。
要求处理的对象的声明过程必须在try后跟的()
中,在try代码块之外。
try(FileWriter writer = new FileWriter("D:\\c.txt")){
writer.write("abc");
}catch (IOException e){
e.printStackTrace();
}
public static void main(String[] args) throws IOException {
FileReader reader = new FileReader("D:\\b.txt");
//定义数组作为缓冲区
char[] cs = new char[5];
//定义一个变量记录每次读取的字符
int hasRead;
//读取到末尾为-1
while ((hasRead = reader.read(cs)) != -1) {
System.out.println(new String(cs, 0, hasRead));
}
reader.close();
}
m!=-1
时,终止循环。运用文件字符输入与输出的小小案例:
public static void copyFile(FileReader reader, FileWriter writer) throws IOException {
//利用字符数组作为缓冲区
char[] cs = new char[5];
//定义变量记录读取到的字符个数
int hasRead;
while((hasRead = reader.read(cs)) != -1){
//将读取到的内容写入新的文件中
writer.write(cs, 0, hasRead));
}
reader.close();
writer.close();
}
FileOutputStream
在输出的时候没有缓冲区,所以不需要进行flush操作。 public static void main(String[] args) throws Exception {
FileOutputStream out = new FileOutputStream("D:\\b.txt");
//写入数据
//字节输出流没有缓冲区
out.write("天乔巴夏".getBytes());
//关流是为了释放文件
out.close();
}
FileInputStream
,可以定义字节数组作为缓冲区。 public static void main(String[] args) throws Exception{
FileInputStream in = new FileInputStream("E:\\1myblog\\Node.png");
//1.读取字节
int i;
while((i = in.read()) ! =-1)
System.out.println(i);
//2.定义字节数组作为缓冲区
byte[] bs = new byte[10];
//定义变量记录每次实际读取的字节个数
int len;
while((len = in.read(bs)) != -1){
System.out.println(new String(bs, 0, len));
}
in.close();
}
BufferedRead
从Reader
对象中获取数据提供缓冲区。 public static void main(String[] args) throws IOException {
//真正读取文件的流是FileReader,它本身并没有缓冲区
FileReader reader = new FileReader("D:\\b.txt");
BufferedReader br = new BufferedReader(reader);
//读取一行
//String str = br.readLine();
//System.out.println(str);
//定义一个变量来记录读取的每一行的数据(回车)
String str;
//读取到末尾返回null
while((str = br.readLine())!=null){
System.out.println(str);
}
//关外层流即可
br.close();
}
newLine
的方法用于换行,以屏蔽不同操作系统的差异性。 public static void main(String[] args) throws Exception {
//真正向文件中写数据的流是FileWriter,本身具有缓冲区
//BufferedWriter 提供了更大的缓冲区
BufferedWriter writer = new BufferedWriter(new FileWriter("E:\\b.txt"));
writer.write("天乔");
//换行: Windows中换行是 \r\n linux中只有\n
//提供newLine() 统一换行
writer.newLine();
writer.write("巴夏");
writer.close();
}
缓冲流基于装饰设计模式,即利用同类对象构建本类对象,在本类中进行功能的改变或者增强。
例如,BufferedReader本身就是Reader对象,它接收了一个Reader对象构建自身,自身提供缓冲区和其他新增方法,通过减少磁盘读写次数来提高输入和输出的速度。
除此之外,字节流同样也存在缓冲流,分别是BufferedInputStream
和BufferedOutputStream
。
利用转换流可以实现字符流和字节流之间的转换。
public static void main(String[] args) throws Exception {
//在构建转换流时需要传入一个OutputStream 字节流
OutputStreamWriter ow =
new OutputStreamWriter(
new FileOutputStream("D:\\b.txt"),"utf-8");
//给定字符--> OutputStreamWriter转化为字节-->以字节流形式传入文件FileOutputStream
//如果没有指定编码,默认使用当前工程的编码
ow.write("天乔巴夏");
ow.close();
}
最终与文件接触的是字节流,意味着将传入的字符转换为字节。
public static void main(String[] args) throws IOException {
//以字节形式FileInputStream读取,经过转换InputStreamReader -->字符
//如果没有指定编码。使用的是默认的工程的编码
InputStreamReader ir =
new InputStreamReader(
new FileInputStream("D:\\b.txt"));
char[] cs = new char[5];
int len;
while((len=ir.read(cs))!=-1){
System.out.println(new String(cs,0,len));
}
ir.close();
}
最初与文件接触的是字节流,意味着将读取的字节转化为字符。
缓冲流基于适配器设计模式,将某个类的接口转换另一个用户所希望的类的接口,让原本由于接口不兼容而不能在一起工作的类可以在一起进行工作。
以OutputStreamWriter为
例,构建该转换流时需要传入一个字节流,而写入的数据最开始是由字符形式给定的,也就是说该转换流实现了从字符向字节的转换,让两个不同的类在一起共同办事。
程序的所有输入都可以来自于标准输入,所有输出都可以发送到标准输出,所有错误信息都可以发送到标准错误。
对象 | 解释 | 封装类型 |
---|---|---|
System.in | 标准输入流 | InputStream |
System.out | 标准输出流 | PrintStream |
System.err | 标准错误流 | PrintStream |
可以直接使用System.out
和System.err
,但是在读取System.in
之前必须对其进行封装,例如我们之前经常会使用的读取输入:Scanner sc = new Scanner(System.in);
实际上就封装了System.in
对象。
/**
* 从控制台获取一行数据
* @throws IOException readLine 可能会抛出异常
*/
public static void getLine() throws IOException {
//获取一行字符数据 -- BufferedReader
//从控制台获取数据 -- System.in
//System是字节流,BufferedReader在构建的时候需要传入字符流
//将字节流转换为字符流
BufferedReader br =
new BufferedReader(
new InputStreamReader(System.in));
//接收标准输入并转换为大写
String str = br.readLine().toUpperCase();
//发送到标准输出
System.out.println(str);
}
通过转换流,将System.in读取的标准输入字节流转化为字符流,发送到标准输出,打印显示。
打印流只有输出流没有输入流
public static void main(String[] args) throws IOException {
//创建PrintStream对象
PrintStream p = new PrintStream("D:\\b.txt");
p.write("abc".getBytes());
p.write("def".getBytes());
p.println("abc");
p.println("def");
//如果打印对象,默认调用对象身上的toString方法
p.println(new Object());
p.close();
}
//将System.out转换为PrintStream
public static void main(String[] args) {
//第二个参数autoFlash设置为true,否则看不到结果
PrintWriter p = new PrintWriter(System.out,true);
p.println("hello,world!");
}
SequenceInputStream
用于将多个字节流合并为一个字节流的流。Enumeration
中来进行。InputStream
对象。以第一种构建方式为例,我们之前说过,Enumeration
可以通过Vector容器的elements
方法创建。
public static void main(String[] args) throws IOException {
FileInputStream in1 = new FileInputStream("D:\\1.txt");
FileInputStream in2 = new FileInputStream("D:\\a.txt");
FileInputStream in3 = new FileInputStream("D:\\b.txt");
FileInputStream in4 = new FileInputStream("D:\\m.txt");
FileOutputStream out = new FileOutputStream("D:\\union.txt");
//准备一个Vector存储输入流
Vector<InputStream> v = new Vector<>();
v.add(in1);
v.add(in2);
v.add(in3);
v.add(in4);
//利用Vector产生Enumeration对象
Enumeration<InputStream> e = v.elements();
//利用迭代器构建合并流
SequenceInputStream s = new SequenceInputStream(e);
//读取
byte[] bs = new byte[10];
int len;
while((len = s.read(bs))!=-1){
out.write(bs,0,len);
}
out.close();
s.close();
}
对象序列化的目标是将对象保存在磁盘中,或允许在网络中直接传输对象。对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,通过网络将这种二进制流传输到另一个网络节点。其他程序一旦获得了这种流,都可以将这种二进制流恢复为原来的Java对象。
让某个对象支持序列化的方法很简单,让它实现Serializable
接口即可:
public interface Serializable {
}
这个接口没有任何的方法声明,只是一个标记接口,表明实现该接口的类是可序列化的。
我们通常在Web开发的时候,JavaBean可能会作为参数或返回在远程方法调用中,如果对象不可序列化会出错,因此,JavaBean需要实现Serializable接口。
创建一个Person类。
//必须实现Serializable接口
class Person implements Serializable {
//序列化ID serialVersionUID
private static final long serialVersionUID = 6402392549803169300L;
private String name;
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
创建序列化流,将对象转化为字节,并写入"D:\1.data"。
public class ObjectOutputStreamDemo {
public static void main(String[] args) throws IOException {
Person p = new Person();
p.setAge(18);
p.setName("Niu");
//创建序列化流
//真正将数据写出的流是FileOutputStream
//ObjectOutputStream将对象转化为字节
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("D:\\1.data"));
out.writeObject(p);
out.close();
}
}
创建反序列化流,将从"D:\1.data"中读取的字节转化为对象。
public static void main(String[] args) throws IOException, ClassNotFoundException {
//创建反序列化流
//真正读取文件的是FileInputStream
//ObjectInputStream将读取的字节转化为对象
ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:\\1.data"));
//读取数据必须进行数据类型的强制转换
Person p = (Person)in.readObject();
in.close();
System.out.println(p.getName());//Niu
System.out.println(p.getAge());//18
}
需要注意的是:
serializable
,该接口没有任何方法,仅仅作为标记使用。static
或transient
修饰的属性不会进行序列化。如果属性的类型没有实现serializable
接口但是也没有用这两者修饰,会抛出NotSerializableException
。InvalidClassException
。InvalidClassException
的异常。serialVersonUID
。static final
修饰,本身必须是long
类型。// 实现writeObject和readObject两个方法
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person implements Serializable {
private String name;
private int age;
// 将name的值反转后写入二进制流
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeObject(new StringBuffer(name).reverse());
out.writeInt(age);
}
// 将读取的字符串反转后赋给name
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
this.name = ((StringBuffer) in.readObject()).reverse().toString();
this.age = in.readInt();
}
}
还有一种更加彻底的自定义机制,直接将序列化对象替换成其他的对象,需要定义writeReplace
:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person implements Serializable {
private String name;
private int age;
private Object writeReplace(){
ArrayList<Object> list = new ArrayList<>();
list.add(name);
list.add(age);
return list;
}
}
Externalizable实现了Seriablizable接口,并规定了两个方法:
public interface Externalizable extends java.io.Serializable {
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
实现该接口,并给出两个方法的实现,也可以实现自定义序列化。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Externalizable {
String name;
int age;
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(new StringBuffer(name).reverse());
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.name = ((StringBuffer) in.readObject()).reverse().toString();
this.age = in.readInt();
}
}