尚学堂Java300集:IO

文章目录


前言

该文为尚学堂Java300集IO部分的学习笔记
文字部分内容参考:https://www.sxt.cn/Java_jQuery_in_action/ten-task.html
代码为自测代码


1 基本概念和IO入门

1.1 数据源

  • 数据源分为:源设备、目标设备。
    • 源设备:为程序提供数据,一般对应输入流。
    • 目标设备:程序数据的目的地,一般对应输出流。

1.2 流的概念

  • 流是一个抽象、动态的概念,是一连串连续动态的数据集合。
    • 输入流:流向程序的流。数据流向是数据源到程序(以InputStream、Reader结尾的流)。
    • 输出流:程序输出的流。数据流向是程序到目的地(以OutPutStream、Writer结尾的流)。
      在这里插入图片描述

1.3 流的分类

  • 按流的方向分类:
    • 1.输入流:数据流向是数据源到程序(以InputStream、Reader结尾的流)。
    • 2.输出流:数据流向是程序到目的地(以OutPutStream、Writer结尾的流)
  • 按处理的数据单元分类:
    • 1.字节流:以字节为单位获取数据,命名上以Stream结尾的流
    • 2.字符流:以字符为单位获取数据,命名上以Reader/Writer结
  • 按处理对象不同分类:
    • 1.节点流:可以直接从数据源或目的地读写数据,如FileInputStream、FileReader、DataInputStream等。
    • 2.处理流:不直接连接到数据源或目的地,是”处理流的流”。通过对其他流的处理提高程序的性能,如:BufferedInputStream、BufferedReader等。处理流也叫包装流。
    • 节点流处于IO操作的第一线,所有操作必须通过它们进行;处理流可以对节点流进行包装,提高性能或提高程序的灵活性。
      在这里插入图片描述

1.4 Java中IO流类的体系

在这里插入图片描述

  • 总结:
    • Java IO是采用的是装饰模式,即采用处理流来包装节点流的方式,来达到代码通用性。
    • 处理流和节点流的区分方法,节点流在新建时需要一个数据源(文件、网络)作为参数,而处理流需要一个节点流作为参数。
    • 处理流的作用就是提高代码通用性,编写代码的便捷性,提高性能。
    • 节点流都是对应抽象基类的实现类,它们都实现了抽象基类的基础读写方法。其中read()方法如果返回-1,代表已经读到数据源末尾。

1.5 四大IO抽象类

  • InputStream、OutputStream、Reader、writer类是所有IO流类的抽象父类。

  • InputStream:

    • InputSteam是一个抽象类,它不可以实例化。
    • 数据的读取需要由它的子类来实现。
    • 根据节点的不同,它派生了不同的节点流子类 。
    • 继承自InputStream的流都是用于向程序中输入数据,且数据的单位为字节(8 bit)。
    • 常用方法:
      • int read():读取一个字节的数据,并将字节的值作为int类型返回(0-255之间的一个值)。如果未读出字节则返回-1(返回值为-1表示读取结束)。
      • void close():关闭输入流对象,释放相关系统资源。
  • OutputStream

    • 此抽象类是表示字节输出流的所有类的父类。输出流接收输出字节并将这些字节发送到某个目的地。
    • 常用方法:
      • void write(int n):向目的地中写入一个字节。
      • void close():关闭输出流对象,释放相关系统资源。
  • Reader

    • Reader用于读取的字符流抽象类,数据单位为字符。
    • 常用方法:
      • int read(): 读取一个字符的数据,并将字符的值作为int类型返回(0-65535之间的一个值,即Unicode值)。如果未读出字符则返回-1(返回值为-1表示读取结束)。

      • void close() : 关闭流对象,释放相关系统资源。

  • Writer

    • Writer用于写入的字符流抽象类,数据单位为字符。
    • 常用方法:
      • void write(int n): 向输出流中写入一个字符。
      • void close() : 关闭输出流对象,释放相关系统资源。

2 常用的流

2.1 文件字节流

2.1.1 作用

  • FileInputStream 通过字节的方式读取文件,适合读取所有类型的文件(图像、视频、文本文件等)。Java也提供了FileReader专门读取文本文件。
  • FileOutputStream 通过字节的方式写数据到文件中,适合所有类型的文件。Java也提供了FileWriter专门写入文本文件。

2.1.2 将文件内容读取到程序中

  • 最基础写法(不推荐)
public static void readFile1(){
        FileInputStream fileInputStream=null;
        try {
            fileInputStream = new FileInputStream("D:\\Codes\\kuang.study\\JUC\\src\\main\\java\\com\\kuang\\io\\TestResource\\story.txt");
            while (fileInputStream.read() !=-1){
                System.out.println(fileInputStream.read());
            }
        } catch (FileNotFoundException e) {
            System.out.println("文件不存在");
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(fileInputStream!=null){
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
  • 改良版:用stringBuffer接取的值
    public static void readFile2(String src){
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(src);
            StringBuffer stringBuffer = new StringBuffer();
            int temp=0;
            while ((temp=fileInputStream.read())!=-1){
                stringBuffer.append(temp);
            }
            System.out.println(stringBuffer);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(fileInputStream!=null){
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

2.1.3 将字符串/字节数组的内容写入到文件中

  • 下例中用到一个write方法:void write(byte[ ] b),该方法不再一个字节一个字节地写入,而是直接写入一个字节数组;另外其还有一个重载的方法:void write(byte[ ] b, int off, int length),这个方法也是写入一个字节数组,但是我们程序员可以指定从字节数组的哪个位置开始写入,写入的长度是多少。
    /**
     * 将src文件的内容拷贝到desc文件
     * @param src 源文件
     * @param desc 目标文件
     */
    public static void writeFile(){
        String story2="大笨狗爱小猫咪";
        FileOutputStream fileOutputStream = null;
        try {
            // true表示内容会追加到文件末尾;false表示重写整个文件内容。
            fileOutputStream = new FileOutputStream("D:\\Codes\\kuang.study\\JUC\\src\\main\\java\\com\\kuang\\io\\TestResource\\story2.txt",true);
            // 该方法是直接将一个字节数组写入文件中; 而write(int n)是写入一个字节
            fileOutputStream.write(story2.getBytes());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(fileOutputStream !=null){
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

2.1.4 利用文件流实现文件的复制(重要)

   public static void copy(String src,String desc){
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;
        try {
            //为了提高效率,设置缓存数组!(读取的字节数据会暂存放到该字节数组中)
            byte[] bytes = new byte[1024];
            fileInputStream = new FileInputStream(src);
            fileOutputStream = new FileOutputStream(desc,false);
            int temp=0;
            //边读边写
            //temp指的是本次读取的真实长度,temp等于-1时表示读取结束
            while ((temp=fileInputStream.read(bytes))!=-1){
                /*将缓存数组中的数据写入文件中,注意:写入的是读取的真实长度;
                 *如果使用fos.write(buffer)方法,那么写入的长度将会是1024,即缓存数组的长度
                 *在该例中乱码
                 * */
                fileOutputStream.write(bytes,0,temp);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //两个流需要分别关闭
            if(fileInputStream!=null){
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fileOutputStream!=null){
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

2.1.5 总结

  • 在使用文件字节流时,我们需要注意以下两点:
    • 为了减少对硬盘的读写次数,提高效率,通常设置缓存数组。相应地,读取时使用的方法为:read(byte[] b);写入时的方法为:write(byte[ ] b, int off, int length)。
    • 程序中如果遇到多个流,每个流都要单独关闭,防止其中一个流出现异常后导致其他流无法关闭的情况。

2.2 文件字符流

2.2.1 作用

  • 前面介绍的文件字节流可以处理所有的文件,但是字节流不能很好的处理Unicode字符,经常会出现“乱码”现象。所以,我们处理文本文件,一般可以使用文件字符流,它以字符为单位进行操作。

2.2.2 使用FileReader与FileWriter实现文本文件的复制

    /**
     * 将src文件的内容拷贝到desc文件
     * @param src 源文件
     * @param desc 目标文件
     */
    public static void copy(String src,String desc){
        FileReader fileReader = null;
        FileWriter fileWriter = null;
        try {
            fileReader = new FileReader(src);
            fileWriter = new FileWriter(desc,false);
            char[] chars = new char[1024];
            int temp=0;
            //FileInputStream对象 有 read(byte[] b)方法
            //FileReader对象 有 read(char[] a)方法
            while ((temp=fileReader.read(chars))!=-1){
                fileWriter.write(chars,0,temp);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fileReader != null){
                try {
                    fileReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fileWriter != null){
                try {
                    fileWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

2.2.3 注意

  • 当用缓冲数组提高读写效率时:
    • FileInputStream 对应要用byte[]数组,FileInputStream对象 有 read(byte[] b)方法
    • FileReader 对应要用char[]数组,FileReader对象 有 read(char[] a)方法

2.3 缓冲字节流

2.3.1 作用

  • Java缓冲流是一种处理流。
    • Java缓冲流本身并不具有IO流的读取与写入功能,只是在别的流(节点流或其他处理流)上加上缓冲功能提高效率,就像是把别的流包装起来一样,因此缓冲流是一种处理流(包装流)。
  • Java缓冲流可以提高性能。
    • 当对文件或者其他数据源进行频繁的读写操作时,效率比较低,这时如果使用缓冲流就能够更高效的读写信息。因为缓冲流是先将数据缓存起来,然后当缓存区存满后或者手动刷新时再一次性的读取到程序或写入目的地。
  • BufferedInputStream和BufferedOutputStream这两个流是缓冲字节流,通过内部缓存数组来提高操作流的效率。

2.3.2 使用缓冲流实现文件的高效率复制

    public static void copy1(String src,String desc){
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;
        BufferedInputStream bufferedInputStream = null;
        BufferedOutputStream bufferedOutputStream = null;
        try {
            fileInputStream = new FileInputStream(src);
            fileOutputStream = new FileOutputStream(desc);
            //使用缓冲字节流包装文件字节流,增加缓冲功能,提高效率
            //缓存区的大小(缓存数组的长度)默认是8192,也可以自己指定大小
            bufferedInputStream = new BufferedInputStream(fileInputStream);
            bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
            byte[] bytes = new byte[1024];
            int temp =0;
            while ((temp=fileInputStream.read(bytes))!=-1){
                bufferedOutputStream.write(bytes,0,temp);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //注意:增加处理流后,注意流的关闭顺序!“后开的先关闭!”
            if(bufferedOutputStream != null){
                try {
                    bufferedOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(bufferedInputStream != null){
                try {
                    bufferedInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fileOutputStream != null){
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fileInputStream != null){
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

2.3.3 注意

  • BufferedInputStream的read方法的用法同InputStream的read用法。BufferedOutputStream的write方法的用法同OuputStream的write用法。
  • 在关闭流时,应该先关闭最外层的包装流,即“后开的先关闭”。
  • 缓存区的大小默认是8192字节,也可以使用其它的构造方法自己指定大小。
  • 注意:只有资源较大时,缓冲流的效率才会高。如果是小文件,BufferedInputStream的效率可能不如FileInputStream。

2.4 缓冲字符流

2.4.1 作用

  • BufferedReader/BufferedWriter增加了缓存机制,大大提高了读写文本文件的效率,同时,提供了更方便的按行读取的方法:readLine(); 处理文本时,我们一般可以使用缓冲字符流。

2.4.2 使用BufferedReader与BufferedWriter实现文本文件的复制

  • 同FileReader的read和write写法:
    public static void copy(String src,String desc){
        FileReader fileReader = null;
        FileWriter fileWriter = null;
        BufferedReader bufferedReader = null;
        BufferedWriter bufferedWriter = null;
        try {
            fileReader = new FileReader(src);
            fileWriter = new FileWriter(desc);
            bufferedReader = new BufferedReader(fileReader);
            bufferedWriter = new BufferedWriter(fileWriter);
            char[] chars = new char[1024];
            int temp=0;
            while ((temp=bufferedReader.read(chars))!=-1){
                bufferedWriter.write(chars,0,temp);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(bufferedWriter != null){
                try {
                    bufferedWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(bufferedReader != null){
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fileWriter != null){
                try {
                    fileWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fileReader != null){
                try {
                    fileReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
  • 用BufferedReader 的 readLine 方法,但是像下例这么写会多一个换行。
    public static void copy1(String src,String desc){
        FileReader fileReader = null;
        FileWriter fileWriter = null;
        BufferedReader bufferedReader = null;
        BufferedWriter bufferedWriter = null;
        try {
            fileReader = new FileReader(src);
            fileWriter = new FileWriter(desc);
            bufferedReader = new BufferedReader(fileReader);
            bufferedWriter = new BufferedWriter(fileWriter);
            String tempString="";
            while ((tempString=bufferedReader.readLine())!=null){
                bufferedWriter.write(tempString);
                //下次写入之前先换行,否则会在上一行后边继续追加,而不是另起一行
                bufferedWriter.newLine();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(bufferedWriter != null){
                try {
                    bufferedWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(bufferedReader != null){
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fileWriter != null){
                try {
                    fileWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fileReader != null){
                try {
                    fileReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
  • 改进上例,解决最后多一个换行问题。思路:加一个代表文件行数的变量,假设从0起。循环里先判断该变量是否为0,如果为0则跳过换行直接写入;如果不为0则先换行再写入。
    public static void copy2(String src,String desc){
        FileReader fileReader = null;
        FileWriter fileWriter = null;
        BufferedReader bufferedReader = null;
        BufferedWriter bufferedWriter = null;
        try {
            fileReader = new FileReader(src);
            fileWriter = new FileWriter(desc);
            bufferedReader = new BufferedReader(fileReader);
            bufferedWriter = new BufferedWriter(fileWriter);
            String tempString="";
            int init=0;
            while ((tempString=bufferedReader.readLine())!=null){
                if(init!=0){
                    bufferedWriter.newLine();
                }
                bufferedWriter.write(tempString);
                init++;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(bufferedWriter != null){
                try {
                    bufferedWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(bufferedReader != null){
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fileWriter != null){
                try {
                    fileWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fileReader != null){
                try {
                    fileReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

2.4.3 注意

  • readLine()方法是BufferedReader特有的方法,可以对文本文件进行更加方便的读取操作。
  • 先写入后用newLine()方法换行会造成多一行,请参考2.4.2最后一例改进。

2.5 字节数组流

2.5.1 作用

  • ByteArrayInputStream和ByteArrayOutputStream经常用在需要流和数组之间转化的情况!
  • FileInputStream是把文件当做数据源。ByteArrayInputStream则是把内存中的”某个字节数组对象”当做数据源。

2.5.2 简单测试ByteArrayInputStream 的使用

    public static void test(byte[] b) {
        ByteArrayInputStream bais = null;
        StringBuilder sb = new StringBuilder();
        int temp = 0;
        //用于保存读取的字节数
        int num = 0;
        try {
            //该构造方法的参数是一个字节数组,这个字节数组就是数据源
            bais = new ByteArrayInputStream(b);
            while ((temp = bais.read()) != -1) {
                sb.append((char) temp);
                num++;
            }
            System.out.println(sb);
            System.out.println("读取的字节数:" + num);
        } finally {
            try {
                if (bais != null) {
                    bais.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

2.5.3 注意

  • 读取的是中文时上述写法会乱码。

2.6 数据流

2.6.1 作用

  • 数据流将“基本数据类型与字符串类型”作为数据源,从而允许程序以与机器无关的方式从底层输入输出流中操作Java基本数据类型与字符串类型。
  • DataInputStream和DataOutputStream提供了可以存取与机器无关的所有Java基础类型数据(如:int、double、String等)的方法。
  • DataInputStream和DataOutputStream是处理流,可以对其他节点流或处理流进行包装,增加一些更灵活、更高效的功能。

2.6.2 DataOutputStream的使用

	/**
     * @param desc 输出文件的位置和文件名
     */
    public static void writeOut(String desc){
        FileOutputStream fileOutputStream = null;
        DataOutputStream dataOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(desc);
            dataOutputStream = new DataOutputStream(fileOutputStream);
            //将如下数据写入到文件中
            dataOutputStream.writeByte(17);
            dataOutputStream.writeChar(18);
            dataOutputStream.writeDouble(12.71);
            dataOutputStream.writeFloat((float) 1.2);
            dataOutputStream.writeInt(111);
            dataOutputStream.writeLong(11);
            dataOutputStream.writeShort(123);
            dataOutputStream.writeBoolean(true);
            dataOutputStream.writeUTF("大笨狗爱小猫咪");
            //手动刷新缓冲区:将流中数据写入到文件中
            dataOutputStream.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(dataOutputStream != null){
                try {
                    dataOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fileOutputStream != null){
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

2.6.3 DataInputStream的使用

public static void readIn(String src){
        FileInputStream fileInputStream = null;
        DataInputStream dataInputStream = null;
        try {
            fileInputStream = new FileInputStream(src);
            dataInputStream = new DataInputStream(fileInputStream);
            //读取数据:读取的顺序要与写入的顺序一致,否则不能正确读取数据。
            System.out.println(dataInputStream.readByte());
            System.out.println(dataInputStream.readChar());
            System.out.println(dataInputStream.readDouble());
            System.out.println(dataInputStream.readFloat());
            System.out.println(dataInputStream.readInt());
            System.out.println(dataInputStream.readLong());
            System.out.println(dataInputStream.readShort());
            System.out.println(dataInputStream.readBoolean());
            System.out.println(dataInputStream.readUTF());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(dataInputStream != null){
                try {
                    dataInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fileInputStream != null){
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

2.6.4 注意

  • 使用数据流时,读取的顺序一定要与写入的顺序一致,否则不能正确读取数据。
    • 读取的数据不对。
    • 如果有readUTF()方法,则会报 java.io.EOFException 错误。

2.7 对象流

2.7.1 作用

  • 我们前边学到的数据流只能实现对基本数据类型和字符串类型的读写,并不能读取对象(字符串除外),如果要对某个对象进行读写操作,我们需要学习一对新的处理流:ObjectInputStream/ObjectOutputStream。
  • ObjectInputStream/ObjectOutputStream是以“对象”为数据源,但是必须将传输的对象进行序列化与反序列化操作。
  • 序列化与反序列化的具体内容,请见<10.3 Java对象的序列化和反序列化>。示例10-11仅演示对象流的简单应用。

2.7.2 ObjectInputStream 和 ObjectOutputStream的使用

package com.kuang.io;

import java.io.*;
import java.util.Date;

public class TestObjectInputStream {
    public static void main(String[] args) {
        writeOut("D:\\Codes\\kuang.study\\JUC\\src\\main\\java\\com\\kuang\\io\\TestResource\\story21.txt");
        readIn("D:\\Codes\\kuang.study\\JUC\\src\\main\\java\\com\\kuang\\io\\TestResource\\story21.txt");
    }

    //使用对象输出流将数据写入文件
    public static void writeOut(String desc){
        Cat cat = new Cat("花花",7);
        Date date = new Date();
        FileOutputStream fileOutputStream = null;
        ObjectOutputStream objectOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(desc);
            objectOutputStream = new ObjectOutputStream(fileOutputStream);
            // 使用Object输出流
            //对象流也可以对基本数据类型进行读写操作
            objectOutputStream.writeUTF("大笨狗爱小猫咪");
            objectOutputStream.writeObject(cat);
            objectOutputStream.writeObject(date);
            objectOutputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(objectOutputStream != null){
                try {
                    objectOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fileOutputStream != null){
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    public static void readIn(String src){
        FileInputStream fileInputStream = null;
        ObjectInputStream objectInputStream = null;
        try {
            fileInputStream = new FileInputStream(src);
            objectInputStream = new ObjectInputStream(fileInputStream);
            System.out.println(objectInputStream.readUTF());
            System.out.println(objectInputStream.readObject());
            System.out.println(objectInputStream.readObject());
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if(objectInputStream != null){
                try {
                    objectInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fileInputStream != null){
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

class Cat implements Serializable {
    private static final long serialVersionUID = 87839899372L;
    private String name;
    private int age;

    public Cat(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 "Cat{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

2.7.3 注意

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

2.8 转换流

2.8.1 作用

  • InputStreamReader/OutputStreamWriter用来实现将字节流转化成字符流。比如,如下场景:
    • System.in是字节流对象,代表键盘的输入,如果我们想按行接收用户的输入时,就必须用到缓冲字符流BufferedReader特有的方法readLine(),但是经过观察会发现在创建BufferedReader的构造方法的参数必须是一个Reader对象,这时候我们的转换流InputStreamReader就派上用场了。
    • 而System.out也是字节流对象,代表输出到显示器,按行读取用户的输入后,并且要将读取的一行字符串直接显示到控制台,就需要用到字符流的write(String str)方法,所以我们要使用OutputStreamWriter将字节流转化为字符流。

2.8.2 使用InputStreamReader接收用户的输入,并输出到控制台

  • 将键盘输入的内容用System.out.println打出到控制台,遇到"exit"时终止。
    public static void test(){
        InputStreamReader inputStreamReader = null;
        BufferedReader bufferedReader = null;
        try {
            inputStreamReader = new InputStreamReader(System.in);
            bufferedReader = new BufferedReader(inputStreamReader);
            String tempString=bufferedReader.readLine();
            while (!tempString.equals("exit")){
                System.out.println(tempString);
                tempString=bufferedReader.readLine();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(bufferedReader != null){
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(inputStreamReader != null){
                try {
                    inputStreamReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
  • 将键盘输入的内容用 OutputStreamWriter 转换后打出到控制台,遇到"exit"时终止。
    public static void test1(){
        InputStreamReader inputStreamReader = null;
        BufferedReader bufferedReader = null;
        OutputStreamWriter outputStreamWriter = null;
        BufferedWriter bufferedWriter = null;

        try {
            inputStreamReader = new InputStreamReader(System.in);
            bufferedReader = new BufferedReader(inputStreamReader);
            outputStreamWriter = new OutputStreamWriter(System.out);
            bufferedWriter = new BufferedWriter(outputStreamWriter);

            String tempString=bufferedReader.readLine();
            while (!tempString.equals("exit")){
                bufferedWriter.write(tempString);
                bufferedWriter.newLine();
                bufferedWriter.flush();
                tempString=bufferedReader.readLine();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(bufferedWriter != null){
                try {
                    bufferedWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(bufferedReader != null){
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(outputStreamWriter != null){
                try {
                    outputStreamWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(inputStreamReader != null){
                try {
                    inputStreamReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

3 序列化与反序列化

3.1 序列化和反序列化是什么

  • 当两个进程远程通信时,彼此可以发送各种类型的数据。 无论是何种类型的数据,都会以二进制序列的形式在网络上传送。比如,我们可以通过http协议发送字符串信息;我们也可以在网络上直接发送Java对象。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象才能正常读取。
  • 把Java对象转换为字节序列的过程称为对象的序列化。把字节序列恢复为Java对象的过程称为对象的反序列化。
  • 对象序列化的作用有如下两种:
    • 持久化: 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中,比如:休眠的实现。以后服务器session管理,hibernate将对象持久化实现。
    • 网络通信:在网络上传送对象的字节序列。比如:服务器之间的数据通信、对象传递。

3.2 序列化涉及的类和接口

  • ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
  • ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
  • 只有实现了Serializable接口的类的对象才能被序列化。 Serializable接口是一个空接口,只起到标记作用

3.3 序列化/反序列化的步骤和实例

3.3.1 将自定义类的实例进行序列化和反序列化

package com.kuang.io;

import java.io.*;

public class TestSerializable {
    public static void main(String[] args) {
        String src="D:\\Codes\\kuang.study\\JUC\\src\\main\\java\\com\\kuang\\io\\TestResource\\story22.txt";

        test(src);
    }

    public static void test(String file){
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;
        ObjectInputStream objectInputStream = null;
        ObjectOutputStream objectOutputStream = null;
        Dog dog = new Dog("大笨狗", 20);
        try {
            fileOutputStream = new FileOutputStream(file);
            objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(dog);

            fileInputStream = new FileInputStream(file);
            objectInputStream = new ObjectInputStream(fileInputStream);

            System.out.println(objectInputStream.readObject());
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if(objectOutputStream != null){
                try {
                    objectOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fileOutputStream != null){
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(objectInputStream != null){
                try {
                    objectInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fileInputStream != null){
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

class Dog implements Serializable{
    private static final long serialVersionUID = 37482743L;
    private String name;
    private int age;

    public Dog(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 "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

3.3.2 注意

  • static属性不参与序列化。
  • 对象中的某些属性如果不想被序列化,不能使用static,而是使用transient修饰。
  • 为了防止读和写的序列化ID不一致,一般指定一个固定的序列化ID。即在需要序列化的类加一个 private static final long serialVersionUID = 37482743L;值自定。

4 装饰器模式

4.1 装饰器模式简介

4.1.1 概念

  • 装饰器模式是GOF23种设计模式中较为常用的一种模式。它可以实现对原有类的包装和装饰,使新的类具有更强的功能。

4.1.2 装饰器模式演示

package com.kuang.io;

public class TestDecoration {
    public static void main(String[] args) {
        Apple apple = new Apple();
        DelicateApple delicateApple = new DelicateApple(apple);
        delicateApple.evaluate();
    }
}

class Apple{
    public void taste(){
        System.out.println("苹果是苹果味的!");
    }
}

class DelicateApple{
    private Apple apple;

    public DelicateApple(Apple apple) {
        this.apple = apple;
    }

    public void evaluate(){
        apple.taste();
        System.out.println("精品苹果大");
        System.out.println("精品苹果还甜");
    }
}

4.2 IO流体系中的装饰器模式

  • IO流体系中大量使用了装饰器模式,让流具有更强的功能、更强的灵活性。比如:
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(src));
  • 显然BufferedInputStream装饰了原有的FileInputStream,让普通的FileInputStream也具备了缓存功能,提高了效率。

5 Apache IOUtils和FileUtils

  • JDK中提供的文件操作相关的类,但是功能都非常基础,进行复杂操作时需要做大量编程工作。实际开发中,往往需要你自己动手编写相关的代码,尤其在遍历目录文件时,经常用到递归,非常繁琐。 Apache-commons工具包中提供了IOUtils/FileUtils,可以让我们非常方便的对文件和目录进行操作。 本文就是让大家对IOUtils/FileUtils类有一个全面的认识,便于大家以后开发与文件和目录相关的功能。
  • Apache IOUtils和FileUtils类库为我们提供了更加简单、功能更加强大的文件操作和IO流操作功能。非常值得大家学习和使用。

5.1 Apache基金会介绍

  • Apache软件基金会(也就是Apache Software Foundation,简称为ASF),是专门为支持开源软件项目而办的一个非盈利性组织。在它所支持的Apache项目与子项目中,所发行的软件产品都遵循Apache许可证(Apache License)。 官方网址为:www.apache.org 。
  • 很多著名的Java开源项目都来源于这个组织。比如:commons、kafka、lucene、maven、shiro、struts等技术,以及大数据技术中的:hadoop(大数据第一技术)、hbase、spark、storm、mahout等。

5.2 FileUtils的妙用

5.2.1 eclipse如何使用FileUtils(没亲测)

5.2.1.1 jar包下载和介绍
  • 首先,我们要下载FileUtils相关的Apache-commons-io jar包以及api文档。FileUtils类库的下载页面在:

http://commons.apache.org/proper/commons-io/download_io.cgi

  • 点击进入下载页面:
    在这里插入图片描述
  • API文档的页面:

http://commons.apache.org/proper/commons-io/apidocs/index.html

5.2.1.2 eclpise项目如何导入外部的jar包操作
  • 在eclipse项目下新建lib文件夹
  • 解压下载后的版本,找到commons-io-2.5.jar包,并拷贝到lib文件夹下
    在这里插入图片描述
  • 设置jar包进入项目的classpath中。
    • 项目名上右击,依次选择【Build Path】–>【Configure Build Path…】,在打开的窗口中,先选中【Libraries】页,再从右边的按钮中点击 【add JARs…】; 在打开的窗口中,我们依次展开本项目的项目和lib文件夹,然后选中我们刚才复制到项目中的jar包,然后点击【Apply】使刚才的操作生效,最后点击【OK】关闭窗口。
      在这里插入图片描述
    • 项目结构如下:
      在这里插入图片描述
  • 注意:很多初学者会忘记配置项目的classpath,从而项目找不到相关的jar包。

5.2.2 idea的maven项目如何使用FileUtils(亲测)

  • 进入maven仓库:

https://mvnrepository.com/

  • 在搜索栏里搜索 Commons IO,然后点击“Apache Commons IO”
    在这里插入图片描述
  • 点击红框
    在这里插入图片描述
  • 将红框里的内容拷贝到pom里的<dependencies></dependencies>
    在这里插入图片描述
  • 效果如下:
    在这里插入图片描述
  • 点击IDEA右侧的Maven,展开后点击2处刷新,IDEA自动下载 Apache Commons IO 的jar包,完成后就可以使用了。
    在这里插入图片描述

5.2.3 FieUtils类中常用方法的介绍

  • 打开FileUtils的api文档,我们抽出一些工作中比较常用的方法,进行总结和讲解。总结如下:
    • cleanDirectory:清空目录,但不删除目录。
    • contentEquals:比较两个文件的内容是否相同。
    • copyDirectory:将一个目录内容拷贝到另一个目录。可以通过FileFilter过滤需要拷贝的 文件。
    • copyFile:将一个文件拷贝到一个新的地址。
    • copyFileToDirectory:将一个文件拷贝到某个目录下。
    • copyInputStreamToFile:将一个输入流中的内容拷贝到某个文件。
    • deleteDirectory:删除目录。
    • deleteQuietly:删除文件。
    • listFiles:列出指定目录下的所有文件。
    • openInputSteam:打开指定文件的输入流。
    • readFileToString:将文件内容作为字符串返回。
    • readLines:将文件内容按行返回到一个字符串数组中。
    • size:返回文件或目录的大小。
    • write:将字符串内容直接写到文件中。
    • writeByteArrayToFile:将字节数组内容写到文件中。
    • writeLines:将容器中的元素的toString方法返回的内容依次写入文件中。
    • writeStringToFile:将字符串内容写到文件中。

5.2.4 读取文件内容,并输出到控制台上

    /**
     * 
     * @param file:读什么目录下的什么文件
     * @param outString:文件编码
     * @throws IOException
     */
    public static void testReadFileToString(String file,String outString) throws IOException {
        //第一个参数:读什么目录下的什么文件
        //第二个参数:文件编码
        //readFileToString方法可以读取多行的文件
        System.out.println(FileUtils.readFileToString(new File(file), outString));
    }

5.2.5 目录拷贝,并使用FileFilter过滤目录和以html结尾的文件

    /**
     *
     * @param src:被拷贝的文件目录
     * @param desc:目的文件目录
     * @throws IOException
     */
    public static void testCopyDirectory(String src,String desc) throws IOException {
        //第三个参数表示考虑条件
        //return true的会复制
        //return false的不会复制
        FileUtils.copyDirectory(new File(src), new File(desc), new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                if(pathname.isDirectory() || pathname.getName().endsWith("html")){
                    return true;
                }else {
                    return false;
                }
            }
        });
    }

5.3 IOUtils的妙用

5.3.1 IOUtils的常用方法

  • 打开IOUtils的api文档,我们发现它的方法大部分都是重载的。所以,我们理解它的方法并不是难事。因此,对于方法的用法总结如下:
    • buffer方法:将传入的流进行包装,变成缓冲流。并可以通过参数指定缓冲大小。

    • closeQueitly方法:关闭流。

    • contentEquals方法:比较两个流中的内容是否一致。

    • copy方法:将输入流中的内容拷贝到输出流中,并可以指定字符编码。

    • copyLarge方法:将输入流中的内容拷贝到输出流中,适合大于2G内容的拷贝。

    • lineIterator方法:返回可以迭代每一行内容的迭代器。

    • read方法:将输入流中的部分内容读入到字节数组中。

    • readFully方法:将输入流中的所有内容读入到字节数组中。

    • readLine方法:读入输入流内容中的一行。

    • toBufferedInputStream,toBufferedReader:将输入转为带缓存的输入流。

    • toByteArray,toCharArray:将输入流的内容转为字节数组、字符数组。

    • toString:将输入流或数组中的内容转化为字符串。

    • write方法:向流里面写入内容。

    • writeLine方法:向流里面写入一行内容。

5.3.2 TestIOUtils.toString方法

package com.kuang.io;

import org.apache.commons.io.IOUtils;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class TestIOUtils {
    public static void main(String[] args) {
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream("D:\\Codes\\kuang.study\\JUC\\src\\main\\java\\com\\kuang\\io\\TestResource\\story13.txt");
            System.out.println(IOUtils.toString(fileInputStream, "utf-8"));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fileInputStream != null){
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

6 总结

  • 按流的方向分类:

    • 输入流:数据源到程序(InputStream、Reader读进来)。

    • 输出流:程序到目的地(OutPutStream、Writer写出去)。

  • 按流的处理数据单元分类:

    • 字节流:按照字节读取数据(InputStream、OutputStream)。

    • 字符流:按照字符读取数据(Reader、Writer)。

  • 按流的功能分类:

    • 节点流:可以直接从数据源或目的地读写数据。

    • 处理流:不直接连接到数据源或目的地,是处理流的流。通过对其他流的处理提高程序的性能。

  • IO的四个基本抽象类:

    • InputStream
    • OutputStream
    • Reader
    • Writer
  • InputStream的实现类:

    • FileInputStream

    • ByteArrayInutStream

    • BufferedInputStream

    • DataInputStream

    • ObjectInputStream

  1. OutputStream的实现类:

    FileOutputStream

    ByteArrayOutputStream

    BufferedOutputStream

    DataOutputStream

    ObjectOutputStream

    PrintStream

  • Reader的实现类

    • FileReader

    • BufferedReader

    • InputStreamReader

  • Writer的实现类

    • FileWriter

    • BufferedWriter

    • OutputStreamWriter

  • 把Java对象转换为字节序列的过程称为对象的序列化。

  • 把字节序列恢复为Java对象的过程称为对象的反序列化。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值