IO流(输入输出流)

  1. 按照流向分
    • 输入流:从硬盘上读取数据到内存(读)
    • 输出流:从内存写出数据到硬盘(写)

一个文件在传输过程中经历了很多次的拷贝,IO性能很低
零拷贝、Nio附加题!!!

  1. 按照操作单元分
    • 字节流:是一个字节一个字节的操作。二进制操作。操作任意类型的文件
    • 字符流:是一个字符一个字符的操作,一个字符两个字节,主要用来处理文本文件
  1. 按照角色划分
    • 节点流:直接操作一个特定的IO设备
    • 处理流:在节点流的基础上,做进一步处理

Java中输入/输出流常用的流:

字节输入流 字节输出流 字符输入流 字符输出流
抽象基类 InputStream OutputStream Reader Writer
操作对象 ObjectInputStream ObjectOutputStream --- ---
访问文件(节点流) FileInputStream FileOutputStream FileReader FileWrite
缓冲流 BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter

File类不能够操作文件的内容

流到底怎么用?
输入流:就是一点一点的往内存中读数据

  1. 字节输入流
    创建一个FileInputStream对象
    定义一个标记,用来控制输入流的读取
    循环读取,如果读到-1,说明读取到了文件的末尾,循环结束
    关闭资源
@Test
    public void test01() {
        InputStream inputStream = null;
        try {
            inputStream = new FileInputStream("e:/aaa.txt");
            // 开始读的操作,read方法,返回值是int,当返回值为-1时,说明文件读取到了末尾
            // 读取文件是否结束的标记
            int read;
            // 字节流读数据的时候一个字节一个字节去读
            // 循环读取
            while((read = inputStream.read()) != -1) {
                System.out.print(read + " ");
            }
            System.out.println();
            System.out.println("读取完毕,再读一次。。。。");
            // 字节流读数据的时候一个字节一个字节去读
            // 循环读取
            while((read = inputStream.read()) != -1) {
                System.out.print(read + " ");
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                // 关闭流
                inputStream.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

注意: 当一个流读完了就没有了,不能再读了。当一个流读完之后就会默认调用mark和reset方法来进行记录和重置,这个流就已经重置到了上次读完的位置,所以无法再次读取内容,并不是读完一次之后就关闭

  1. 字节输出流
    FileOutputStream构造器:
    • 传入参数true,则代表在原有的内容上追加,不覆盖
    • 传入参数false:则代表覆盖原有的内容,不追加
    @Test
    public void test02() {
        OutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream("e:/aaa.txt", true);
            // 一个字节一个字节的写
            outputStream.write("\r\n".getBytes());
            outputStream.write("八月正午的阳光都没你耀眼".getBytes());
            System.out.println("数据写出成功...");
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
               if(Objects.nonNull(outputStream)){
                    outputStream.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
  • boolean append参数:如果传入true,则代表在原有基础上追加,不覆盖;如果传入false,或者不传,覆盖原有内容
  • 写的操作,目标文件如果不存在,会自动新建。

字符流

  • 字符处理流——只能处理纯文本文件
  • 访问文件
public class Ch04 {
    @Test
    public void test01() {
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;

        try {
            fileInputStream = new FileInputStream("E:/aaa.txt");
            fileOutputStream = new FileOutputStream("E:/bbb.txt");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        int i = 0;
        byte[] read = new byte[1024];
        try {
            //fileInputStream.read(read))把读取到的数据存入数组中,每次读取1024个字节,如果read()内不填默认读取1字节
            if (((i = fileInputStream.read(read)) != -1)) {
//                System.out.println(i + " ");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            if (Objects.nonNull(fileInputStream)) {
                fileOutputStream.write(read);
                fileOutputStream.write("\n".getBytes());//写入时,提供换行功能
                fileInputStream.close();
            }
        } catch (Exception e) {  //为确保流的正常关闭,抛异常时,选择Exception来抛异常
            e.printStackTrace();
        }
        try {
            if (Objects.nonNull(fileOutputStream)) {
                fileOutputStream.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 缓冲流
@Test
    public void test01() {
        BufferedReader bufferedReader = null;
        BufferedWriter bufferedWriter = null;
        try {
            bufferedReader = new BufferedReader(new FileReader("E:/aaa.txt"));
            bufferedWriter = new BufferedWriter(new FileWriter("E:/bbb.txt"));
            String str;
            while (((str = bufferedReader.readLine()) != null)) {
                System.out.println(str + " ");
                bufferedWriter.write(str);
                bufferedWriter.write("\n");
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        IOUtil.IOclose(bufferedReader,bufferedWriter);
    }

序列化与反序列化

  • 序列化:将对象写入到IO流中,将内存模型的对象变成字节数字,可以进行存储和传输
  • 反序列化:从IO流中恢复对象,将存储在硬盘上或者在网络中接收的数据恢复成对象模型
  • 使用场景:所有可在网络上,传输的对象都必须是可序列化的,否则会报错,所有保存在硬盘上的对象也必须可序列化
  • 注意事项:
    • 序列化版本号:反序列化必须拥有class文件,但随着项目的升级,class文件也会升级,序列化保障升级的兼容性【java序列化提供了一个版本号】
    • 版本号是可以自由指定,如果不指定,JVM会根据类信息自己计算一个版本号,所以无法匹配,则报错
    • 不指定版本号,还有一个隐患,不利于JVM的移植,可能class文件没有改,但是不同的jvm计算规则不一样,导致无法反序列化
    • 如果只修改了方法,反序列化不受影响的,无序修改版本号
    • 修改了静态变量static,瞬态变量transient,反序列化也不受影响,无需修改版本号

总结:

  1. 所有需要网络传输的对象都需要实现序列化接口
  2. 对象的类名,实例变量都会被序列化;方法、类变量、transient不会被序列化
  3. 如果想让某个变量不被序列化,可以用transient修饰
  4. 序列化对象的引用类型成员变量,也必须是可序列化的,否则会报错
  5. 反序列化时,必须有序列化对象的class文件
  6. 如果同一个对象被序列化多次,只有第一次序列化为二进制流,以后都只是保存序列化的版本号
  7. 建议所有可序列化的类加上版本号,方便项目的升级