day11-IO流

IO流

1 IO流的概述和分类

1.1学习IO流的目的?

1,将数据写到文件中,实现数据永久化存储

2,读取文件中已经存在的数据

1.2 IO流概述

其中:I表示intput,是数据从硬盘进内存的过程,称之为读。

O表示output,是数据从内存到硬盘的过程。称之为写。

1.3 思考一个问题?

在数据传输的过程中,是谁在读?是谁在写?这个参照物是谁?

IO的数据传输,可以看做是一种数据的流动,按照流动的方向,以内存为参照物,进行读写操作。

简单来说:内存在读,内存在写。

在这里插入图片描述

1.4 IO流的分类

1.按流向分—输入流 输出流

在这里插入图片描述

2.按数据类型分–字节流 字符流

字节流 操作所有类型的文件 (包括音频视频图片等)

字符流 只能操作纯文本文件 (包括java文件,txt文件等)

在这里插入图片描述

​ 一般来说,IO流的分类是按照数据类型来分的

1.5 IO流的技术选型

那我们在用流读取内容的时候,应该如何选择 流的格式呢?

那我们不得不提到一个知识

什么是纯文本文件?

用windows记事本打开能读的懂,那么这样的文件就是纯文本文件。

在这里插入图片描述

思考:office文件可以用字符流操作吗? no! 因为不是纯文本文件

思考:下面这些文件分别可以用什么流操作? 字符 字节 字节 字节

在这里插入图片描述

1.6 总结:IO流具体分类和使用场景

  • 按照数据的流向

    • 输入流:读数据
    • 输出流:写数据
  • 按照数据类型来分

    • 字节流
      • 字节输入流
      • 字节输出流
    • 字符流
      • 字符输入流
      • 字符输出流
  • IO流的使用场景

    • 如果操作的是纯文本文件(可以用记事本打开),优先使用字符流
    • 如果操作的是图片、视频、音频等二进制文件,优先使用字节流
    • 如果不确定文件类型,优先使用字节流.字节流是万能的流

2.字节流

操作所有类型的文件 包括音频视频图片等

2.1 字节流的创建

image-20220426190319349
字节流抽象基类
  - InputStream:这个抽象类是表示字节输入流的所有类的超类。
  - OutputStream:这个抽象类是表示字节输出流的所有类的超类。
  - 子类名特点:子类名称都是以其父类名作为子类名的后缀。
字节输入/出流
 - FileInputStream(String name):创建文件输入流以指定的名称读取文件。
  - FileOutputStream(String name):创建文件输出流以指定的名称写入文件。

案例引入:案例 往images文件夹中的a.txt文件中写入数据

2.2 字节流写数据步骤

1.创建字节输出流对象

注意事项:
如果文件不存在,就创建。
2.写数据

注意事项:
写出的整数,实际上写到文件中,是在ASCII码表中那个字符。
3.释放资源

注意事项:
每次使用完流必须要释放资源。

注意点:

  • 1.如果文件不存在,会帮我们创建2.如果文件存在,会把文件覆盖。
  • 传递一个整数,实际上写到文件中,是在ASCII码表中那个字符。
  • 每次使用完流必须要释放资源。

代码展示:

FileOutputStream fos = new FileOutputStream("E:/file/image/abc.txt");
fos.write(97);//写的是 char中的类型
fos.close();
FileInputStream fis = new FileInputStream("E:/file/image/a.txt");
int read = fis.read();
System.out.println(read);
fos.close();

2.3 字节流写数据的3种方式

方法名说明
void write(int b)一次写一个字节数据
void write(byte[] b)一次写一个字节数组数据
void write(byte[] b, int off, int len)一次写一个字节数组的部分数据

不需要记住 百度直接搜索ASCALL码表即可

代码演示

//    fos.write(97);//写的是 char中的类型
//        fos.write(98);
//        fos.write(99);
//        fos.write(100);
//        fos.write(101);//abcde

        //字节数组
        byte[]bytes = {97,98,99,100,101};
//        fos.write(bytes);//abcde

        //void write(byte[] b, int off, int len):将 len字节从指定的字节数组开始,从偏移量off开始写入此文件输出流
        fos.write(bytes,2,2);//off 可以认为索引 从0开始 截取长度为len的内容输出到文件中
        fos.close();

2.4 字节流写数据的两个小问题

字节流写数据如何实现换行呢?
写完数据后,加换行符

windows:\r\n 单写\r \n 也可以

linux:\n

mac:\r

字节流写数据如何实现追加写入呢?

public FileOutputStream(String name,boolean append)

创建文件输出流以指定的名称写入文件。如果第二个参数为true ,不会清空文件里面的内容

FileOutputStream fos = new FileOutputStream("E:/file/image/abc.txt",true);
fos.write(97);//写的是 char中的类型
fos.write("\r\n".getBytes());//换行
fos.write(98);
fos.write("\r\n".getBytes());//换行
fos.write(99);
fos.write("\r\n".getBytes());//换行
fos.write(100);
fos.write("\r\n".getBytes());//换行
fos.write(101);//abcde
fos.write("\r\n".getBytes());//换行
fos.close();

2.5 字节流写数据加try…catch异常处理

思考:那么我们如何操作才能让close方法一定执行呢?

finally:在异常处理时提供finally块来执行所有清除操作。比如说IO流中的释放资源

特点:被finally控制的语句一定会执行,除非JVM退出

异常处理标准格式:try….catch…finally

代码展示:

FileOutputStream fos = null;
try {
    fos = new FileOutputStream("E:/file/image/abc.txt",true);
    fos.write(97);//写的是 char中的类型
    fos.write("\r\n".getBytes());//换行
    fos.write(98);
    fos.write("\r\n".getBytes());//换行
    fos.write(99);
    fos.write("\r\n".getBytes());//换行
    fos.write(100);
    fos.write("\r\n".getBytes());//换行
    fos.write(101);//abcde
    fos.write("\r\n".getBytes());//换行
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}finally {
    try {
        fos.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

2.5 小结:字节流读数据 一次读一个字节

步骤:
1. 创建字节输出流对象
文件不存在,就创建。
文件存在就清空。如果不想被清空则加true
2. 写数据
可以写一个字节,写一个字节数组,写一个字节数组的一部分
写一个回车换行:\r\n
3. 释放资源



注意事项:
       如果文件不存在,就直接报错。
注意事项:
       读出来的是文件中数据的码表值。 a  97
注意事项:
       每次使用完流必须要释放资源。

2.6 案例 往images文件夹中的a.txt文件中写入数据

往images文件夹中的a.txt文件中写入数据(只能写英文,中文乱码) I love you HuiHui

  1. 创建一个文件对象
  2. 判断一下这个文件存在否,不存在创建
  3. 创建一个输出流
  4. 创建数据,写数据
  5. 关闭输出流

代码:

public static void main(String[] args) {
        FileOutputStream fos = null;
        try {
            File file = new File("E:/file/image/a.txt");
            if (file != null && file.length() > 0) {
                fos = new FileOutputStream(file);
                String s1 = " I love you HuiHui";
                byte[] bytes = s1.getBytes();
                fos.write(bytes);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

2.8 字节流读取数据

在这里插入图片描述

2.9 字节流读取数据步骤

案例引入:读取images文件夹中的a.txt文件中数据

如何读取呢?

1.创建字节输入流对象。

注意事项:
如果文件存在则读取。
2.读取数据

注意事项:
读取的是字节的ASCII码需要转换为char类型
读取中文会乱码。
3.释放资源

注意事项:
每次使用完流必须要释放资源。

2.10 字节流读取数据的2种方式

方法名说明
int read()一次读取一个字节数据
int read(byte[] b)一次读一个字节组数据,把数据封装到参数b中,返回值为本次读取到的字节个数

读取到最后,返回值为-1

注意:read()方法在一次使用里面最好只调用一次

示例代码:

public static void main(String[] args) {
        FileInputStream fis = null;
         byte[]bytes = {97,98,99};
        try {
            fis = new FileInputStream("E:/file/image/a.txt");

            // 读一个字节
            //int read = fis.read();
            //System.out.println(read);

            //向下继续读取字节 读取多个
            //int read1 = fis.read();
            // System.out.println(read1);
            // 循环读取
            int b;
            //fis.read() 读到结果返回,读不到就返回一个-1.
            // b = 正常值 如果为-1 -1占了两个字节            
            
            while ((b = fis.read()) != -1) {//fis.read(bytes,0,4)
                System.out.println((char)b);
            }
            
 //        System.out.println(fis.read(bytes));//3 可以设置字节数组 每次读取字节数组的字节个数
//        System.out.println(fis.read(bytes));//3
//        System.out.println(fis.read(bytes));//3
//        while ((b=fis.read(bytes))!=-1){
//            System.out.println((char) b);//乱码
//        }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

英文和数字占两个字节

2.11 案例 读取images文件夹中的a.txt文件中数据

**需求:**读取images文件夹中的a.txt文件中的数据(只能写英文,中文乱码)

步骤:

  1. 创建一个文件对象
  2. 判断一下这个文件存在否
  3. 创建一个输入流
  4. 读取数据
  5. 关闭输入流

代码展示:

public static void main(String[] args) {
    FileInputStream fis = null;
    try {
        fis = new FileInputStream("E:/file/image/a.txt");
        int b ;
        while ((b = fis.read() )!= -1) {
            System.out.println((char) b);
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2.12 案例:复制文件

需求:把“E:\images\a.png”复制到当前模块下
分析:
复制文件,其实就把文件的内容从一个文件中读取出来(数据源),然后写入到另一个文件中(目的地)
数据源:
E:\images\a.png ---- 读数据 — FileInputStream
目的地:
模块名称\copy.png — 写数据 — FileOutputStream

代码实现:

public static void main(String[] args) {
    //创建输入流
    FileInputStream fis = null;
    //创建输出流
    FileOutputStream fos = null;
    try {
        fis = new FileInputStream("E:\\file\\image\\a.txt");
        fos = new FileOutputStream("src\\com\\itgaohe\\123.txt");
        //读取 本地的 文件
        int b;
        while ((b = fis.read()) != -1) {
            //写出到 当前模块中
            fos.write(b);
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        try {
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2.13 思考问题

思考:如果操作的文件过大,那么速度会不会有影响?

一个字:慢!!!

在这里插入图片描述

问题:在读取的时候 一次只能 一个字节字节的读取;写出的时候也只能一次写一个字节数据的写出。

为了改变这个问题

提高拷贝速度的解决方案

为了解决速度问题,

1.字节流通过创建字节数组,可以一次读写多个数据。
2.一次读一个字节数组的方法:public int read(byte[] b)从输入流读取最多b.length个字节的数据
返回的是读入缓冲区的总字节数,也就是实际的读取字节个数

在这里插入图片描述

代码展示:

//读取 本地的 文件
int b = -1;
byte[]bytes = new byte[1024];
while ((b = fis.read(bytes)) != -1) {
    //写出到 当前模块中
    fos.write(bytes,0,b);
}

2.14 字节流缓冲流

字节缓冲流:
BufferOutputStream:缓冲输出流
BufferedInputStream:缓冲输入流

构造方法:
字节缓冲输出流:BufferedOutputStream​(OutputStream out)
字节缓冲输入流:BufferedInputStream​(InputStream in)

为什么构造方法需要的是字节流,而不是具体的文件或者路径呢?
字节缓冲流仅仅提供缓冲区,而真正的读写数据还得依靠基本的字节流对象进行操作

private static int DEFAULT_BUFFER_SIZE = 8192; 默认创建长度为8192的字符缓冲数组

在这里插入图片描述

在这里插入图片描述

完善

在这里插入图片描述

在这里插入图片描述

代码演示:

 public static void main(String[] args) throws IOException {
        //ppt doc txt jpg png mp4 都可以读
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:/file/image/IO.pptx"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:/file/image/IO2.pptx"));
        int len = -1;
        byte[] bytes = new byte[1024];
        while ((len = bis.read(bytes)) != -1) {
            bos.write(bytes,0,len);
        }
        bis.close();
        bos.close();
    }

2.15 案例:复制视频

需求:把“E:\itgaohe\a.avi”复制到模块目录下的“b.avi”
思路:
根据数据源创建字节输入流对象
根据目的地创建字节输出流对象
读写数据,复制视频
释放资源

代码展示:

public static void main(String[] args) throws IOException {
    BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:/file/image/guangzhou.mp4"));
    BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("src/com/itgaohe/guangzhou2.mp4"));
    int len = -1;
    byte[] bytes = new byte[1024];
    while ((len = bis.read(bytes)) != -1) {
        bos.write(bytes,0,len);
    }
    bis.close();
    bos.close();
}

2.16 小结

方法名说明
void write(int b)一次写一个字节数据
void write(byte[] b)一次写一个字节数组数据
void write(byte[] b, int off, int len)一次写一个字节数组的部分数据
int read()一次读取一个字节数据
int read(byte[] b)一次读一个字节组数据,把数据封装到参数b中,返回值为本次读取到的字节个数
操作所有类型的文件 没有读取到位-1 字节缓冲流:可以提高读写效率

3.字符流

3.1 思考:为什么要学习字符流

把文件中的数据读取到内存时,如果此时文件中出现了中文,那么字节流就会出现乱码现象。所以纯文本的文件,我们就需要使用字符流来进行操作。

为什么字节流读取纯文本文件,可能会出现乱码?

其实这个跟 计算机编码规则有关

 public static void main(String[] args) throws IOException {
        //创建输入流
        FileInputStream fis = null;
            fis = new FileInputStream("E:\\file\\123.txt");
            //读取 本地的 文件
            int b = -1;
            while ((b = fis.read()) != -1) {
                //写出到 当前模块中
//                System.out.println(b);//打印12个字节
                System.out.println((char) b);// 文件中加入中文 出现中文乱码
            }
                fis.close();
    }

那具体是遵循什么规则呢?

3.2 编码表

基础知识:

计算机中储存的信息都是用二进制数表示的;我们在屏幕上看到的英文、汉字等字符是二进制数转换之后的结果
按照某种规则,将字符存储到计算机中,称为编码
按照同样的规则,将存储在计算机中的二进制数解析显示出来,称为解码
编码和解码的方式必须一致,否则会导致乱码。
简单理解:
存储一个字符a,首先需在码表中查到对应的数字是97,然后按照转换成二进制的规则进行存储。
读取的时候,先把二进制解析出来,再转成97,通过97查找到对应的字符是a。

ASCII字符集:
ASCII(American Standard Code for Information Interchange,美国信息交换标准代码):包括了数字,大小写字符和一些常见的标点符号。
注意:ASCII码表中是没有中文的。
GBKwindow系统默认的码表。兼容ASCII码表,也包含了21003个汉字,并支持繁体汉字以及部分日韩文字。
注意:GBK是中国的码表,一个中文以两个字节的形式存储。但不包含世界上所有国家的文字。

Unicode码表:

由国际组织ISO 制定,是统一的万国码,计算机科学领域里的一项业界标准,容纳世界上大多数国家的所有常见文字和符号。
但是因为表示的字符太多,所以Unicode码表中的数字不是直接以二进制的形式存储到计算机的,会先通过UTF-7,UTF-7.5,UTF-8,UTF-16,以及 UTF-32的编码方式再存储到计算机,其中最为常见的就是UTF-8。
注意: Unicode是万国码,以UTF-8编码后一个中文以三个字节的形式存储

编码表小结

在这里插入图片描述

  • 什么是字符集

    是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等

    l计算机要准确的存储和识别各种字符集符号,就需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBXXX字符集、Unicode字符集等

  • 常见的字符集

    • ASCII字符集

      lASCII:是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)

      基本的ASCII字符集,使用7位表示一个字符,共128字符。ASCII的扩展字符集使用8位表示一个字符,共256字符,方便支持欧洲常用字符。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等

    • GBXXX字符集

      GBK:最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等

    • Unicode字符集

      UTF-8编码:可以用来表示Unicode标准中任意字符,它是电子邮件、网页及其他存储或传送文字的应用 中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。它使用一至四个字节为每个字符编码

      编码规则:

      128个US-ASCII字符,只需一个字节编码

      拉丁文等字符,需要二个字节编码

      大部分常用字(含中文),使用三个字节编码

      其他极少使用的Unicode辅助字符,使用四字节编码

3.3 汉字存储和展示过程解析

我们先来了解一下 汉字的存储过程 再去看为什么字节流读取的时候可能会出现乱码

在这里插入图片描述

重点:windows默认使用码表为:GBK,一个字符两个字节。
idea和以后工作默认使用Unicode的UTF-8编解码格式,一个中文三个字节。

3.4 字符串中的编码解码问题

那对于汉字/字符串读取的内容 他是如何进行存储的呢?

编码:
byte[] getBytes​():使用平台的默认字符集将该 String编码为一系列字节,将结果存储到新的字节数组中

byte[] getBytes​(String charsetName):使用指定的字符集将该 String编码为一系列字节,将结果存储到新的字节数组中

解码:

String​(byte[] bytes):通过使用平台的默认字符集解码指定的字节数组来构造新的 String

String​(byte[] bytes, String charsetName):通过指定的字符集解码指定的字节数组来构造新的 String

代码展示:

String s = "山东高合";
byte[] bytes = s.getBytes();//UTF-8默认
System.out.println(Arrays.toString(bytes));

byte[] bytes1 = s.getBytes("UTF-8");默认为UTF-8  一个中文3个字节
System.out.println(Arrays.toString(bytes1));

byte[] bytes2 = s.getBytes("GBK");
System.out.println(Arrays.toString(bytes2));//一个中文2个字节


byte[]bytes3 = {-27, -79, -79, -28, -72, -100, -23, -85, -104, -27, -112, -120};
String s1 = new String(bytes3);
System.out.println(s1);

String s2 = new String(bytes3,"GBK");//灞变笢楂樺悎 乱码 原因:编码和解码格式不一致!
System.out.println(s2);

byte[]bytes4 = {-55, -67, -74, -85, -72, -33, -70, -49};
String s4 = new String(bytes4,"GBK");
System.out.println(s4);

结论:不管是那种流,只要支持中文,以相同的编码格式读取、解析就能得到中文。

​ //编码和解码 - 码表不一致。乱码。

例如:GBK 读取 GBK 输出

​ UTF-8读取 UTF-8输出

3.5 为什么字节流读取纯文本文件,可能会出现乱码?

因为字节流一次读一个字节,而不管GBK还是UTF-8一个中文都是多个字节,用字节流每次只能读其中的一部分,所以就会出现乱码问题。

在这里插入图片描述

3.6 字符流读取中文的过程

为了解决字节流读取纯文本文件,可能会出现乱码的问题。我们提供字符流来读取中文 我们来看一下他具体是怎么存储的

字符流 = 字节流 + 编码表

基础知识: 不管是在哪张码表中,中文的第一个字节一定是负数。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3.7 字符流写数据步骤:

1.创建字符输出流对象
注意事项:
如果文件不存在,就创建。但是要保证父级路径存在。
如果文件存在就清空。

2.写数据
注意事项:
1,写出int类型的整数,实际写出的是整数在码表上对应的字母。
2,写出字符串数据,是把字符串本身原样写出。

fw.write(97);

3.释放资源
注意事项:
每次使用完流必须要释放资源。

3.8 字符流的创建

接下来我们就用字符流 来解决中文乱码的问题。

1. FileWriter字符输出流

构造器说明
public FileWriter(File file)创建字符输出流管道与源文件对象接通
public FileWriter(File file,boolean append)创建字符输出流管道与源文件对象接通,可追加数据
public FileWriter(String filepath)创建字符输出流管道与源文件路径接通
public FileWriter(String filepath,boolean append)创建字符输出流管道与源文件路径接通,可追加数据

2.FileReader字符输入流

构造器说明
public FileReader(File file)创建字符输入流管道与源文件对象接通
public FileReader(String pathname)创建字符输入流管道与源文件路径接通

代码:

   FileReader fr = new FileReader("src/abc.txt");
    FileWriter fw = new FileWriter("src/cde.txt");
    int len ;
    while ((len=fr.read())!=-1){
        fw.write(len);
    }
    fw.close();
    fr.close();
}

3.9 字符流写数据的5种方式

方法名说明
void write(int c)写一个字符
void write(char[] cbuf)写入一个字符数组
void write(char[] cbuf, int off, int len)写入字符数组的一部分
void write(String str)写一个字符串
void write(String str, int off, int len)写一个字符串的一部分

3.10 字符流读和关闭流刷新流

方法名说明
flush()刷新流,还可以继续写数据
close()关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据
int read()一次读一个字符数据
int read(char[] cbuf)一次读一个字符数组数据

flush() 方法的使用原因:flush本意是冲刷,这个方法大概取自它引申义冲马桶的意思,马桶有个池子,你往里面扔东西,会暂时保存在池子里,只有你放水冲下去,东西才会进入下水道。

专业术语叫缓冲区 当你print或者write的时候,会暂时保存在缓冲区 当你直接调用close()方法关闭流的时候,在流的通道中 还有缓存 没有清理掉,刷新一下,方便下一次的使用。就像刷马桶一样。所以应该在关闭读写流之前先flush()。

代码展示: 用字符流读写文件

    public static void main(String[] args) {
        FileReader fr = null;
        FileWriter fw = null;
        try {
            //读
            fr = new FileReader("E:/file/dogdaily.txt");
            //写
            fw = new FileWriter("src/com/itgaohe/my.txt");
            int c = -1;
            while ((c = fr.read()) != -1) {
                fw.write(c);
            }
            //如果读取的字符数量很多 则用如下格式
//            char[] chars = new char[1024];
//            int c = -1;
//            while ((c = fr.read(chars)) != -1) {
//                fw.write(chars);
//            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

3.11 案例:使用字符流保存键盘录入的数据

需求:将用户键盘录入的用户名和密码保存到本地实现永久化存储。
步骤:
用户键盘录入用户名
将用户名和密码写到本地文件中

代码演示:

public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        FileWriter fw = null;
        try {
            fw = new FileWriter("E:/file/userPass.txt");
            System.out.println("请输入用户名:");
            String username = sc.next();//灰灰
            System.out.println("请输入密码:");
            String password = sc.next();
            fw.write("username:" + username);
            fw.write("\r\n");
            fw.write("password:" + password);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println("存储成功!");
        }
    }

3.12 字符缓冲流

字符缓冲流:
BufferedWriter:将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入,可以指定缓冲区大小,或者可以接受默认大小。默认值足够大,可用于大多数用途
BufferedReader:从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取,可以指定缓冲区大小,或者可以使用默认大小。 默认值足够大,可用于大多数用途
构造方法:
BufferedWriter​(Writer out)
BufferedReader​(Reader in)

3.13 字符缓冲流特有功能

BufferedWriter:
void newLine​():写一行行分隔符,行分隔符字符串由系统属性定义

BufferedReader:
public String readLine​() :读一行文字。 结果包含行的内容的字符串,不包括任何行终止字符,如果流的结尾已经到达,则为null

public static void main(String[] args) throws IOException {
    BufferedReader br = null;
    BufferedWriter bw = null;
    try {
        br = new BufferedReader(new FileReader("E:/file/userPass.txt"));
        bw = new BufferedWriter(new FileWriter("src/com/itgaohe/123.txt"));
        String line = null;
        while ((line = br.readLine()) != null) {
            bw.write(line);
            bw.newLine();
        }

    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        br.close();
        bw.close();
    }
}

3.14 案例:读取文件中的数据排序后再次写到本地

需求:读取文件中的数据,排序后再次写到本地文件
步骤:
读取数据
将数据排序
写回本地

代码展示:

userPass

username:huihui
password:123
public static void main(String[] args) {
    BufferedReader br = null;
    BufferedWriter bw = null;
    ArrayList<String> list = new ArrayList<>();
    try {
        br = new BufferedReader(new FileReader("E:/file/userPass.txt"));
        bw = new BufferedWriter(new FileWriter("src/com/itgaohe/123.txt"));
        String line = null;
        while ((line = br.readLine()) != null) {
            list.add(line);
        }
        //倒序排序
        Collections.sort(list, (o1, o2) -> o2.hashCode() - o1.hashCode());
        for (String s : list) {
            bw.write(s);
            bw.newLine();
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            bw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3.15 案例:使用字符缓冲流读取user.txt中用户名和密码

需求:读取用户名和密码,判断用户输入数据是否正确
步骤:
用户通过控制台输入数据
读取文件内容,进行数据撕裂获取username, password
比较用户输入数据是否正确

代码展示:

 public static void main(String[] args) {
        BufferedReader br = null;
        ArrayList<String> list = new ArrayList<>();
        String username = null;
        String password = null;
        int time = 3;
        Scanner sc = new Scanner(System.in);
        try {
            br = new BufferedReader(new FileReader("E:/file/userPass.txt"));
            String line = null;
            while ((line = br.readLine())!=null){
                list.add(line);
            }
            for (String s : list) {
                if (s.startsWith("username")){
                    //说明是账号
                    String[] split = s.split(":");
                     username = split[1];
                    System.out.println(username);
                }
                if (s.startsWith("password")){
                    String[] split = s.split(":");
                     password = split[1];
                    System.out.println(password);
                }
            }
            //输入值进行比较
            while (true){
                System.out.println("请输入用户名:");
                String username1 = sc.next();
                System.out.println("请输入密码:");
                String password1 = sc.next();
                if (username1.equals(username)&&password1.equals(password)){
                    System.out.println("登陆成功!!!");
                    break;
                }else {
                    System.out.println("比对失败!!!请重新输入:你还有" +(--time)+"次机会!");
                    if (time <=0){
                        System.out.println("你账号被锁定!");
                        break;
                    }
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

3.16 IO流小结

在这里插入图片描述

  1. 学会字节流完成文件的读取操作!!!
  2. 学会用缓冲流完成文件读取操作!!!
  3. 字节流打印中文为什么乱码?字节流复制文件为什么不会乱码?
  4. 字符串转字节 、 字节转字符串!!!
  5. 转码的概念。什么情况下会乱码!!!

能力目标

  1. 把一个照片文件用 IO流(以字节为单位去搬运) 拷贝到项目模块目录下。

  2. 把一个照片文件用 IO流(以数组为单位去搬运) 拷贝到项目模块目录下。

  3. 把一个照片文件用 缓冲流 拷贝到项目模块目录下。

  4. 把一个文件夹目录 拷贝到项目模块目录下。(要求:文件夹及其子文件全部拷贝)

    尝试使用缓冲流完成这个copy操作。

4.转换流

4.1 案例引入

用转换流往a.txt数据文件写入GBK编码中文数据,再用转换流读取a.txt,解码为GBK编码

输入流----读取文件

输出流----将内容输入到文件

代码展示:

public static void main(String[] args) throws IOException {
     File file1 = new File("E:\\file\\aa\\image\\a.txt");
     File file2 = new File("src/com/itgaohe/123.txt");
     OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(file2), "GBK");
     InputStreamReader isr = new InputStreamReader(new FileInputStream(file1), "GBK");
     int len = -1;
     while ((len = isr.read()) != -1) {
         osw.write(len);
     }
     osw.close();
     isr.close();
    }

4.2 查看本地文件的编码格式

那我们如何才能知道本地文件的编码格式是什么样的呢?

在这里插入图片描述

4.3 查看idea中的编码格式

在我们查看输入到idea中 如果代码出现乱码的问题 该怎么解决?

在这里插入图片描述

查看idea默认的项目编码格式 我们发现 项目指定的编码格式是UTF-8 而我们展出

在这里插入图片描述

4.4 转换流模型图

在这里插入图片描述

在这里插入图片描述

4.5 转换流读写数据

构造方法

IDEA中默认字符编码格式为:UTF-8

方法名说明
InputStreamReader(InputStream in)使用默认字符编码创建InputStreamReader对象
InputStreamReader(InputStream in,String chatset)使用指定的字符编码创建InputStreamReader对象
OutputStreamWriter(OutputStream out)使用默认字符编码创建OutputStreamWriter对象
OutputStreamWriter(OutputStream out, String charset)使用指定的字符编码创建OutputStreamWriter对象

代码展示:

 public static void main(String[] args) throws IOException {
        File file1 = new File("E:\\file\\aa\\image\\a.txt");
        File file2 = new File("src/com/itgaohe/123.txt");
//        InputStreamReader isr = new InputStreamReader(new FileInputStream(file1));//utf-8
        InputStreamReader isr = new InputStreamReader(new FileInputStream(file1), "GBK");
//        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(file2));//UTF-8
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(file2),"GBK");
        
        int len = -1;
        while ((len = isr.read()) != -1) {
            osw.write(len);
        }
        osw.close();
        isr.close();
    }

4.6 转换流的使用场景

在JDK11之前,指定编码读写

JDK11之后,

FileReader(File file, Charset charset):创建一个新的FileReader,给出File读取和创建charset
FileReader(String fileName, Charset charset):创建一个给定文件名称的FileReader,给出File读取和创建charset

【注意】:
charset需要通过Charset.forName(“”)获取

4.7 小结

转换流就是来进行字节流和字符流之间转换的

InputStreamReader是从字节流到字符流的桥梁

它读取字节,并使用指定的编码将其解码为字符。

它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集。

OutputStreamWriter是从字符流到字节流的桥梁

是从字符流到字节流的桥梁,使用指定的编码将写入的字符编码为字节。

它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集。

5.对象操作流

5.1 案例引入

案例:用对象操作流读写多个对象

需求:创建多个Javabean类对象到文件中,再次读取到内存。

思路:
创建学生对象
利用对象操作输出流写到本地
利用对象操作输入流读到内存

代码展示:

/**
 *  read():
 *      读取到文件末尾返回值是 -1
 *  readLine():
 *      读取到文件的末尾返回值 null
 *  readObject():
 *      读取到文件的末尾 直接抛出异常java.io.EOFException
 *  如果要序列化的对象有多个,不建议直接将多个对象序列化到文件中,因为反序列化时容易出异常
 *      建议: 将要序列化的多个对象存储到集合中,然后将集合序列化到文件中
 */
public class ObjectStream2 {
    public static void main(String[] args) throws Exception {
        // 序列化
        //1.创建序列化流对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src\\com\\itgaohe\\123.txt"));
        ArrayList<Student> arrayList = new ArrayList<>();
        //2.创建多个学生对象
        Student s01 = new Student("佟丽娅",30,"女");
        Student s02 = new Student("王宝强",30,"男");
        //3.将学生对象添加到集合中
        arrayList.add(s01);
        arrayList.add(s02);
        //4.将集合对象序列化到文件中
        oos.writeObject(arrayList);
        oos.close();
        // 反序列化
        //5.创建反序列化流对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src\\com\\itgaohe\\123.txt"));
        //6.将文件中的对象数据,读取到内存中
        Object obj = ois.readObject();
        ArrayList<Student> list = (ArrayList<Student>)obj;
        ois.close();
        for (Student s : list) {
            System.out.println(s.getName() + "," + s.getAge());
        }
    }
}

5.2 对象操作流的特点

可以把对象以字节的形式写到本地文件,直接打开文件,是读不懂的,需要再次用对象操作流读到内存中。

5.3 了解对象操作流

在这里插入图片描述

5.4 对象操作流模型图

在这里插入图片描述

5.5 对象操作流概述

对象操作流分为两类:对象操作输入流和对象操作输出流
对象操作输出流(对象序列化流):就是将对象写到本地文件中,或者在网络中传输对象
对象操作输入流(对象反序列化流):把写到本地文件中的对象读到内存中,或者接收网络中传输的对象

5.6 对象序列化反序列化概述

什么是对象的序列化和反序列化?

对象序列化介绍

  • 对象序列化:就是将对象保存到磁盘中,或者在网络中传输对象
  • 这种机制就是使用一个字节序列表示一个对象,该字节序列包含:对象的类型、对象的数据和对象中存储的属性等信息
  • 字节序列写到文件之后,相当于文件中持久保存了一个对象的信息
  • 反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化

对象序列化流: ObjectOutputStream

  • 将Java对象的原始数据类型和图形写入OutputStream。 可以使用ObjectInputStream读取(重构)对象。 可以通过使用流的文件来实现对象的持久存储。 如果流是网络套接字流,则可以在另一个主机上或另一个进程中重构对象 。
  • 只有支持java.io.Serializable接口的对象才能写入流。
  • writeObject方法用于将对象写入流。

5.7 对象操作输出流

对象操作输出流(对象序列化流):就是将对象写到本地文件中,或者在网络中传输对象
构造方法

方法名说明
ObjectOutputStream(OutputStream out)创建一个写入指定的OutputStream的ObjectOutputStream

序列化对象的方法

方法名说明
void writeObject(Object obj)将指定的对象写入ObjectOutputStream

注意事项

  • 一个对象要想被序列化,该对象所属的类必须必须实现Serializable 接口
  • Serializable是一个标记接口,实现该接口,不需要重写任何方法

5.8 对象操作输入流

对象操作输入流(对象反序列化流):把写到本地文件中的对象读到内存中,或者接收网络中传输的对象
构造方法

方法名说明
ObjectInputStream(InputStream in)创建从指定的InputStream读取的ObjectInputStream

反序列化对象的方法

方法名说明
Object readObject()从ObjectInputStream读取一个对象

代码展示:

Student

public class Student implements Serializable {
    private String name;
    private int age;
    private String sex;
    //getset 有参无参 toStirng 实现序列化接口
}

Test

public static void main(String[] args) throws IOException, ClassNotFoundException {
        //3.创建对象
        Student student = new Student("杨金辉",18,"男");
        //1.创建对象字节输出流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src\\com\\itgaohe\\123.txt"));
       //2.将对象输出到 文件中  //存储的是对象在txt文件中
        oos.writeObject(student);
       
        //1.创建对象输入流
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src\\com\\itgaohe\\123.txt"));
        //2.读取txt文件中的对象
        Object object = ois.readObject();
        //3.输出对象
        System.out.println(object.toString());
        ois.close();
        oos.close();
    }

5.9 对象操作流注意事项

注意:

  1. 对象流不仅可以读写对象,还可以读写基本数据类型。
  2. 使用对象流读写对象时,该对象必须序列化与反序列化。
  3. 系统提供的类(如Date等)已经实现了序列化接口,自定义类必须手动实现序列化接口。

代码展示:

public static void main(String[] args) throws IOException {
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/com/itgaohe/123.txt"));
    oos.writeInt(123);
    oos.writeObject("灰灰真帅!");
    oos.writeObject(new Date());
    oos.writeObject(new Student());//java.io.NotSerializableException
    oos.close();
}

5.10 对象操作流问题

问题引入:

用对象序列化流序列化了一个对象后,我们再去修改对象所属的Javabean类,比如添加一个属性,读取数据会不会出问题呢?

会出问题,会抛出InvalidClassException异常

代码展示–先做如下操作:

1.对学生对象进行序列化存储

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/com/itgaohe/123.txt"));
oos.writeObject(new Student("灰灰",18,"nan"));
oos.close();

2.将 序列化存储隐掉,在学生类中添加一个新的字段 同时打开反序列化存储

public class Student implements Serializable {
    private String name;
    private int age;
    private String sex;
    private String number;//新加字段
    ...
    }
public static void main(String[] args) throws IOException, ClassNotFoundException {
//        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/com/itgaohe/123.txt"));
//        oos.writeObject(new Student("灰灰",18,"nan"));//java.io.NotSerializableException
//        oos.close();
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src/com/itgaohe/123.txt"));
        System.out.println(ois.readObject());
        ois.close();
    }

3.控制台报错 java.io.InvalidClassException

Exception in thread "main" java.io.InvalidClassException: com.itgaohe.test07.Student; local class incompatible: stream classdesc serialVersionUID = -3749205021108360658, local class serialVersionUID = -1984186709931597183

解决方案:

1.给对象所属的类加一个serialVersionUID

private static final long serialVersionUID = 42L;

2.自动生成
在这里插入图片描述

在这里插入图片描述

如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?

给该成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程

6.Properties

6.1 Properties概述:

是一个Map体系的集合类 继承了Hashtable implements Map接口

Properties中有跟IO相关的方法

作用:只存字符串!!!

6.2 Properties方法

Properties作为集合的特有方法:

方法名说明
Object setProperty(String key, String value)设置集合的键和值,都是String类型,底层调用Hashtable方法 put
String getProperty(String key)使用此属性列表中指定的键搜索属性
SetstringPropertyNames()从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串

6.3练习: Properties作为Map集合的使用(增删改查)

public static void main(String[] args) {
    //增
    Properties properties = new Properties();
    properties.setProperty("大哥","123");
    System.out.println(properties);
     //删
        p.remove("username");

      //改
        p.setProperty("username","lisi");

        //查-
        System.out.println(p.getProperty("username"));

        //遍历   -- 获取keys
        Set<Object> keySet = p.keySet();
        for (Object key : keySet) {
            Object value = p.get(key);
            System.out.println(key + "---" + value);
        }
        System.out.println("==========");
        //stringPropertyNames() 获取keys  string类型
        Set<String> names = p.stringPropertyNames();
        for (String key : names) {
            String property = p.getProperty(key);
            System.out.println(key+"--"+property);
        }
}

6.4 Properties和IO流结合的方法

方法名说明
void load(InputStream inStream)从输入字节流读取属性列表(键和元素对),+p集合中存入的内容
void load(Reader reader)从输入字符流读取属性列表(键和元素对)
void store(Writer writer, String comments)将此属性列表(键和元素对)写入此 Properties表中,以适合使用load(Reader)方法的格式写入输出字符流

代码展示:

a.properties

jdbc.username=root
jdbc.password=1234
public static void main(String[] args) throws IOException {
        Properties properties = new Properties();
        properties.setProperty("gaoheday01", "张飞");
        properties.setProperty("gaoheday02", "张飞2");
        properties.setProperty("gaoheday03", "张飞3");
//        FileReader fr = new FileReader("src/com/itgaohe/a.properties");
//        properties.load(fr);
//        properties.store(new FileWriter("src/com/itgaohe/b.properties"),"备注:");


        InputStream is = new FileInputStream("src/com/itgaohe/a.properties");
        properties.load(is);
        properties.store(new FileOutputStream("src/com/itgaohe/b.properties"),"");
        Set<String> set = properties.stringPropertyNames();
        for (String s : set) {
           System.out.println(s+":"+properties.getProperty(s));
        }
    }

6.5 案例

案例需求
在Properties文件中手动写上用户名密码,读取到集合中,将该数据封装成用户对象,写到本地文件.

  • 实现步骤
    • 创建Properties集合,将本地文件中的数据加载到集合中.
    • 获取集合中的键值对数据,封装到用户对象中.
    • 创建序列化流对象,将用户对象序列化到本地文件中.

代码演示:

a.properties

jdbc.username=root
jdbc.password=1234

test

public static void main(String[] args) throws IOException {
    //1.创建Properties集合 将本地文件中的数据加载到集合中
    Properties prop = new Properties();
    //2.创建字符输入流 读取配置文件中的信息
    FileReader fr = new FileReader("src/com/itgaohe/a.properties");
    //3.用集合对象 加载 配置文件
    prop.load(fr);
    fr.close();
    //4.获取Properties集合中的键值对数据 封装到对象中
    String username = prop.getProperty("jdbc.username");
    String password = prop.getProperty("jdbc.password");
    User user = new User(username, Integer.parseInt(password));
    //3.创建序列化输出流 输出到本地中
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/com/itgaohe/123.txt"));
    oos.writeObject(user);
    oos.close();
}

etProperty(“gaoheday03”, “张飞3”);
// FileReader fr = new FileReader(“src/com/itgaohe/a.properties”);
// properties.load(fr);
// properties.store(new FileWriter(“src/com/itgaohe/b.properties”),“备注:”);

    InputStream is = new FileInputStream("src/com/itgaohe/a.properties");
    properties.load(is);
    properties.store(new FileOutputStream("src/com/itgaohe/b.properties"),"");
    Set<String> set = properties.stringPropertyNames();
    for (String s : set) {
       System.out.println(s+":"+properties.getProperty(s));
    }
}

## 6.5  案例

**案例需求**
在Properties文件中手动写上用户名密码,读取到集合中,将该数据封装成用户对象,写到本地文件.

- 实现步骤
  - 创建Properties集合,将本地文件中的数据加载到集合中.
  - 获取集合中的键值对数据,封装到用户对象中.
  - 创建序列化流对象,将用户对象序列化到本地文件中.



**代码演示:**

**a.properties**

```java
jdbc.username=root
jdbc.password=1234

test

public static void main(String[] args) throws IOException {
    //1.创建Properties集合 将本地文件中的数据加载到集合中
    Properties prop = new Properties();
    //2.创建字符输入流 读取配置文件中的信息
    FileReader fr = new FileReader("src/com/itgaohe/a.properties");
    //3.用集合对象 加载 配置文件
    prop.load(fr);
    fr.close();
    //4.获取Properties集合中的键值对数据 封装到对象中
    String username = prop.getProperty("jdbc.username");
    String password = prop.getProperty("jdbc.password");
    User user = new User(username, Integer.parseInt(password));
    //3.创建序列化输出流 输出到本地中
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/com/itgaohe/123.txt"));
    oos.writeObject(user);
    oos.close();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值