Java IO流笔记

本文深入解析Java中的IO流,包括输入流与输出流、字节流与字符流、节点流与处理流的区别与联系,以及各种流的具体用法。通过实例演示如何使用FileInputStream和FileOutputStream复制文件,探讨InputStreamReader如何将字节流转换为字符流。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文总结自《疯狂Java讲义》

IO流分类:

根据数据流向的不同可分为输入流输出流
根据处理的数据类型不同可分为字节流字符流
根据流的角色来分,可以分为节点流处理流

1. 输入流输出流:

一个流的流向是相对于当前这个程序的,从这个程序的内存中传输数据出去,就是输出流;将数据从外部写入程序内存中,就是输入流。

在 Java 中,输入流主要由 InputStream 和 Reader 作为基类,输出流主要由 OutputStream 和 Writer 作为基类。

2. 字节流字符流:

这两个流的用法几乎完全相同,区别在于字节流和字符流所操作的数据单元不同——字节流操作的数据单元是 8 位的字节,而字符流操作的数据单元是 16 位的字符。

在 Java 中,字节流主要由 InputStream 和 OutputStream作为基类,字符流主要由 Reader 和 Writer 作为基类。

3. 节点流处理流:

可以从/向一个特定的 IO 设备(如磁盘、网络)读/写数据的流,称为节点流,节点流也被称为低级流。

处理流则用于对一个已存在的流进行连接或封装,通过封装后的流来实现数据读/写功能。处理流也称为高级流。

节点流和处理流就是一个典型的装饰器设计模式,通过使用处理流来包装节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入/输出功能。

比如上一篇的序列化的介绍中,就是用处理流 ObjectOutputStream 包装节点流 ByteArrayWriter ,再调用 ObjectOutputStream 的方法将序列化的对象写入文件中。

处理流:

  1. Buffering缓冲流:在读入或写出时,对数据进行缓存,以减少I/O的次数:BufferedReader与BufferedWriter、BufferedInputStream与BufferedOutputStream。
  2. Filtering 滤流:在数据进行读或写时进行过滤:FilterReader与FilterWriter、FilterInputStream与FilterOutputStream。
  3. Converting between Bytes and Characters 转换流:按照一定的编码/解码标准将字节流转换为字符流,(Stream到Reader):InputStreamReader、OutputStreamWriter。
  4. Object Serialization 对象流 :ObjectInputStream、ObjectOutputStream。
  5. DataConversion数据流: 按基本数据类型读、写(处理的数据是Java的基本类型(如布尔型,字节,整数和浮点数)):DataInputStream、DataOutputStream 。
  6. Peeking Ahead预读流: 通过缓存机制,进行预读 :PushbackReader、PushbackInputStream。
  7. Printing打印流: 包含方便的打印方法 :PrintWriter、PrintStream

节点流:

  1. 文件:File开头的流
  2. 数组:CharArray或ByteArray开头的流
  3. 字符串:String开头的流
  4. 管道:Pipe开头的流

我们通过查看源码可以发现,所有的处理流都会有以 Reader、Writer、InputStream、OutputStream 作为参数的构造器,就是用来传入(装饰)节点流的。
而节点流中,我们可以发现他们也都是分别继承自 Reader、Writer、InputStream、OutputStream ,或者他们的子类 InputStreamReader、OutputStreamWriter,但是没有这些构造器。

nUluBF.md.jpg

输入流InputStream和Reader

InputStream 和 Reader 是所有输入流的抽象基类,本身并不能创建实例来执行输入,但他们将成为所有输入流的模板,所以他们的方法是所有输入流都可以使用的方法。

他们中包含如下三个方法(Reader的方法中的参数不是 byte[] 而是char[] ):

  • int read():从输入流中读取单个字节,返回所读取的字节数据(字节数据可直接转换为int类型)
  • int read(byte[] b):从输入流中最多读取 b.length 个字节的数据,并将其存储在字节数组 b 中,返回实际读取的字节数
  • int read(byte[] b, int off, int len):从输入流中最多读取 len 个字节的数据,并将其存储在数组 b 中,放入数组 b 中时,并不是从数组起点开始,而是从 off 位置开始,返回实际读取的字节数。

此处使用他们的子类,一个节点流 FileInputStream 示范一下读取文件内容的效果:

先在项目目录中创建一个 txt 文件,写入内容 “文件输入流” ,并命名为 test.txt 。
执行下面的程序:

public static void main(String[] args) {
		FileInputStream fis = null;
        try {
            fis = new FileInputStream("test.txt");
            byte[] bbuf = new byte[1024];
            int hasRead = 0;               //用来存储实际读取的数目
            while((hasRead = fis.read(bbuf))>0){     //重复读取,但文件内数据较少一次就可以读取完。
                System.out.println(new String(bbuf, 0, hasRead));  //将读出来的字节转为字符串输出
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

输出结果为

文件输入流

除此之外,InputStream 和 Reader 还支持如下几个方法来移动记录指针:

  • void mark(int readAheadLimit):在记录指针当前位置记录一个标记(mark)。
  • boolean markSupported():判断此输入流是否支持 mark() 操作,即是否支持记录标记。
  • void reset():将此流的记录指针重新定位到上一次记录的标记(mark)的位置。
  • long skip(int n):记录指针向前移动 n 个字节/字符

输出流OutputStream和Writer

OutputStream 和 Writer 也非常相似,两个流都提供了如下三个方法(Reader的方法中的参数不是 byte[] 而是char[] ):

  • int write(int c):将指定的字节/字符输出到输出流中,(其中 c 既可以代表字节,也可以代表字符)
  • int write(byte[] b):将字节数组/字符数组中的数据输出到指定输出流中。
  • int write(byte[] b, int off, int len):将字节数组/字符数组中从 off 位置开始,长度为 len 的字节/字符输出到输出流中

因为字符流直接以字符作为操作单位。所以 Writer 可以用字符串来代替字符数组,即以 String 对象作为参数。Writer 里还包含如下两个方法:

  • void write(String str):将 str 字符串里包含的字符输出到指定输出流中
  • void write(String str, int off, int len):将 str 字符串里从 off 位置开始,长度为 len 的字符输出到指定输出流中。

下面来个示范:

public static void main(String[] args) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream("test.txt");
            fos = new FileOutputStream("newtest.txt");
            byte[] bbuf = new byte[1024];
            int hasRead = 0;
            while((hasRead = fis.read(bbuf))>0){
                fos.write(bbuf, 0, hasRead);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fis.close();
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

执行完后,可以发现项目目录先多了一个 newtest.txt ,里面的内容与 test.txt 一致

注:
  • 当使用完IO流后,要记得关闭,如果有输出流有 flush() 方法,应在 close() 执行前执行一次,保证数据的完整输出,尤其是在非 Java 封装的流中
  • 如果用处理流包装了节点流,关闭处理流即可,节点流会由处理流自动关闭

转换流

  • InputStreamReader 将字节输入流转换成字符输入流。
  • OutputStreamWriter 将字节输出流转换成字符输出流。

Java使用System.in代表标准输入,即键盘输入,但这个标准输入流是字节输入流 InputStream 类的实例,使用不太方便,而且我们知道键盘输入内容都是文本内容,所以我们可以使用 InputStreamReader 将其转化成字符输入流,普通 Reader 读取输入内容时依然不太方便,我们可以将普通 Reader 再次包装成 BufferedReader ,利用 BufferedReader 的 readLine() 方法可以一次读取一行内容。如下程序所示:

public static void main(String[] args) {
        BufferedReader br = null;
        try{
            //将System.in对象转换成Reader对象
            InputStreamReader reader = new InputStreamReader(System.in);
            //将普通Reader包装成BufferedReader
            br = new BufferedReader(reader);
            String buffer = null;
            //采用循环方式来一行一行的读取
            while((buffer = br.readLine()) != null){
                //如果读取的字符串为"exit", 程序退出
                if(buffer.equals("exit")){
                    System.exit(1);
                }
                System.out.println("输入内容为:"+buffer);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try{
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

为什么没有字符流转为字节流的转换流?

字节流比字符流的使用范围更广,但字符流比字节流操作方便。如果有一个流已经是字符流了,也就是说是一个用起来更方便的流,为什么要转换成字节流呢?反之,如果现在已有一个字节流,但我们知道这个字节流的内容都是文本内容,那么把它转换成字符流来处理就会更方便一些,所以Java只提供了将字节流转换字符流的转换流,没有提供将字符流转换成字节流的转换流。

 
 
 
 
最后放张表,源自《疯狂Java讲义》
nDrtO0.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值