IO流

235 阅读25分钟

13.3 IO流

13.3.1 IO流作用和分类

三、IO流
1、IO流的作用
数据无论是写到文件、还是发送到网络、还是其他目的地,只要有数据的输出,就必须用到IO流。
或者从文件读取、从网络接收、从其他数据源(键盘输入),只要有数据的读取,也必须使用IO流。
​
Scanner input = new Scanner(System.in);
System.out.println();
​
即IO流用了读、写数据。
​
2、IO流的分类
(1)按照方向分:
输入流:        ==> read(),next(),nextInt()
    从其他地方到当前的程序,参照物是当前的程序。
    从键盘输入数据到当前程序,System.in 输入流
    从文件中读取数据到当前程序,FileInputStream 输入流
    ....
​
输出流       ==> write/print
    从当前程序到控制台,System.out  输出流
    从当前程序把数据写到文件中,相当于输出到磁盘,FileOutputStream 输出流
​
(2)按照处理数据的方式、单位不同
字节流:以字节为单位,最小可以处理1个字节
字符流:以字符为单位,最小可以处理1个字符
    1个字符可能是1~4个字节,和具体的编码方式以及和当前字符的编码值有关。
​
    字符流只能处理纯文本数据,即字符串。
    字节流可以处理任意类型的数据。包括基本数据类型、字符串、图片、音频、视频等。
​
(3)按照IO流的角色不同
节点流:例如文件是一个数据的节点,网络终端(网页、数据库、服务器等)也是一个数据的结点,内存中一个字节数组、字符数组都可以是数据的节点。
处理流(也称为装饰流):对IO流操作的一个装饰作用,
    例如:缓冲流(提高读写效率)
        对象流(对对象进行序列化和反序列化)
        转换流(对文本进行解码和编码)
​
    所有的IO流操作必须要有节点流,但是处理流可以没有,如果有对数据更加复杂的操作要求,需要加1~n个处理流。
​
3、无论是那种分类,都是从4个最基本的基类中派生出去
(1)InputStream     ==>所有字节输入流的父类
(2)OutputStream    ==>所有字节输出流的父类
(3)Reader          ==>所有字符输入流的父类
(4)Writer          ==>所有字符输出流的父类
​
​
节点流:
    FileInputStream、FileOutputStream、FileReader、FileWriter  ==>操作文件
    ByteArrayInputStream、ByteArrayOutputStream               ==>操作字节数组
    CharArrayReader、CharArrayWriter                          ==>操作字符数组
    StringReader、StringWriter                                ==>操作字符串
其余:
    BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter   ==>缓冲流
    InputStreamReader         ==>把字节数据转为字符数据  解码
    OutputStreamWriter       ==>把字符数据转为字节数据   编码
    ObjectInputStream 、ObjectOutpuStream

13.3.2 文件IO流

一、文件IO流
FileInputStream:文件字节输入流,以字节为单位从文件中读取数据到当前程序
FileOutputStream:文件字节输出流,以字节为单位把数据写到文件中,保存到文件中
FileReader:文件字符输入流,以字符为单位从纯文本文件中读取数据到当前程序
FileWriter:文件字符输出流,以字符为单位把当前程序中的数据写到纯文本文件中

1、FileWriter

​
1、FileWriter:文件字符输出流
​
步骤:
(1)先创建一个IO流对象
    FileWriter fw = new FileWriter("aaa.txt");
(2)输出数据:调用write
    fw.write("我们下周JavaSE就要学完了!");
(3)关闭资源
    fw.close()
​
​
A:输出时,这个文件不存在,会自动创建。
B:如果该IO流对象,后面还要用,当前数据想要及时出去,就要用flush
C:默认FileWriter是覆盖模式,如果输出之前,文件已存在,新输出的内容会覆盖原来的文件
  如果要追加输出的话,需要在创建FileWriter对象时,指定用追加模式。
   FileWriter fw = new FileWriter("aaa.txt",true);
D:FileWriter对应的需要是一个文件,而且最好是个纯文本文件。不能是文件夹。
​
什么是纯文本文件?
    .txt,.java, .css, .js, .html等
​
import org.junit.Test;
​
import java.io.FileWriter;
import java.io.IOException;
​
public class TestFileWriter {
    @Test
    public void test1() throws IOException {
        //把下面这句话“我们下周JavaSE就要学完了!”保存到  atguigu.txt文件中
        FileWriter fw = new FileWriter("aaa.txt");//这是一个相对路径
        //如果是JUnit的@Test,相对于当前的模块 day0403_teacher_code
        fw.write("我们下周JavaSE就要学完了!1");
//        fw.flush();//手动刷新
        //如果后面不再使用fw对象了,在最后需要close关闭
        fw.close();//在关闭时,会自动刷新一下
    }
​
    @Test
    public void test2() throws IOException {
        //把下面这句话“我们下周JavaSE就要学完了!”保存到  atguigu.txt文件中
        FileWriter fw = new FileWriter("aaa.txt");//这是一个相对路径
        //如果是JUnit的@Test,相对于当前的模块 day0403_teacher_code
        fw.write("我们下周JavaSE就要学完了!2");
        fw.flush();//手动刷新
        //如果后面不再使用fw对象了,在最后需要close关闭
​
        fw.write("hello");
​
        fw.close();
    }
​
    @Test
    public void test3() throws IOException {
        FileWriter fw = new FileWriter("aaa.txt",true);//表示追加模式输出
        //如果是JUnit的@Test,相对于当前的模块 day0403_teacher_code
        fw.write("我们下周JavaSE就要学完了!3");
        fw.close();
    }
​
    @Test
    public void test4() throws IOException {
        FileWriter fw = new FileWriter("atguigu");
        //java.io.FileNotFoundException: atguigu (拒绝访问。)  atguigu是一个文件夹,不能输出数据
        fw.write("hello");
        fw.close();
    }
​
    @Test
    public void test5() throws IOException {
        FileWriter fw = new FileWriter("chai.jpg");//不是纯文本文件,没有意义
        fw.write("beautiful");
        fw.close();
    }
}
​

2、FileReader

​
2、FileReader:读
​
步骤:
(1)先创建一个IO流对象
    FileReader fr = new FileReader("aaa.txt");
(2)输出数据:调用write
    fr.read();  默认读取1个字符,返回当前字符的编码值,如果已经到达流末尾,返回-1
    fr.read(char[] data):一次读取data.length个,如果流中没有data.length个字符,有几个读取几个,如果已经到达流末尾,返回-1
                    返回本次读取的字符数量。
(3)关闭资源
    fr.close()
​
A:读取文件时,如果文件不存在,会报FileNotFoundException
B:FileReader 对应的需要是一个文件,而且最好是个纯文本文件。   不能是文件夹。
import org.junit.Test;
​
import java.io.FileReader;
import java.io.IOException;
​
public class TestFileReader {
    @Test
    public void test1() throws IOException {
        FileReader fr = new FileReader("aaa.txt");
        System.out.println(fr.read());//25105  读第1个字符
        System.out.println((char)fr.read());//们  读第2个字符
        fr.close();
    }
​
    @Test
    public void test2(){
        System.out.println((int)'我');//25105
    }
​
    @Test
    public void test3() throws IOException {
        FileReader fr = new FileReader("atguigu.txt");
        while(true){
            int code = fr.read();
            if(code == -1){
                break;
            }
            System.out.print((char)code);
        }
        fr.close();
    }
​
    @Test
    public void test4() throws IOException {
        FileReader fr = new FileReader("aaa.txt");
        char[] data = new char[10];
        int len;
        while((len = fr.read(data)) != -1){
            System.out.println("本次:" + len);
            System.out.println(new String(data,0,len));
        }
        fr.close();
    }
}
3、FileInputStream:
步骤:
(1)先创建一个IO流对象
    FileInputStream fis = new FileInputStream("iotest/aaa.txt");
(2)读取数据:调用read
    int read():一次读取一个字节,返回读取的字节值,如果已经到达流末尾,继续读,返回-1
    int read(byte[] data):一次读取多个字节,最多读取data.length个字节,返回本次读取的字节数量,如果已经到达流末尾,继续读,返回-1
                读取的数据放到了data数组中,这个数据往往是一个重复使用的数组。
(3)关闭资源
    fis.close()
import org.junit.Test;
​
import java.io.FileInputStream;
import java.io.IOException;
​
public class TestFileInputStream {
    @Test
    public void test1() throws IOException {
        FileInputStream fis = new FileInputStream("iotest/aaa.txt");
        System.out.println(fis.read());
        fis.close();
    }
​
    @Test
    public void test2(){
        System.out.println((int)'中');
    }
​
    @Test
    public void test3() throws IOException {
        FileInputStream fis = new FileInputStream("iotest/aaa.txt");
        byte[] data = new byte[10];
        int len;
        while((len = fis.read(data)) != -1){
            System.out.print(new String(data,0,len));
        }
        fis.close();
    }
}
​

4、FileOutputStream

4、FileOutputStream:
步骤:
(1)先创建一个IO流对象
    FileOutputStream fos = new FileOutputStream("iotest/aaa.txt");
(2)输出数据:调用write
    fos.write(字节数组)
(3)关闭资源
    fos.close()
​
package com.atguigu.file;
​
import org.junit.Test;
​
import java.io.FileOutputStream;
import java.io.IOException;
​
public class TestFileOutputStream {
    @Test
    public void test1() throws IOException {
        FileOutputStream fos = new FileOutputStream("iotest/aaa.txt");
        fos.write("大家注意了,别睡着了!".getBytes());//调用String类的getBytes方法,将字符串转为字节
        fos.close();
    }
}
​

5、文件复制


​
import java.io.*;
​
public class FileTools {
​
    /*
    用于删除非空目录
     */
    public static void forceDir(File dir){
​
        //dir可能是一个文件,可能是一个空目录,也可能是一个非空目录
        if(dir.isDirectory()){//如果是非空目录,先删除它的下一级,再删除自己
            //删除目录的下一级
            //先获取它的下一级
            File[] allSubFile = dir.listFiles();
            for (File sub : allSubFile) {//sub代表dir的下一级,下一级可能是一个文件,可能是一个空目录,也可能是一个非空目录
                //删除sub
                forceDir(sub);
            }
        }
        dir.delete();//删除自己,自己可以是是一个文件,可以是一个空目录
    }
​
    public static long getDirectoryLength(File dir){
        //按照Java的好习惯来说,对于引用数据类型的参数,最好都加非空判断
/*        if(dir == null){
            return 0;
        }*/
​
        if(dir != null && dir.isFile()){
            return dir.length();
        }else if(dir != null && dir.isDirectory()){
            //把该文件夹下的所有下一级的大小累加起来
            long sum = 0;
            //先获取它的下一级
            File[] allSubFile = dir.listFiles();
            if(allSubFile != null) {
                for (File sub : allSubFile) {//sub代表dir的下一级,下一级可能是一个文件,可能是一个空目录,也可能是一个非空目录
                    //                sum += sub的大小;
                    sum += getDirectoryLength(sub);
                }
            }
​
            return sum;
        }
        return 0;
    }
​
    /*
    复制一个文件
    要求源文件srcFile必须存在的,但是destFile可以不存在,不存在会自动创建。
    要求srcFile和destFile两个File对象代表的都必须是文件,不能是文件夹。
    srcFile和destFile文件可以是任意类型的文件,不一定非得是纯文本文件
     */
    public static void copyFile(File srcFile, File destFile) throws IOException {
        if(srcFile == null || destFile == null){
            return;
        }
        if(srcFile.isDirectory() || destFile.isDirectory()){
            return;
        }
​
        //从srcFile里面读数据,写到destFile文件中
        FileInputStream fis = new FileInputStream(srcFile);
        FileOutputStream fos = new FileOutputStream(destFile);
​
        byte[] data = new byte[1024*1024*1024];
        int len;
        while((len = fis.read(data)) != -1){//比喻:相当于每次用data这个盆装一盆水,从srcFile这个 入水管道,搬到 destFile出水管道
            fos.write(data, 0 ,len);//本次用盆装了多少,就倒多少
        }
​
        fos.close();
        fis.close();
    }
}
​
import org.junit.Test;
​
import java.io.File;
import java.io.IOException;
​
public class TestFileTools {
    @Test
    public void test1() throws IOException {
        File srcFile = new File("iotest/集合框架图.jpg");
        File destFile = new File("d:/集合.jpg");
​
        FileTools.copyFile(srcFile,destFile);
​
    }
    @Test
    public void test2() throws IOException {
        File srcFile = new File("iotest/day0403_04哈希值.avi");
        File destFile = new File("d:/集合.avi");
​
        FileTools.copyFile(srcFile,destFile);
    }
}

6、文件夹复制(了解)


​
import java.io.*;
​
public class FileTools {
​
       /*
    复制一个文件
    要求源文件srcFile必须存在的,但是destFile可以不存在,不存在会自动创建。
    要求srcFile和destFile两个File对象代表的都必须是文件,不能是文件夹。
    srcFile和destFile文件可以是任意类型的文件,不一定非得是纯文本文件
     */
    public static void copyFile(File srcFile, File destFile) throws IOException {
        if(srcFile == null || destFile == null){
            return;
        }
        if(srcFile.isDirectory() || destFile.isDirectory()){
            return;
        }
​
        //从srcFile里面读数据,写到destFile文件中
        FileInputStream fis = new FileInputStream(srcFile);
        FileOutputStream fos = new FileOutputStream(destFile);
​
        byte[] data = new byte[1024*8];
        int len;
        while((len = fis.read(data)) != -1){//比喻:相当于每次用data这个盆装一盆水,从srcFile这个 入水管道,搬到 destFile出水管道
            fos.write(data, 0 ,len);//本次用盆装了多少,就倒多少
        }
​
        fos.close();
        fis.close();
    }
​
    /*
    把srcFile文件复制到destDir文件夹中
     */
    public static void copyFileToDirectory(File srcFile, File destDir) throws IOException {
        if(srcFile == null || destDir == null){
            return;
        }
        if(srcFile.isDirectory() || destDir.isFile()){
            return;
        }
        if(!destDir.exists()){//如果destDir文件夹不存在,就创建它
            destDir.mkdirs();
        }
        //构造器:File(File parent, String child)
        File destFile = new File(destDir, srcFile.getName());
        copyFile(srcFile, destFile);
    }
​
    /*
    srcDir是一个文件夹,destDir也是一个文件夹目录,
    例如:d:/download文件夹,复制  d:/temp/download
    srcDir代表的是 d:/download文件夹
    dest代表的是 d:/temp
     */
    public static void copyDirectory(File srcDir, File destDir) throws IOException {
        if(srcDir == null || destDir == null){
            return;
        }
        if(srcDir.isFile() && destDir.isFile()){
            copyFile(srcDir,destDir);
        }else if(srcDir.isFile() && destDir.isDirectory()){
            copyFileToDirectory(srcDir,destDir);
        }else if(srcDir.isDirectory() && destDir.isFile()){
            return;
        }else if(srcDir.isDirectory() && destDir.isDirectory()){
            //(1)在destDir中创建srcDir这个层次的文件
            //比喻:在d:/temp 创建一个 download文件夹,最终效果 d:/temp/download
            File dir = new File(destDir, srcDir.getName());
            dir.mkdir();
​
            //(2)获取原来的文件夹的下一级
            File[] allSubFiles = srcDir.listFiles();
            for (File sub : allSubFiles) {
                copyDirectory(sub, dir);
            }
​
        }
    }
​
}
​
    @Test
    public void test3() throws IOException {
        File srcFile = new File("iotest/集合框架图.jpg");
        File destDir = new File("d:/");
        FileTools.copyFileToDirectory(srcFile,destDir);
    }
​
    @Test
    public void test4() throws IOException {
        File srcDir = new File("D:\Download");
        File destDir = new File("D:\temp");
        FileTools.copyDirectory(srcDir,destDir);
    }

7、文件夹剪切(了解)


​
import java.io.*;
​
public class FileTools {
    /*
    复制一个文件
    要求源文件srcFile必须存在的,但是destFile可以不存在,不存在会自动创建。
    要求srcFile和destFile两个File对象代表的都必须是文件,不能是文件夹。
    srcFile和destFile文件可以是任意类型的文件,不一定非得是纯文本文件
     */
    public static void copyFile(File srcFile, File destFile) throws IOException {
        if(srcFile == null || destFile == null){
            return;
        }
        if(srcFile.isDirectory() || destFile.isDirectory()){
            return;
        }
​
        //从srcFile里面读数据,写到destFile文件中
        FileInputStream fis = new FileInputStream(srcFile);
        FileOutputStream fos = new FileOutputStream(destFile);
​
        byte[] data = new byte[1024*8];
        int len;
        while((len = fis.read(data)) != -1){//比喻:相当于每次用data这个盆装一盆水,从srcFile这个 入水管道,搬到 destFile出水管道
            fos.write(data, 0 ,len);//本次用盆装了多少,就倒多少
        }
​
        fos.close();
        fis.close();
    }
​
  
​
    /*
    把srcFile文件复制到destDir文件夹中
     */
    public static void copyFileToDirectory(File srcFile, File destDir) throws IOException {
        if(srcFile == null || destDir == null){
            return;
        }
        if(srcFile.isDirectory() || destDir.isFile()){
            return;
        }
        if(!destDir.exists()){//如果destDir文件夹不存在,就创建它
            destDir.mkdirs();
        }
        //构造器:File(File parent, String child)
        File destFile = new File(destDir, srcFile.getName());
        copyFile(srcFile, destFile);
    }
​
    /*
    srcDir是一个文件夹,destDir也是一个文件夹目录,
    例如:d:/download文件夹,复制  d:/temp/download
    srcDir代表的是 d:/download文件夹
    dest代表的是 d:/temp
     */
    public static void copyDirectory(File srcDir, File destDir) throws IOException {
        if(srcDir == null || destDir == null){
            return;
        }
        if(srcDir.isFile() && destDir.isFile()){
            copyFile(srcDir,destDir);
        }else if(srcDir.isFile() && destDir.isDirectory()){
            copyFileToDirectory(srcDir,destDir);
        }else if(srcDir.isDirectory() && destDir.isFile()){
            return;
        }else if(srcDir.isDirectory() && destDir.isDirectory()){
            //(1)在destDir中创建srcDir这个层次的文件
            //比喻:在d:/temp 创建一个 download文件夹,最终效果 d:/temp/download
            File dir = new File(destDir, srcDir.getName());
            dir.mkdir();
​
            //(2)获取原来的文件夹的下一级
            File[] allSubFiles = srcDir.listFiles();
            for (File sub : allSubFiles) {
                copyDirectory(sub, dir);
            }
​
        }
    }
​
    public static void cutDirectory(File srcDir, File destDir) throws IOException {
        if(srcDir == null || destDir == null){
            return;
        }
        if(srcDir.isFile() && destDir.isFile()){
            copyFile(srcDir,destDir);
        }else if(srcDir.isFile() && destDir.isDirectory()){
            copyFileToDirectory(srcDir,destDir);
        }else if(srcDir.isDirectory() && destDir.isFile()){
            return;
        }else if(srcDir.isDirectory() && destDir.isDirectory()){
            //(1)在destDir中创建srcDir这个层次的文件
            //比喻:在d:/temp 创建一个 download文件夹,最终效果 d:/temp/download
            File dir = new File(destDir, srcDir.getName());
            dir.mkdir();
​
            //(2)获取原来的文件夹的下一级
            File[] allSubFiles = srcDir.listFiles();
            for (File sub : allSubFiles) {
                cutDirectory(sub, dir);
            }
​
        }
        //复制完,要删除源文件
        srcDir.delete();
    }
​
}
​
   @Test
    public void test5() throws IOException {
        File srcDir = new File("D:\Download_副本");
        File destDir = new File("D:\temp");
        FileTools.cutDirectory(srcDir,destDir);
    }

13.3.3 缓冲流

1、缓冲IO流的类型:
BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter
​
2、缓冲IO流的作用
(1)最基本的作用:给IO流增加缓冲效应,提高效率
(2)BufferedReader、BufferedWriter可以给字符流增加一个辅助的功能,可以按行读、写
​
3、注意
(1)缓冲流无法独立使用,必须依赖于其他的IO流,例如文件IO流等
(2)BufferedInputStream只能包装和处理InputStream系列的IO流
    BufferedOutputStream只能包装和处理OutputStream系列的IO流
    BufferedReader只能包装和处理Reader系列的IO流
    BufferedWriter只能包装和处理Writer系列的IO流

​
import org.junit.Test;
​
import java.io.*;
​
public class TestBufferedInputAndOutputStream {
    @Test
    public void test1() throws IOException {
        FileInputStream fis = new FileInputStream("iotest/aa.txt");
        BufferedInputStream bis = new BufferedInputStream(fis);
​
        FileOutputStream fos = new FileOutputStream("iotest/aa_副本.txt");
        BufferedOutputStream bos = new BufferedOutputStream(fos);
​
        //iotest/atguigu.txt ==> fis => bis ==> data ==> bos ==> fos ==> iotest/atguigu_副本.txt
        byte[] data = new byte[10];
        int len;
        while((len = bis.read(data)) != -1){
            bos.write(data,0,len);
        }
​
        bos.close();
        fos.close();
        bis.close();
        fis.close();
    }
​
    @Test
    public void test2() throws IOException {
        FileReader fr = new FileReader("iotest/aa.txt");
        BufferedReader br = new BufferedReader(fr);
​
        FileWriter fw = new FileWriter("iotest/aa_副本.txt");
        BufferedWriter bw = new BufferedWriter(fw);
​
        //iotest/atguigu.txt ==> fr => br ==> line ==> bw ==> fw ==> iotest/atguigu_副本.txt
        String line;
        while((line = br.readLine()) != null){
            bw.write(line);
            bw.newLine();
        }
​
        bw.close();
        fw.close();
        br.close();
        fr.close();
    }
}
​

​
import java.io.*;
​
public class FileTools {
​
    public static void copyFileNoBuffer(File srcFile, File destFile) throws IOException {
        if(srcFile == null || destFile == null){
            return;
        }
        if(srcFile.isDirectory() || destFile.isDirectory()){
            return;
        }
​
        //从srcFile里面读数据,写到destFile文件中
        FileInputStream fis = new FileInputStream(srcFile);
        FileOutputStream fos = new FileOutputStream(destFile);
​
        byte[] data = new byte[1024*8];
        int len;
        while((len = fis.read(data)) != -1){//比喻:相当于每次用data这个盆装一盆水,从srcFile这个 入水管道,搬到 destFile出水管道
            fos.write(data, 0 ,len);//本次用盆装了多少,就倒多少
        }
​
        fos.close();
        fis.close();
    }
​
    /*
    复制一个文件
    要求源文件srcFile必须存在的,但是destFile可以不存在,不存在会自动创建。
    要求srcFile和destFile两个File对象代表的都必须是文件,不能是文件夹。
    srcFile和destFile文件可以是任意类型的文件,不一定非得是纯文本文件
     */
    public static void copyFile(File srcFile, File destFile) throws IOException {
        if(srcFile == null || destFile == null){
            return;
        }
        if(srcFile.isDirectory() || destFile.isDirectory()){
            return;
        }
​
        //从srcFile里面读数据,写到destFile文件中
        FileInputStream fis = new FileInputStream(srcFile);
        FileOutputStream fos = new FileOutputStream(destFile);
​
        BufferedInputStream bis = new BufferedInputStream(fis);//默认缓冲区大小8192
        BufferedOutputStream bos =new BufferedOutputStream(fos);
​
        byte[] data = new byte[1024];
        int len;
        while((len = bis.read(data)) != -1){//比喻:相当于每次用data这个盆装一盆水,从srcFile这个 入水管道,搬到 destFile出水管道
            bos.write(data, 0 ,len);//本次用盆装了多少,就倒多少
        }
​
        bos.close();
        bis.close();
        fos.close();
        fis.close();
    }
​
}
​
    @Test
    public void test6() throws IOException {
        File srcFile = new File("iotest/day0403_04哈希值.avi");
        File destFile = new File("d:/集合1.avi");
​
        long start = System.currentTimeMillis();
        FileTools.copyFileNoBuffer(srcFile,destFile);
        long end = System.currentTimeMillis();
        System.out.println("耗时:" + (end-start));//耗时:3217  耗时:483
    }
​
    @Test
    public void test7() throws IOException {
        File srcFile = new File("iotest/day0403_04哈希值.avi");
        File destFile = new File("d:/集合2.avi");
​
        long start = System.currentTimeMillis();
        FileTools.copyFile(srcFile,destFile);
        long end = System.currentTimeMillis();
        System.out.println("耗时:" + (end-start));//耗时:512
    }

13.3.4 转换流

1、转换流
InputStreamReader:把字节数据转为字符数据,也称为解码IO流
OutputStreamWriter:把字符数据转为字节数据,也称为编码IO流
​
2、转换流的使用
(1)它俩不能独立使用,必须包装其他IO流使用
(2)它们仅限于操作纯文本数据
​

​
import org.junit.Test;
​
import java.io.*;
​
public class TestInputStreamReader {
    @Test
    public void test1()throws IOException {
        //文件:iotest/1.txt,它的文件编码是GBK
        //当前程序的编码是UTF-8
        FileInputStream fis  = new FileInputStream("iotest/1.txt");
        InputStreamReader isr = new InputStreamReader(fis,"GBK");
        //iotest/1.txt ==> fis(里面是字节流,GBK编码的字节流) ==>isr,转换,按照GBK进行解码==> 字符
        char[] data = new char[10];
        int len;
        while((len = isr.read(data)) != -1){
            System.out.println(new String(data,0,len));
        }
​
        isr.close();
        fis.close();
    }
​
    @Test
    public void test2()throws IOException{
        //文件:iotest/1.txt,它的文件编码是GBK
        //当前程序的编码是UTF-8
        FileInputStream fis  = new FileInputStream("iotest/1.txt");
        BufferedInputStream bis = new BufferedInputStream(fis);
        InputStreamReader isr = new InputStreamReader(bis,"GBK");
        BufferedReader br = new BufferedReader(isr);
        //iotest/1.txt ==> fis(里面是字节流,GBK编码的字节流)==>bis(字节流) ==>isr,转换,按照GBK进行解码==> 字符==>br==>按行读
        String str ;
        while((str = br.readLine()) != null){
            System.out.println(str);
        }
​
        br.close();
        isr.close();
        bis.close();
        fis.close();
    }
​
}
import org.junit.Test;
​
import java.io.*;
​
public class TestOutputStreamWriter {
    @Test
    public void test1()throws IOException {
        //输出一段话,追加到iotest/1.txt文件,这个文件的编码依然GBK
        //当前程序的编码是UTF-8
        FileWriter fw = new FileWriter("iotest/1.txt",true);//错误
        fw.write("尚硅谷");
        fw.close();
    }
​
    @Test
    public void test2()throws IOException {
        //输出一段话,追加到iotest/1.txt文件,这个文件的编码依然GBK
        //当前程序的编码是UTF-8
        FileOutputStream fos = new FileOutputStream("iotest/1.txt",true);
        OutputStreamWriter osw = new OutputStreamWriter(fos,"GBK");
        //程序中字符串"尚硅谷" ==>osw 字符==>转换,以GBK将字符转为字节==>字节==>fos==>iotest/1.txt
        osw.write("尚硅谷");
        osw.close();
        fos.close();
    }
​
    @Test
    public void test3()throws IOException {
        //输出一段话,追加到iotest/1.txt文件,这个文件的编码依然GBK
        //当前程序的编码是UTF-8
        FileOutputStream fos = new FileOutputStream("iotest/1.txt",true);
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        OutputStreamWriter osw = new OutputStreamWriter(bos,"GBK");
        BufferedWriter bw = new BufferedWriter(osw);
​
        //程序中字符串"练习" ==>osw 字符==>转换,以GBK将字符转为字节==>字节==>fos==>iotest/1.txt
        bw.newLine();
        bw.write("练习");
        bw.newLine();
​
        bw.close();
        osw.close();
        bos.close();
        fos.close();
    }
}
​

13.3.5 对象流

1、对象流读和写基本数据类型

两个对象IO流:
1、ObjectInputStream
2、ObjectOutputStream:输出各种数据类型的数据
​
作用:
1、读或写“基本数据类型”的各种数据
2、读或写引用数据类型的“对象”
​
注意:
1、它们不能独立使用,必须依赖于其他的IO流。
2、建议保存的文件后缀名自己命名,不要与现有的文件类型相同。
3、用ObjectInputStream读数据的顺序需要与用ObjectOutputStream写数据的顺序一致。

​
import org.junit.Test;
​
import java.io.*;
​
public class TestObject {
    @Test
    public void test1()throws IOException {
        String name = "巫师";
        int age = 300;
        char gender = '男';
        int energy = 5000;
        double price = 75.5;
        boolean relive = true;
​
        //把上面的数据保存到一个文件中,之后重新读取到程序中使用
        FileOutputStream fos = new FileOutputStream("iotest/game.txt");
//        fos.write(int),fos.write(byte[])处理不了其他的数据类型
        FileWriter fw = new FileWriter("iotest/game.txt");
        fw.write(name);
        fw.write(age+"");
        fw.write(gender+"");
        fw.write(energy+"");
        fw.write(price+"");
        fw.write(relive+"");
        fw.close();
​
    }
​
    @Test
    public void test2()throws IOException{
        FileReader fr = new FileReader("iotest/game.txt");
        char[] data = new char[10];
        int len;
        while((len = fr.read(data)) != -1){
            //如何区分每一项数据,占几个字符
        }
        fr.close();
    }
​
    @Test
    public void test3()throws IOException {
        String name = "巫师";
        int age = 300;
        char gender = '男';
        int energy = 5000;
        double price = 75.5;
        boolean relive = true;
​
        FileOutputStream fos = new FileOutputStream("iotest/game.aaa");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
​
        oos.writeUTF(name);
        oos.writeInt(age);
        oos.writeChar(gender);
        oos.writeInt(energy);
        oos.writeDouble(price);
        oos.writeBoolean(relive);
​
        oos.close();
        fos.close();
    }
​
    @Test
    public void test4()throws IOException{
        //比喻:自己的解码工具,播放器才能识别里面的数据
        //所以,建议使用ObjectOutputStream输出的文件数据,后缀名不要使用.txt等现有的文件的扩展名
        FileInputStream fis = new FileInputStream("iotest/game.aaa");
        ObjectInputStream ois = new ObjectInputStream(fis);
        
        //读的使用要注意,顺序与写的时候完全一致
        String str = ois.readUTF();
        int age = ois.readInt();
        char c = ois.readChar();
        int en = ois.readInt();
        double d = ois.readDouble();
        boolean b = ois.readBoolean();
​
        System.out.println(str);
        System.out.println(age);
        System.out.println(c);
        System.out.println(en);
        System.out.println(d);
        System.out.println(b);
​
        ois.close();
        fis.close();
    }
}

2、对象序列化与反序列化

两个对象IO流:
1、ObjectInputStream
2、ObjectOutputStream:输出各种数据类型的数据
​
作用:
1、读或写“基本数据类型”的各种数据
2、读或写引用数据类型的“对象”
​
注意:
1、它们不能独立使用,必须依赖于其他的IO流。
2、建议保存的文件后缀名自己命名,不要与现有的文件类型相同。
3、用ObjectInputStream读数据的顺序需要与用ObjectOutputStream写数据的顺序一致。
4、如果要用ObjectOutputStream输出引用数据类型的对象,要求该对象的类型必须实现java.io.Serializable接口
​
对象的序列化:把Java对象转为字节数据输出,序列化的IO流:ObjectOutputStream
对象的反序列化:把字节数据“重构”成一个Java对象,反序列化的IO流:ObjectInputStream
import java.io.Serializable;
​
public class Student implements Serializable {
    private String name;
    private  int age;
​
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
​
​
    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;
    }
​
    @Override
    public String toString() {
        return "name=" + name +", age=" + age;
    }
}
​
    @Test
    public void test5()throws IOException{
        Student stu = new Student("小刘", 25);
​
        FileOutputStream fos = new FileOutputStream("iotest/stu.aaa");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
​
        oos.writeObject(stu);//com.atguigu.object.Student类的对象java.io.NotSerializableException不支持序列化异常
​
        oos.close();
        fos.close();
    }
​
    @Test
    public void test6() throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream("iotest/stu.aaa");
        ObjectInputStream ois = new ObjectInputStream(fis);
​
        /*
        readObject方法可能发生ClassNotFoundException异常
         */
        Object obj = ois.readObject();//编译时readObject方法返回的是Object类型,实际的运行时类型是Student类型
        System.out.println(obj);
​
        ois.close();
        fis.close();
    }

3、序列化版本ID

两个对象IO流:
1、ObjectInputStream
2、ObjectOutputStream:输出各种数据类型的数据
​
作用:
1、读或写“基本数据类型”的各种数据
2、读或写引用数据类型的“对象”
​
注意:
1、它们不能独立使用,必须依赖于其他的IO流。
2、建议保存的文件后缀名自己命名,不要与现有的文件类型相同。
3、用ObjectInputStream读数据的顺序需要与用ObjectOutputStream写数据的顺序一致。
4、如果要用ObjectOutputStream输出引用数据类型的对象,要求该对象的类型必须实现java.io.Serializable接口
​
对象的序列化:把Java对象转为字节数据输出,序列化的IO流:ObjectOutputStream
对象的反序列化:把字节数据“重构”成一个Java对象,反序列化的IO流:ObjectInputStream
​
5、当某个类实现了Serializable接口之后,如果没有明确指定“序列化版本ID”,
那么每次修改类重新编译后都会自动产生1个新的“序列化版本ID”,
如果类描述信息中的“序列化版本ID”不一致,是无法正确的反序列化的。
解决:
    必须给实现了Serializable接口的类(例如Student类)确定一个“序列化版本ID”。
    “序列化版本ID”必须是private static final long serialVersionUID
​
“序列化版本ID”的值为多少合适呢?
(1)情况一:如果是第一次实现Serializable接口,该类的对象还没有被序列化过,那么“序列化版本ID”写什么值都可以。
(2)情况二:之前已经实现Serializable接口,该类的对象已经被序列化过了,那么要让“序列化版本ID”和上一次序列化时的版本ID值一致
例如:当我们修改了Student类之后,用这段程序重新读之前的stu.aaa文件,发生了如下异常:
          java.io.InvalidClassException(无效的类异常):
           com.atguigu.object.Student; (Student类)
           local class incompatible:(本地的 类 不兼容,不匹配)
            stream classdesc serialVersionUID = 7049296973331162856, (流中的类描述信息中的序列化版本ID)  (上次序列化的版本ID值)
            local class serialVersionUID = -1502441291691716068(本地的类描述信息中序列化版本ID)
​
import java.io.Serializable;
​
public class Student implements Serializable {
    private int id;
    private String name;
    private  int age;
    private static final long serialVersionUID = 7049296973331162856L;
​
    public Student(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
​
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
​
​
    public int getId() {
        return id;
    }
​
    public void setId(int id) {
        this.id = id;
    }
​
    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;
    }
​
    @Override
    public String toString() {
        return "id="+ id +
                ",name=" + name +
                ", age=" + age 
    }
​
}
    @Test
    public void test7() throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream("iotest/stu.aaa");
        ObjectInputStream ois = new ObjectInputStream(fis);
​
         /*
         当我们修改了Student类之后,用这段程序重新读之前的stu.aaa文件,发生了如下异常:
       java.io.InvalidClassException(无效的类异常):
        com.atguigu.object.Student; (Student类)
        local class incompatible:(本地的 类 不兼容,不匹配)
         stream classdesc serialVersionUID = 7049296973331162856, (流中的类描述信息中的序列化版本ID)
         local class serialVersionUID = -1502441291691716068(本地的类描述信息中序列化版本ID)
​
        解决版本:给Student类加了
        private static final long serialVersionUID = 7049296973331162856L;
     */
        Object obj = ois.readObject();
        System.out.println(obj);
​
        ois.close();
        fis.close();
    }

4、static和transient不序列化

1、它们不能独立使用,必须依赖于其他的IO流。
2、建议保存的文件后缀名自己命名,不要与现有的文件类型相同。
3、用ObjectInputStream读数据的顺序需要与用ObjectOutputStream写数据的顺序一致。
4、如果要用ObjectOutputStream输出引用数据类型的对象,要求该对象的类型必须实现java.io.Serializable接口
​
对象的序列化:把Java对象转为字节数据输出,序列化的IO流:ObjectOutputStream
对象的反序列化:把字节数据“重构”成一个Java对象,反序列化的IO流:ObjectInputStream
​
5、当某个类实现了Serializable接口之后,如果没有明确指定“序列化版本ID”,
那么每次修改类重新编译后都会自动产生1个新的“序列化版本ID”,
如果类描述信息中的“序列化版本ID”不一致,是无法正确的反序列化的。
解决:
    必须给实现了Serializable接口的类(例如Student类)确定一个“序列化版本ID”。
    “序列化版本ID”必须是private static final long serialVersionUID
​
“序列化版本ID”的值为多少合适呢?
(1)情况一:如果是第一次实现Serializable接口,该类的对象还没有被序列化过,那么“序列化版本ID”写什么值都可以。
(2)情况二:之前已经实现Serializable接口,该类的对象已经被序列化过了,那么要让“序列化版本ID”和上一次序列化时的版本ID值一致
例如:当我们修改了Student类之后,用这段程序重新读之前的stu.aaa文件,发生了如下异常:
          java.io.InvalidClassException(无效的类异常):
           com.atguigu.object.Student; (Student类)
           local class incompatible:(本地的 类 不兼容,不匹配)
            stream classdesc serialVersionUID = 7049296973331162856, (流中的类描述信息中的序列化版本ID)  (上次序列化的版本ID值)
            local class serialVersionUID = -1502441291691716068(本地的类描述信息中序列化版本ID)
​
​
6、对象序列化,顾名思义,只针对“对象的实例变量”,不会针对类的“静态变量”,因为“静态变量”不属于某个对象,是属于类的。
 结论:static修饰的变量值不会被序列化
​
7、不是对象的所有属性/实例变量值都要进行序列化的,例如对象中临时数据,敏感数据等不需要序列化。
总结:statictransient修饰的成员变量不会序列化。
import java.io.Serializable;
​
public class Student implements Serializable {
    private int id;
    private String name;
    private transient int age;
    private static final long serialVersionUID = 7049296973331162856L;
​
    private static String school;
​
    public Student(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
​
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
​
    public static String getSchool() {
        return school;
    }
​
    public static void setSchool(String school) {
        Student.school = school;
    }
​
    public int getId() {
        return id;
    }
​
    public void setId(int id) {
        this.id = id;
    }
​
    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;
    }
​
    @Override
    public String toString() {
        return "id="+ id +
                ",name=" + name +
                ", age=" + age +
                ",school = " + school;
    }
​
}
​
    @Test
    public void test8()throws IOException{
        Student stu = new Student(1,"李", 25);
        Student.setSchool("小学");
​
        FileOutputStream fos = new FileOutputStream("iotest/stu.aaa");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
​
        oos.writeObject(stu);
​
        oos.close();
        fos.close();
    }
​
    @Test
    public void test9() throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream("iotest/stu.aaa");
        ObjectInputStream ois = new ObjectInputStream(fis);
​
        Object obj = ois.readObject();
        System.out.println(obj);
​
        ois.close();
        fis.close();
    }

5、学生问题:自定义序列化规则(不用掌握)

import java.io.Serializable;
​
public class Student implements Serializable {
    private int id;
    private String name;
    private transient int age;
    private static final long serialVersionUID = 7049296973331162856L;
​
    private static String school;
​
    public Student(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
​
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
​
    public static String getSchool() {
        return school;
    }
​
    public static void setSchool(String school) {
        Student.school = school;
    }
​
    public int getId() {
        return id;
    }
​
    public void setId(int id) {
        this.id = id;
    }
​
    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;
    }
​
    @Override
    public String toString() {
        return "id="+ id +
                ",name=" + name +
                ", age=" + age +
                ",school = " + school;
    }
​
    //如果你的类中重写了writeObject方法,readObject方法,那么对象的序列化和反序列化就按你自己定义的规则来了
    private void writeObject(java.io.ObjectOutputStream s)
            throws java.io.IOException{
        s.writeUTF(school);//静态变量
        s.writeInt(age);//transient修饰的变量
        s.writeUTF(name);
    }
​
    private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
        school  = s.readUTF();
        age = s.readInt();
        name = s.readUTF();
    }
}
​
    @Test
    public void test8()throws IOException{
        Student stu = new Student(1,"李", 25);
        Student.setSchool("小学");
​
        FileOutputStream fos = new FileOutputStream("iotest/stu.aaa");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
​
        oos.writeObject(stu);
​
        oos.close();
        fos.close();
    }
​
    @Test
    public void test9() throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream("iotest/stu.aaa");
        ObjectInputStream ois = new ObjectInputStream(fis);
​
        Object obj = ois.readObject();
        System.out.println(obj);
​
        ois.close();
        fis.close();
    }

13.4 IO流关闭顺序

import org.junit.Test;
​
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
​
public class TestClose {
    @Test
    public void test1() throws IOException {
        FileWriter fw = new FileWriter("iotest/2.txt");
        BufferedWriter bw = new BufferedWriter(fw);
​
        bw.write("hello");
​
        //不关闭,数据在IO流自带缓冲区,没有flush,close,数据没有出去
    }
​
    @Test
    public void test2() throws IOException {
        FileWriter fw = new FileWriter("iotest/2.txt");
        BufferedWriter bw = new BufferedWriter(fw);
​
        bw.write("hello");
​
        fw.close();
        bw.close();//java.io.IOException: Stream closed
        /*
        fw和文件连接的IO流,先关闭了。
        bw是和程序连接的IO流,后关闭,关闭时,会清理bw缓冲区中的数据,此时想把缓冲区中的数据写出去时,无法正常输出,因为通道断了
         */
    }
​
    @Test
    public void test3() throws IOException {
        FileWriter fw = new FileWriter("iotest/2.txt");//穿内衣
        BufferedWriter bw = new BufferedWriter(fw);//穿外套
​
        bw.write("hello");
​
        //正确的关闭,倒着关
        bw.close();//脱外套
        fw.close();//脱内衣
    }
​
    @Test
    public void test4() throws IOException {
        BufferedWriter bw = new BufferedWriter(new FileWriter("iotest/2.txt"));
​
        bw.write("hello");
​
//        或
//        只关最外面的
        bw.close();
    }
}
​

13.5 IO流异常处理

JDK1.7版本,引入try-catch的新形式:
    try(需要自动关闭的资源对象的声明和初始化){
        可能发生异常的业务逻辑代码
    }catch(异常的类型 e){
        处理异常的代码
    }finally{
        释放资源之外的其他必须执行的代码
        或在try()之外声明的资源的释放代码
    }
​
注意:
    放到try()中声明和初始化的资源类必须实现AutoCloseable或Closeable接口。
    在try()中声明的资源默认是final修饰的。
package com.atguigu.close;
​
import java.io.Closeable;
import java.io.IOException;
​
public class MyClose implements Closeable {
    @Override
    public void close() throws IOException {
​
    }
}
​
import org.junit.Test;
​
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
​
public class TestTryCatch {
    @Test
    public void test1()  {
        FileWriter fw = null;
        BufferedWriter bw = null;
        try {
            fw = new FileWriter("iotest/2.txt");
            bw = new BufferedWriter(fw);
​
            bw.write("hello");
        } catch (IOException e) {
            e.printStackTrace();
            //如果想要把异常传给上级,这里还可以这么写
//            throw new RuntimeException(e);
        }finally {
            //正确的关闭,倒着关
            try {
                bw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
​
            /*try {
                bw.close();
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }*/
​
​
        }
    }
​
    @Test
    public void test2() {
​
       try( FileWriter fw = new FileWriter("iotest/2.txt");
        BufferedWriter bw = new BufferedWriter(fw);) {
​
           bw.write("hello");
       }catch (IOException e){
           e.printStackTrace();
       }
​
​
    }
​
    @Test
    public void test3(){
        try(MyClose my = new MyClose();){
//            my = new MyClose();//不能重新赋值
        }catch (Exception e){
​
        }
    }
}
​