【学习笔记】IO 流(一)

IO 流

 

流的概念

  • 流是内存与存储设备之间传输数据的通道。

    • 如果保存在内存中的程序,想要读取硬盘中的文件,那么内存和硬盘之间就要建立一条通道。反之如果想要把内存中的数据保存到硬盘中,也需要建立一条通道

  • 数据借助流传输,就像水借助管道传输

 

流的分类

  • 按方向:

    • 输入流:将<存储设备>中的内容读入到<内存>中。

    • 输出流:将<内存>中的内容写入到<存储设备>中。

    • 文件 ----输入流-------> 程序 -------输出流--------> 文件

  • 按单位:

    • 字节流:以字节为单位,可以读写所有数据

    • 字符流:以字符为单位,只能读写文本数据

  • 按功能:

    • 节点流:具有实际传输数据的读写功能,也叫底层流

    • 过滤流:在节点流的基础之上增强功能,比如缓冲流

 

字节流

  • 字节流的父类(抽象类):

    • InputStream: 字节输入流

      • public int read(){}

      • public int read(byte[] b){}

      • public int read(byte[] b, int off, int len){}

    • OutputStream: 字节输出流

      • public int write(){}

      • public int write(byte[] b){}

      • public int write(byte[] b, int off, int len){}

 

文件字节流

  • FileInputStream:继承InputStream

    • public int read(byte[] b){} //从流中读取多个字节,将读到的内容存入b数组,返回实际读到的字节数,如果达到文件的尾部,则返回-1

    • 构造方法:FileInputStream(String name) //name 为文件的路径

    • public int read(){} //读取一个字节

       

使用FileInputStream

1.读取单个字节

package com.iosteam;
​
import java.io.FileInputStream;
import java.io.FileNotFoundException;
​
public class Demo01 {
    public static void main(String[] args) throws Exception {
        //创建FileInputSteam,并指定文件路径
        FileInputStream fis = new FileInputStream("f:\\aaa.txt");
​
        //读单个字节
        int data = 0;   //用来保存读取的字节
        while((data = fis.read()) != -1){
            System.out.println((char) data);
        }
    }
}

image-20220726100040726

这样的效率并不高,原因是读取一个字节就打印一个字节

我们先读取多个字节在存到数组中,最后打印

2.读取多个字节

//读取多个字节
byte[] bytes = new byte[3];  //先创建一个长度为3的数组,用来存储
int count = fis.read(bytes);  //返回的是读取字节的个数
System.out.println(new String(bytes));  //把数组转成字符串
System.out.println(count);

image-20220726100855203

这时候,只读出了 abc 3个字节,原因是我们创建的保存数组长度就是3

要想把其他的字节也读出来就要继续读

//读取多个字节
byte[] bytes = new byte[3];  //先创建一个长度为3的数组,用来存储
int count = fis.read(bytes);  //返回的是读取字节的个数
System.out.println(new String(bytes));  //把数组转成字符串
System.out.println(count);
int count2 = fis.read(bytes);  //返回的是读取字节的个数
System.out.println(new String(bytes));  //把数组转成字符串
System.out.println(count2);
int count3 = fis.read(bytes);  //返回的是读取字节的个数
System.out.println(new String(bytes,0,count3));  //把数组转成字符串
System.out.println(count3);

image-20220726101202718

 

需要注意的是:在最后一次读取的时候,只剩下一个字节,我们在转成字符串时,要给它多传两个参数,offset - 要解码的第一个字节的索引length - 要解码的字节数,即代码中的0和count3

否则,上一次读取的后两个字节 ef 就会在这个数组中,结果就是gef了.

 

这样读取三次不是我们想要读取的方式,应该用循环来写

//读取多个字节
byte[] bytes = new byte[3];  //先创建一个长度为3的数组,用来存储
int count = 0;  //返回的是读取字节的个数
while((count = fis.read(bytes)) != -1){   //等于-1 就说明读取完毕
    System.out.println(new String(bytes,0,count));
}

image-20220726102149567

 

一般我们创建的数组缓冲区的大小不会这么小

 

  • FileOutputStream : 继承OutputStream

    • 构造方法:

      • FileOutputStream (String name) //name 是文件的路径,每次写都覆盖前面的内容

      • FileOutputStream (String name,boolean append) // append 来控制每一次写入数据是覆盖还是继续往后写

    • wirte() //写单个字节

    • write(byte[] b) //一次写多个字节

package com.iosteam;
​
import java.io.FileOutputStream;
​
public class Demo02 {
    public static void main(String[] args) throws Exception{
        FileOutputStream fos = new FileOutputStream("f:\\bbb.txt");
        fos.write(97);
        fos.write('b');   //自动转换成 int
        fos.write('c');
        fos.close();
    }
}

image-20220726103406314

 

//写多个字节
String str = "helloworld";
fos.write(str.getBytes());
fos.close();

image-20220726103633182

 

我们创建一个不覆盖,继续向后写的流

package com.iosteam;
​
import java.io.FileOutputStream;
​
public class Demo02 {
    public static void main(String[] args) throws Exception{
        FileOutputStream fos = new FileOutputStream("f:\\bbb.txt",true);
​
        //写多个字节
        String str = "helloworld";
        fos.write(str.getBytes());
        String str1 = "zhangsan";
        fos.write(str1.getBytes());
        fos.close();
    }
}
​

image-20220726103933107

 

getBytes() 是 获取字符串所对应的字节数组

 

  • 案例:使用文件字节流复制文件

package com.iosteam;
​
import java.io.FileInputStream;
import java.io.FileOutputStream;
​
public class Demo03 {
    public static void main(String[] args) throws Exception{
        //创建输入流
        FileInputStream fis = new FileInputStream("f:\\001.png");
        //创建输出流
        FileOutputStream fos = new FileOutputStream("f:\\002.jpg");
        //边读边写
        byte[] b = new byte[1024];   //存放数据,缓冲作用
        int count = 0;
        while ((count = fis.read(b)) != -1){     //等于-1说明读取完毕
            fos.write(b,0,count);
        }
        fis.close();
        fos.close();
        System.out.println("完成");
​
    }
}

 

字节缓冲流

  • 如果单独使用文件字节流,其内部没有缓冲区,所以效率就没有那么高

  • 缓冲流在使用过程中会创建缓冲区,这样的话,如果缓冲区中有,我们就不用从硬盘中读取了,直接从缓冲区中读。

    • 提高IO效率,减少访问磁盘的次数

    • 数据存储在缓冲区中,flush 是将缓冲区的内容写入文件中,也可以直接close

  • BufferedInputStream 继承于 FilterInputStream 过滤流

package com.iosteam;
​
import java.io.BufferedInputStream;
import java.io.FileInputStream;
​
public class Demo04 {
    public static void main(String[] args) throws Exception{
        //创建BufferedInputStream
        FileInputStream fis = new FileInputStream("f:\\aaa.txt");
        BufferedInputStream bis = new BufferedInputStream(fis);   //需要传入节点流
        //读取
        //单个读
//        int data = 0;
//        while((data = bis.read()) != -1){
//            System.out.print((char) data);
//        }
        //读取多个
        byte[] b = new byte[1024];
        int count = 0;
        while ((count = bis.read(b)) != -1){
            System.out.println(new String(b,0,count));
        }
         bis.close();
    }
}

在 BufferedInputStream 中有长度为 8kb 的缓冲数组

最后不用关闭 FileInputStream ,直接关闭 BufferedInputStream 就行,因为在BufferedInputStream 直接就把 FileInputStream 给关了

 

  • BufferedOutputStream 继承于 FilterOutputStream 过滤流

package com.iosteam;
​
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
​
public class Demo05 {
    public static void main(String[] args) throws Exception{
        //创建BufferedOutputStream
        FileOutputStream fos = new FileOutputStream("f:\\buf.txt");
        BufferedOutputStream bos = new BufferedOutputStream(fos);  //需要传入节点流
        //读取
        for (int i = 0; i < 10; i++) {
            bos.write("helloworld\r\n".getBytes());
        }
    }
}

如果只是这样,它没有写到硬盘,而是写到了 8 kb 的缓冲区

我们需要在写入后 加入 bos.flush(),这样才写入了硬盘

或者在最后关闭bos.close() 因为在关闭方法内部调用了flush方法

这两种方法都可以把缓冲区中的内容写到硬盘,

第一种方法 比较安全,可以防止数据丢失,但效率低,第二种方法效率高,但如果写着写着断电了,那么数据就丢失了。

package com.iosteam;
​
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
​
public class Demo05 {
    public static void main(String[] args) throws Exception{
        //创建BufferedOutputStream
        FileOutputStream fos = new FileOutputStream("f:\\buf.txt");
        BufferedOutputStream bos = new BufferedOutputStream(fos);  //需要传入节点流
        //读取
        for (int i = 0; i < 10; i++) {
            bos.write("helloworld\r\n".getBytes());
            bos.flush();
        }
        bos.close();
    }
}

image-20220726115106394

 


 

 

对象流

  • 对象流:ObjectOutputStream/ObjectInputStream

    • 增强了缓冲区功能

    • 增强了读写8种基本数据类型和字符串功能

    • 增强了读写对象的功能

      • readObject() //从流中读取一个对象

      • writeObject(Object obj) //向流中写一个对象

  • 序列化:把内存中的对象写入文件中

  • 反序列化:从文件中读取一个对象

ObjectOutputStream 继承了 OutputStream

里面的方法:

1.构造方法:ObjectOutputStream (OutputStream out) //需要传递一个字节输出流

2.void writeObject(Object obj) //将对象写入流中

3.void writeBoolean(boolean val) //写入一个boolean值

4.void writeInt(int i) //写入一个32位的int值

5.void wirteUTF(String str) 以UTF-8格式写入String 的基本数据

八种基本类型,不一一列举

然后我们来序列化一个 Student 对象

package com.iosteam;
​
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
​
public class Demo06 {
    public static void main(String[] args) throws Exception{
        //创建ObjectOutputStream
        FileOutputStream fos = new FileOutputStream("f:\\test\\stu.bin");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        //序列化对象
        Student s1 = new Student("张三",20);
        oos.writeObject(s1);
        //关闭
        oos.close();
        System.out.println("序列化完成");
    }
}

注意:这个时候并没有序列化成功,报出了 不可序列化异常

image-20220726152620778

 

原因是,我们的Student 类 需要实现 Serializable 接口,这个接口是一个标识符,标志着你这个类能不能被序列化,需要实现这个接口,才能被序列化

image-20220726153053199

实现这个接口后,我们再次运行

image-20220726153151512image-20220726153214235

 

序列化成功!!!

 

然后是反序列化

ObjectInputStream 继承了 InputStream

ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化

方法:

1.构造方法:ObjectInputStream(InputStream in) //需要传入字节输入流

2.boolean readBoolean() //读取Boolean值

3.String readUTF() //读取字符串

4.Object readObject() //读取对象

八大基本类型的方法不在一一列举

 

下面我们来反序列化刚刚序列化的那个对象

package com.iosteam;
​
import java.io.FileInputStream;
import java.io.ObjectInputStream;
​
public class Demo07 {
    public static void main(String[] args) throws Exception{
        //创建一个ObjectInputStream
        FileInputStream fis = new FileInputStream("f:\\test\\stu.bin");
        ObjectInputStream ois = new ObjectInputStream(fis);    //传入字节输入流
        //反序列化
        Student s1 = (Student) ois.readObject();   // 强制转换成 Student
        // 关闭
        ois.close();
        System.out.println("反序列化完成");
        System.out.println(s1.toString());
    }
}

image-20220726154825904

 

注意 只能从流中读一次,如果多读一次会报EOFException

image-20220726154959512

读完一次,流中就没有这个对象了,再读就报错

 

序列化与反序列化中的注意事项:

  • 1.序列化类必须实现 Serializable 接口

  • 2.序列化类中的对象属性也需要实现 Serializable 接口

private Address address;   //这是一个属性对象,它也需要实现Serializable 接口
  • 3.序列化版本号ID:

/*
  serialVersionUID:序列化版本号
 */
private static final long serialVersionUID = 100L;

它是一个静态常量,用来保证序列化的类和反序列化的类是同一个类

当我们定义了这个常量后,我们再去反序列化,会发现报错了

image-20220726161501700

InvalidClassException 这个错误的意思是我们反序列化的类和序列化的类不是同一个类

因为我们一开始序列化时,没有给他定义序列化版本号ID,所以程序自动给他定义了一个序列化版本号ID:

image-20220726161709999

然而这个时候,我们自己给他定义了 image-20220726161824581

当我们再去反序列化时,就会报不是一个类的异常了

解决办法:我们重新序列化一遍,让这个类的序列化版本ID变成 100,再去反序列化就不会报错了

 

  • 4.使用transient 修饰的属性不能被序列化

我们使用transient 来修饰 age

private transient int age;

我们来序列化和反序列化

image-20220726162507318

我们发现age = 0,原因是age没有被序列化

  • 5.静态属性也不能序列化

  • 6.使用集合来序列化多个对象

序列化:

//序列化对象
Student s1 = new Student("张三",20);
Student s2 = new Student("李四",18);
ArrayList list = new ArrayList();
list.add(s1);
list.add(s2);
oos.writeObject(list);

反序列化:

 //反序列化
ArrayList list = (ArrayList) ois.readObject();   // 强制转换成 Student
System.out.println(list.toString());

image-20220726163152658

 
posted @ 2022-07-26 16:37  GrowthRoad  阅读(34)  评论(0编辑  收藏  举报