IO流之OutputStream、InputStream详解

前言

  • 学习之前,需要对IO流的分类有个简单的认识,下列是关于本文的IO流分类介绍

IO流的分类

  • 按流的方向划分
    • 输入流
    • 输出流
  • 按流动的数据类型划分
    • 字节流
    • 字符流

本文提到的OutputStream、InputStream属于字节流

  • 字节流
    • 输入流
      • 顶级父类InputStream
    • 输出流
      • 顶级父类OutputStream
  • 还需要明确一个概念
    • 计算机中的任何数据(文本、图片、视频等)都是二进制存储的
    • 8个二进制位(bit)为一个字节(byte)
    • IO流的底层都是二进制
  • “流”其实就是传输数据的管道,把数据从管道的一段输送到另一端
    • 流入管道的一方是输入流,即读取某些数据,放到管道中
    • 流出管道的一方是输出流,即把管道里的数据,写入到某些文件中

一、java.io.OutputStream

1、概述

  • 抽象类是表示字节输出流的所有类的超类
    • 也就是所有字节输出流的顶级父类

2、常用API

void close()

  • 关闭此输出流,释放与该流有关的所有系统资源
    • 当不再需要用到流对象时,尽早关闭流对象
    • 如果不确定什么时候会用到流对象,那么在程序的最后一定要记得关闭流对象

void flush()

  • 刷新此输出流,强制写出任何缓冲的字节数据

void write(byte[] b)

  • 将字节数组b中存储的数据,通过输出流对象写出

void write(byte[] b, int off, int len)

  • 将字节数组b中存储的数据,从起始下标off开始,通过流对象写出len个数据

abstract void write(int b)

  • 该方法由子类实现,将指定的字节通过此流对象写出
  • 写到文档时,写入的int数据会根据ASCALL表转化为对应的字符

二、FileOutputStream

1、概述

  • 是字节输出流(OutputStream)最常用的子类

2、构造方法分析

FileOutputStream(File file)

  • 传入一个文件对象来建立文件输出流
  • 如果文件对象不存在,会自动创建
    • 如果创建失败,可能是权限不够

FileOutputStream(File file, boolean append)

  • 传入一个文件对象来建立文件输出流
  • 如果文件对象不存在,会自动创建
    • 如果创建失败,可能是权限不够
  • append
    • true
      • 表示在file文件原有数据的基础上,继续写入数据
    • false(默认)
      • 表示把file文件的内容清空后,再写入数据
  • 注意
    • 如果是同一个流对象,操作的就是同一个文件。此时如果append为false,重复调用write方法写入数据,也是可以追加内容的
    • 只有使用新的流对象(重新运行程序或创建新的流对象),append为false时才会清空原文件的数据

FileOutputStream(String name)

  • 同FileOutputStream(File file),区别在于本构造器是使用文件的路径来创建字节输出流对象

FileOutputStream(String name, boolean append)

  • 同FileOutputStream(File file, boolean append),区别在于本构造器是使用文件的路径来创建字节输出流对象

3、举例

  • 写入单个字节
public static void main(String[] args) throws IOException {
    //创建字节输出流,并自动创建a.txt文档
    FileOutputStream fos = new FileOutputStream("I:\\a.txt");
    //写入一个字节,65对应的ASCALL编码为A
    fos.write(65);
    //写入一个字节,65对应的ASCALL编码为B
    fos.write(66);
    //写完记得关闭字节数出流
    fos.close();
}
  • 写入字节数组
public static void main(String[] args) throws IOException {
    //创建字节输出流,并自动创建a.txt文档
    FileOutputStream fos = new FileOutputStream("I:\\a.txt");
    byte[] str1 = {65,66}; //AB
    byte[] str2 = "CD".getBytes(); //CD
    //写入一个字节数组
    fos.write(str1);
    //写入一个字节数组
    fos.write(str2);
    //写完记得关闭字节数出流
    fos.close();
}
  • append设置为true,在原文件的基础上继续写入数据
    • a.txt文件原来的内容:ABCD
    • 程序执行后的内容:ABCDBCD
public static void main(String[] args) throws IOException {
    //创建字节输出流,并自动创建a.txt文档,在原有文件的基础上继续写出内容
    FileOutputStream fos = new FileOutputStream("I:\\a.txt",true);
    byte[] str = {66,67,68}; //BCD
    fos.write(str);
    fos.close();
}

三、java.io.IntputStream

1、概述

  • 抽象类是表示字节输入流的所有类的超类
    • 也就是所有字节输入流的顶级父类

2、常用API

void close()

  • 关闭此输入流,释放与该流有关的所有系统资源

abstract int read()

  • 从输入流中读取下一个字节数据
  • 返回值为int,读取到字节的范围是0到255
    • 如果达到流的末尾,读取不到下一个字节,返回-1
  • 是阻塞方法

int read(byte[] b)

  • 从输入流中读取一些字节数,并存储到字节缓冲数组b中
  • 返回值是实际读取到的字节数
    • 如果读到文件末尾,返回-1
  • 此方法经常使用
    • 相比读取单个字节方法,可以减少java代码进行IO操作的频率,提高性能

int read(byte[] b, int off, int len)

  • 从输入流中读取一些字节数,存储到字节缓冲数组b中,并从off下标开始,读取b数组的len个数据

四、FileIntputStream

1、概述

  • 是字节输入流(IutputStream)最常用的子类

2、构造方法分析

FileInputStream(File file)

  • 传入一个文件对象来建立文件输入流
  • 该文件必须存在,否则会报错

FileInputStream(String name)

  • 传入一个文件的路径来建立文件输入流
  • 传入路径代表的文件必须存在,否则报错

3、举例

  • 在I盘下有一个a.txt文档,里面存放了二十六个字母

  • 读取单个字节
public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("I:\\a.txt");
        //读取文件的第一个字节
        int read = fis.read();
        System.out.println(read); //97
        System.out.println((char) read); //a
        fis.close();
    }

  • 读取一个字节数组

public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("I:\\a.txt");
        //字节缓冲数组,一次读取10个字节
        byte[] bytes = new byte[10];
        //第一次读取
        fis.read(bytes);
        //abcdefghij
        System.out.println(new String(bytes));
        //第二次读取
        fis.read(bytes);
        //klmnopqrst
        System.out.println(new String(bytes));
        //第三次读取
        fis.read(bytes);
        //uvwxyzqrst
        System.out.println(new String(bytes));
        fis.close();
    }
  • 我们会发现,第三次读取的字符有问题,正确的输出应该为最后的六个字母
  • 出现上述情况的解释
    • 缓存字节数组是共用的,最后一次读取时还剩6个字母,这六个字母会放在字节数组的前六个位置,因此最后一次读整个字节数组,得到的结果就是uvwxyzqrst
  • 针对上面情况的改良
    • 利用read的返回值,控制每一次读取的字节个数
public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("I:\\a.txt");
        //字节缓冲数组,一次读取10个字节
        byte[] bytes = new byte[10];
        //第一次读取
        int len = fis.read(bytes);
        //abcdefghij
        System.out.println(new String(bytes,0,len));
        //第二次读取
        len = fis.read(bytes);
        //klmnopqrst
        System.out.println(new String(bytes,0,len));
        //第三次读取
        len = fis.read(bytes);
        //uvwxyz
        System.out.println(new String(bytes,0,len));
        fis.close();
    }
  • 可以使用下面的方式进行文件遍历读取操作
 public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("I:\\a.txt");
        //字节缓冲数组,一次读取10个字节
        byte[] bytes = new byte[10];
        //读取的字节个数
        int len = -1;
        //当读取的字节数个数不为-1时,还没有读取到文件末尾,继续读取
        while ((len = fis.read(bytes)) != -1) {
            //打印0~len的数据
            System.out.println(new String(bytes,0,len));
        }
        fis.close();
    }

五、乱码问题

  • 如果数据输入和输出的编码格式不同,就会产生乱码问题
  • 解决办法
    • 将文件的编码格式和读取文件的编码格式统一,通常用的是UTF-8编码
  • UTF-8编码
    • 可变长度字符编码,是1-4字节动态变化的字符编码
  • 字节流的乱码问题
    • 字节流可能会出现两种乱码情况
      • 第一种:上面说的输入和输出编码格式不一致导致的编码
      • 第二种:输入和输出的编码格式统一了,假设采用UTF-8变长字符编码,因为不确定读取的每个数据占多少个字节,因此有可能出现某个字只读了一半的情况,从而导致乱码
  • 结论
    • 乱码问题通常是出现在读取中文的情况,因此读取文字会用到字符流
    • 除了读取文字的情况,其他情况最好都使用字节流,效率会更高一些

Guess you like

Origin blog.csdn.net/future_god_qr/article/details/121188800
Recommended