Skip to content

day20.IO 流

java
课前回顾:
  1.HashMap:
    a.特点:
      无序,无索引,key唯一,value可重复,线程不安全
      可以存null
    b.数据结构:
      哈希表

    c.方法:
      put get remove containsKey keySet  entrySet
  2.LinkedHashMap:
    a.特点:
      有序 无索引 key唯一,value可重复,线程不安全
      可以存null
    b.数据结构:哈希表+链表

  3.TreeSet
    a.特点:可以对元素进行排序,无索引,元素唯一,线程不安全
    b.数据结构:红黑树
    c.构造
      TreeSet()
      TreeSet(比较器)
  4.TreeMap
    a.特点:可以对key进行排序,无索引,key唯一,线程不安全
    b.数据结构:红黑树
    c.构造:
      TreeMap()
      TreeMap(比较器)
  5.Hashtable:
    a.特点:无序,无索引,key唯一,value可重复,线程安全,不可以存null
    b.数据结构:哈希表
  6.Properties:
      a.特点:无序,无索引,key唯一,value可重复,线程安全,不可以存null,key和value都是String
      b.数据结构:哈希表
      c.特有方法:
        setProperty  getProperty  stringPropertyNames()
        load(字节输入流)
今日重点:
  1.all

第一章.字节流

1.IO 流介绍以及输入输出以及流向的介绍

java
1.I:输入 -> Input
  O:输出 -> Output
2.概述:将一个数据从一个设备上传输到另外一个设备上的技术

      谁发数据谁就是输出 ->
      谁收数据谁就是输入 ->

2.IO 流的流向_针对 se 阶段的 IO

java
1.针对于se阶段(内容和硬盘之间):
  输出:将数据从内存中写到硬盘上
  输入:从硬盘上将数据读到内存中
1744161300215

为啥要将数据写到硬盘上:

1.如果我们要保存一个数据,我可以用数组,集合,但是数组和集合都是临时存储,代码运行里面还有数据,但是代码结束了,数组和集合中的数据就没了

2.所以我们想能不能将数据永久保存,我们可以将数据保存到硬盘上,想使用的时候读回来使用即可 -> IO 流

3.IO 流分类

java
1.字节流:万物皆字节,所以字节流叫做"万能流"(侧重指的是复制)

  OutputStream:字节输出流的父类 -> 抽象类
  InputStream:字节输入流的父类 -> 抽象类

2.字符流:专门操作文本文档的

  Writer:字符输出流的父类 -> 抽象类
  Reader:字符输入流的父类 -> 抽象类

IO 流四大基类:

OutputStream

InputStream

Writer

Reader

4.OutputStream 中子类[FileOutputStream]的介绍以及方法的简单介绍

java
1.字节输出流:FileOutputStream extends OutputStream
2.作用:写数据
3.构造:
  FileOutputStream(File file)
  FileOutputStream(String path)
4.注意:
  a.输出流如果指定的文件没有,会自动创建
  b.默认情况下,每次执行,都会创建一个新的文件,覆盖老文件
5.方法:
  a.void write(int data) 一个字节一个字节的写
  b.void write(byte[] bytes) 一个字节数组一个字节数组的写
  c.void write(byte[] bytes,int offset,int count) 一次写一个字节数组一部分
  d.void close() 释放资源
java
public class Demo01FileOutputStream {
    public static void main(String[] args) throws Exception{
        //method01();
        method02();
    }

    /**
     * void write(int data) 一个字节一个字节的写
     * @throws Exception
     */
    private static void method01()throws Exception {
        FileOutputStream fos = new FileOutputStream("day20_IO/output/1.txt");
        fos.write(97);
        fos.close();
    }

    /**
     * void write(byte[] bytes) 一个字节数组一个字节数组的写
     * void write(byte[] bytes,int offset,int count) 一次写一个字节数组一部分
     */
    private static void method02()throws Exception {
        FileOutputStream fos = new FileOutputStream("day20_IO/output/1.txt");
        byte[] bytes = {97,98,99,100,101,102};
        //fos.write(bytes,0,3);
        //fos.write(bytes);

        //byte[] bytes1 = "中国".getBytes();
        //fos.write(bytes1);
        fos.write("你好吗".getBytes());
        fos.close();
    }
}
1744162804271
java
续写追加:
  FileOutputStream(String path,boolean flag) -> flag为true,就会实现续写追加
java
    /**
     * 续写追加
     */
    private static void method03()throws Exception {
        FileOutputStream fos = new FileOutputStream("day20_IO/output/1.txt",true);
        fos.write("春种一粒粟\r\n".getBytes());
        fos.write("秋收万颗子\r\n".getBytes());
        fos.write("四海无闲田\r\n".getBytes());
        fos.write("农夫犹饿死\r\n".getBytes());
        fos.close();
    }

换行符:

windows: \r\n

linux: \n

mac os: \r

5.InputStream 子类[FileInputStream]的介绍以及方法的使用

java
1.概述:字节输入流 -> FileInputStream extends InputStream
2.作用:读数据
3.构造:
  FileInputStream(File file)
  FileInputStream(String path)
4.方法:
  a.int read() 一次读一个字节,返回的是读取的字节
  b.int read(byte[] bytes) 一次读一个字节数组,返回的是读取的个数
  c.int read(byte[] bytes,int offset,int count) 一次读一个字节数组一部分,返回的是读取的个数
  d.void close()释放资源

6.一次读取一个字节

java
  /**
     * int read() 一次读一个字节,返回的是读取的字节
     */
    private static void method01() throws Exception {
        FileInputStream fis = new FileInputStream("day20_IO/output/2.txt");
        //int data1 = fis.read();
        //System.out.println(data1);

        //int data2 = fis.read();
        //System.out.println(data2);

        //int data3 = fis.read();
        //System.out.println(data3);

        //int data4 = fis.read();
        //System.out.println(data4);

        //int data5 = fis.read();
        //System.out.println(data5);

        //定义一个变量,接收读取的字节
        int len = 0;
        while((len = fis.read())!=-1){
            //System.out.println(len);
            System.out.println((char)len);
        }
        fis.close();
    }

1.流中的数据读完之后,就不能再继续读了,如果还想重新读,就再 new 一个对象

2.读取的过程中,不要连续写多个 read

3.流关闭之后,不能再次使用,否则会报错

java
Exception in thread "main" java.io.IOException: Stream Closed
	at java.base/java.io.FileInputStream.read0(Native Method)
	at java.base/java.io.FileInputStream.read(FileInputStream.java:228)
	at com.atguigu.b_input.Demo01FileInputStream.method01(Demo01FileInputStream.java:34)
	at com.atguigu.b_input.Demo01FileInputStream.main(Demo01FileInputStream.java:8)

7.读取-1 的问题

java
每个文件末尾都有一个"结束标记",这个"结束标记"我们看不见摸不到,但是当我们调用read方法,读到了"结束标记",就会固定返回-1
1744167053067

8.一次读取一个字节数组以及过程

java
  /**
     * int read(byte[] bytes) 一次读一个字节数组,返回的是读取的个数
     * @throws Exception
     */
    private static void method02() throws Exception{
        FileInputStream fis = new FileInputStream("day20_IO/output/2.txt");
        /*
           数组长度定为多少,那么每次就会读取多少个数据
           数据都会先保存到数组中,然后我们再从数组中获取
         */
        byte[] bytes = new byte[2];
       /*
        int len1 = fis.read(bytes);
        System.out.println(len1);

        int len2 = fis.read(bytes);
        System.out.println(len2);

        int len3 = fis.read(bytes);
        System.out.println(len3);*/

        //定义一个变量,代表读取的个数
        int len = 0;
        while((len = fis.read(bytes))!=-1){
            //System.out.println(new String(bytes));
            System.out.println(new String(bytes,0,len));
        }
        fis.close();
    }
1744169007955

一般情况我们把数组都会定为 1024 或者其倍数

9.字节流实现图片复制分析

1744170001947

10.字节流实现图片复制代码实现

java
public class Demo02Copy {
    public static void main(String[] args) throws Exception {
        //1.创建FileInputStream用于读取本地上的图片字节
        FileInputStream fis = new FileInputStream("F:\\idea\\io\\2.png");
        //2.创建FileOutputStream用于将读取到的字节写到指定的位置
        FileOutputStream fos = new FileOutputStream("F:\\idea\\io\\2_copy.png");
        //3.边读边写
        byte[] bytes = new byte[1024];
        int len = 0;
        while((len = fis.read(bytes)) != -1){
            fos.write(bytes,0,len);
        }

        //4.关流 -> 先开后关
        fos.close();
        fis.close();
    }
}

第二章.字符流

1.字节流读取中文的问题

java
1.注意:
  a.英文字母:在GBK中,还是UTF-8中都是占一个字节
  b.中文:一个汉字在GBK中占2个字节
         一个汉字在UTF-8中占3个字节
2.字节流属于万能流(侧重于文件复制):但是不能边读边看(一边读,一边输出)
  解决:我们在读取文本文档的时候,如果把内容看成是一个一个的字符去操作,就可以了

说明:即使用字符流去操作文本文档,那么前提也是编码和解码规则一致

​ 如果编码解码规则不一致,字符流操作也会出现乱码情况

​ 字符流操作文本文档,如果编码和解码一致,边读边看是不会乱的

​ 字节流操作文本文档,即使编码和解码一致,边读边看也有可能出现乱码

2.FileReader 的介绍以及使用

java
1.概述:字符输入流-> FileReader extends Reader
2.作用:读字符
3.构造:
  FileReader(File file)
  FileReader(String path)
4.方法:
  a.int read() 一次读取一个字符,返回的是读取的字符
  b.int read(char[] chars)一次读取一个字符数组,返回的是读取的个数
  c.void close() 关闭资源
java
    /**
     * int read() 一次读取一个字符,返回的是读取的字符
     * @throws Exception
     */
    private static void method02() throws Exception {
        FileReader fr = new FileReader("day20_IO/output/3.txt");
  /*      int data1 = fr.read();
        System.out.println((char) data1);

        int data2 = fr.read();
        System.out.println((char) data2);*/

        int len = 0;
        while((len = fr.read())!=-1){
            System.out.println((char) len);
        }
        fr.close();
    }
java
    /**
     * int read(char[] chars)一次读取一个字符数组,返回的是读取的个数
     */
    private static void method03()throws Exception {
        FileReader fr = new FileReader("day20_IO/output/3.txt");
        char[] chars = new char[3];
        //定义一个len,接收读取的个数
        int len = 0;
        while((len = fr.read(chars))!=-1){
            System.out.println(new String(chars,0,len));
        }
    }

1.字符流读取文本文档中的内容,想要边读边输出,在编码一致的情况下,输出的不会是乱码

2.字节流读取文本文档中的内容,想要边读边输出,即使在编码一致的情况下,输出的也有可能是乱码,因为字节有可能读不全就是输出了

3.FileWriter 的介绍以及使用

java
1.概述:字符输出流 -> FileWriter extends Writer
2.作用:写字符
3.构造:
  FileWriter(File file)
  FileWriter(String path)
  FileWriter(String path,boolean flag)-> flag如果是true,就会实现续写追加
4.方法:
  void write(int i) -> 一次写一个字符
  void write(char[] chars) -> 一次写一个字符数组
  void write(char[] chars,int offset,int count) -> 一次写一个字符数组一部分
  void write(String s) -> 一次写一个字符串
  void flush() 刷新缓冲区
  void close() 关闭资源
5.注意:FileWriter底层自带一个缓冲区,我们写的数据先在缓冲区中,我们需要将数据从缓冲区中刷到文件中
java
    /**
     * void write(String s) -> 一次写一个字符串
     */
    private static void method01() throws IOException {
        FileWriter fw = new FileWriter("day20_IO/output/4.txt");
        fw.write("你好吗?小金莲111");
        fw.close();
    }

4.FileWriter 的刷新功能和关闭功能

java
1.flush():刷新缓冲区,这个方法刷完之后,流对象还能用
2.close():先刷新,后关闭,close之后,流对象就不能使用了
java
    /**
     * void write(String s) -> 一次写一个字符串
     */
    private static void method01() throws IOException {
        FileWriter fw = new FileWriter("day20_IO/output/4.txt",true);
        fw.write("你好吗?小金莲111");
        fw.flush();
        fw.write("小金莲说:涛哥,你好吗?");
        fw.flush();
        fw.close();
        //fw.write("涛哥说:小金莲,我嗑药了!你去和庆庆过吧");
    }

5.IO 异常处理的方式

java
public class Demo02FileWriter {
    public static void main(String[] args) {
        FileWriter fw = null;
        try {
            fw = new FileWriter("day20_IO/output/4.txt");
            fw.write("你好");

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            /*
              如果fw不是null,证明new了,所以最后要close
              如果是null,证明没new,这个时候就不用close了
             */
            if (fw!=null){
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

6.JDK7 之后 io 异常处理方式

java
1.格式:
  try(IO流对象;IO流对象){

  }catch(异常 对象名){
      对象名.printStackTrace()
  }
2.会自动关闭流对象
java
    /**
     * jdk7以后得IO流异常处理
     */
    private static void method02() {
        try (FileWriter fw = new FileWriter("day20_IO/output/4.txt")) {
            fw.write("涛哥问:小金莲,你在干什么?");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

7.JDK9 之后的 IO 异常处理方式

之前我们讲过 JDK 1.7 引入了 trywith-resources 的新特性,可以实现资源的自动关闭,此时要求:

  • 该资源必须实现 java.io.Closeable 接口
  • 在 try 子句中声明并初始化资源对象
  • 该资源对象必须是 final 的
java
try(IO流对象1声明和初始化;IO流对象2声明和初始化){
    可能出现异常的代码
}catch(异常类型 对象名){
	异常处理方案
}

JDK1.9 又对 trywith-resources 的语法升级了

  • 该资源必须实现 java.io.Closeable 接口
  • 在 try 子句中声明并初始化资源对象,也可以直接使用已初始化的资源对象
  • 该资源对象必须是 final 的
java
IO流对象1声明和初始化;
IO流对象2声明和初始化;

try(IO流对象1;IO流对象2){
    可能出现异常的代码
}catch(异常类型 对象名){
	异常处理方案
}
java
/**
     * 不用close
     * 还减少了try的压力
     * @throws IOException
     */
    private static void method03() throws IOException {
        FileWriter fw = new FileWriter("day20_IO/output/4.txt");
        try (fw) {
            fw.write("涛哥问:小金莲,你在干什么?");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

第三章.序列化流

一.序列化流和反序列化流介绍

java
1.有两个流对象:
  a.序列化流:ObjectOutputStream
  b.反序列化流:ObjectInputStream
2.作用:读写对象
1744184149818

二.序列化流_ObjectOutputStream

java
1.概述:ObjectOutputStream
2.作用:写对象
3.构造:
  ObjectOutputStream(OutputStream os)
4.方法:
  writeObject(Object o) 写对象
java
public class Person implements Serializable {
    private String name;
    private Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
java
    /**
     * 序列化流
     * 1.概述:ObjectOutputStream
     * 2.作用:写对象
     * 3.构造:
     *   ObjectOutputStream(OutputStream os)
     * 4.方法:
     *   writeObject(Object o) 写对象
     */
    private static void writer() throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("day20_IO/output/person.txt"));
        Person p1 = new Person("小金莲", 18);
        oos.writeObject(p1);
        oos.close();
    }

三.反序列化_ObjectInputStream

java
1.概述:ObjectInputStream
2.作用:读对象
3.构造:
  ObjectInputStream(InputStream is)
4.方法:
  Object readObject()
java
   /**
     * 1.概述:ObjectInputStream
     * 2.作用:读对象
     * 3.构造:
     *   ObjectInputStream(InputStream is)
     * 4.方法:
     *   Object readObject()
     */
    private static void reader()throws Exception {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("day20_IO/output/person.txt"));
        Person p = (Person)ois.readObject();
        System.out.println(p);
        ois.close();
    }

一个对象想要在网络上进行传输,这个对象必须要实现序列化接口

四.不想被序列化操作(了解)

java
关键字: transient

五.反序列化时出现的问题以及分析以及解决

java
1.问题描述:
  我们修改了源码,但是没有重新序列化,直接反序列化了,就会出现序列号冲突问题
1744187053334
java
解决:人为的将序列号定死
java
public class Person implements Serializable {
    public static final long serialVersionUID = 42L;

    private String name;
    public Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

六.经验问题

java
1.如果我们序列化多个对象,我们反序列化时就需要循环读取,如果循环的次数和存储对象的个数不一样,就会出现EOFException(文件意外到达结尾异常)
java
public class Demo02ObjectWriteAndRead {
    public static void main(String[] args)throws Exception {
        //riter();
        reader();
    }

    /**
     * 1.概述:ObjectInputStream
     * 2.作用:读对象
     * 3.构造:
     *   ObjectInputStream(InputStream is)
     * 4.方法:
     *   Object readObject()
     */
    private static void reader()throws Exception {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("day20_IO/output/person.txt"));
        //Person p1 = (Person)ois.readObject();
        //Person p2 = (Person)ois.readObject();
        //Person p3 = (Person)ois.readObject();
        //Person p4 = (Person)ois.readObject();
        //System.out.println(p1);
        //System.out.println(p2);
        //System.out.println(p3);
        //System.out.println(p4);
        ArrayList<Person> list = (ArrayList<Person>) ois.readObject();
        for (Person person : list) {
            System.out.println(person);
        }
        ois.close();
    }

    /**
     * 序列化流
     * 1.概述:ObjectOutputStream
     * 2.作用:写对象
     * 3.构造:
     *   ObjectOutputStream(OutputStream os)
     * 4.方法:
     *   writeObject(Object o) 写对象
     */
    private static void writer() throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("day20_IO/output/person.txt"));
        Person p1 = new Person("小金莲", 18);
        Person p2 = new Person("小涛哥", 20);
        Person p3 = new Person("小庆庆", 19);
        ArrayList<Person> list = new ArrayList<>();
        list.add(p1);
        list.add(p2);
        list.add(p3);
        //oos.writeObject(p1);
        //oos.writeObject(p2);
        //oos.writeObject(p3);
        oos.writeObject(list);
        oos.close();
    }
}

第四章.打印流

1.PrintStream 打印流基本使用

java
1.概述:PrintStream extends OutputStream
2.作用:将数据打印到控制台上或者打印到指定文件中
3.构造:
  PrintStream(String path)
4.方法:
  println():原样输出,自带换行效果
  print():原样输出,不带换行效果
java
public class Demo01PrintStream {
    public static void main(String[] args) throws Exception {
        PrintStream ps = new PrintStream("day20_IO/output/print.txt");
        ps.println("一片两片三四片");
        ps.println("五片六片七八片");
        ps.println("九片十片十一片");
        ps.println("香山红叶红满天");
        ps.close();
    }
}
java
1.System类有一个静态方法:  setOut(PrintStream ps) -> 改变流向 -> 将输出语句在控制台上打印的结果转移到指定的文件中输出保存
java
public class Demo02PrintStream {
    public static void main(String[] args) throws Exception {
        PrintStream ps = new PrintStream("day20_IO/output/print.txt");
        System.out.println("哈哈哈");
        System.out.println("嘿嘿嘿");
        System.setOut(ps);
        System.out.println("此类中有一个异常");
        System.out.println("叫做空指针异常");
        System.out.println("原因是:ps对象为null");
        System.out.println("在代码的第7行");
        ps.close();
    }
}

使用场景:

可以将输出的内容以及详细信息放到日志文件中,永久保存

以后我们希望将输出的内容永久保存,但是输出语句会将结果输出到控制台上,控制台是临时显示,如果有新的程序运行,新程序的运行结果会覆盖之前的结果,这样无法达到永久保存,到时候我们想看看之前的运行结果信息就看不到了,所以我们需要将输出的结果保存到日志文件中,就可以使用 setOut 改变流向

2.PrintStream 打印流完成续写

java
PrintStream(OutputStream out)
            OutputStream抽象类,可以传递FileOutputStream,FileOutputStream中有一个追加续写的构造
java
public class Demo03PrintStream {
    public static void main(String[] args) throws Exception {
        PrintStream ps = new PrintStream(new FileOutputStream("day21_net/3.txt",true));
        ps.println("哈哈哈");
        ps.close();
    }
}

第五章.Properties 结合 IO 流使用方法

java
1.void load(InputStream inStream)-> 将文件中的数据加载到properties集合中
java
问题描述:
  将来我们写代码的时候有很多重要的"硬数据",比如用户名,密码等,如果这些数据放到源代码中,后续要是修改,我们就要频繁的去源代码中修改,将来类和类之间有联系,频繁修改源代码,有可能导致代码出现问题,有可能牵连到其他的类
  所以我们应该将这些"硬数据"放到文件中,用java代码动态解析此文件,动态获取文件中重要的数据
java
1.创建xxx.properties配置文件
2.创建方式: 在当前模块下,右键 -> new -> file -> 取名xxx.properties
3.做配置:
  a.配置文件中数据格式:key=value形式
  b.每一个键值对写完,需要换行写下一对
  c.key和value都是String的,但是不要加""
  d.不要写中文
java
username=root
password=1234
java
public class Demo01Properties {
    public static void main(String[] args) throws Exception {
        Properties properties = new Properties();
        //创建FileInputStream
        FileInputStream fis = new FileInputStream("day20_IO/output/pro.properties");
        //调用load方法将流中的数据加载到properties集合中
        properties.load(fis);

        String username = properties.getProperty("username");
        String password = properties.getProperty("password");
        System.out.println(username+"..."+password);
    }
}