[IO学习笔记]一、理清庞大的Java IO体系

1,775 阅读13分钟

Java IO系列文章:

  1. IO学习笔记]一、理清庞大的Java IO体系
  2. IO学习笔记]二、文件操作不得不了解的Path, Files, RondomAccessFile
  3. IO学习笔记]三、序列化与反序列化

一、概述

1. IO流的概念

在Java类库中,IO部分的内容是很庞大的,因为它涉及的领域很广泛:

  • 标准输入输出
  • 文件的操作
  • 网络上的数据流
  • 字符串流
  • 对象流
  • zip文件流.....

java中将输入输出抽象称为,就好像水管,将两个容器连接起来。将数据从外存中读取到内存中的称为输入流,将数据从内存写入外存中的称为输出流。

2. IO流的划分

2.1 根据流向划分

输入流输出流

注意输入流和输出流是相对于程序而言的

2.2 根据传输数据单位划分

字节流字符流

上面的也是 Java IO流中的四大基流。这四大基流都是抽象类,其他流都是继承于这四大基流的。

  1. 字节流:数据流中最小的数据单元是字节
  2. 字符流:数据流中最小的数据单元是字符

2.3 根据功能划分

节点流包装流

节点流:可以从或向一个特定的地方(节点)读写数据,直接连接数据源。如最常见的是文件的FileReader,还可以是数组、管道、字符串,关键字分别为ByteArray/CharArray,Piped,String。

处理流(包装流):并不直接连接数据源,是对一个已存在的流的连接和封装,是一种典型的装饰器设计模式,使用处理流主要是为了更方便的执行输入输出工作,如PrintStream,输出功能很强大,又如BufferedReader提供缓存机制,推荐输出时都使用处理流包装

2.4 一些特别的流类型

转换流:转换流只有字节流转换为字符流,因为字符流使用起来更方便,我们只会向更方便使用的方向转化。如:InputStreamReader与OutputStreamWriter。

缓冲流:有关键字Buffered,也是一种处理流,为其包装的流增加了缓存功能,提高了输入输出的效率,增加缓冲功能后需要使用flush()才能将缓冲区中内容写入到实际的物理节点。但是,在现在版本的Java中,只需记得关闭输出流(调用close()方法),就会自动执行输出流的flush()方法,可以保证将缓冲区中内容写入。

对象流:有关键字Object,主要用于将目标对象保存到磁盘中或允许在网络中直接传输对象时使用(对象序列化)。

操作IO流的模板:

  1. 创建源或目标对象
  2. 创建IO流对象
    • 进行包装
  3. 具体的IO操作
  4. 关闭资源

注意:程序中打开的文件 IO 资源不属于内存里的资源,垃圾回收机制无法回收该资源。如果不关闭该资源,那么磁盘的文件将一直被程序引用着,不能删除也不能更改。所以应该手动调用 close() 方法关闭流资源。

Java IO流整体架构图

二、标准IO

Java程序可通过命令行参数与外界进行简短的信息交换,同时,也规定了与标准输入、输出设备,如键盘、显示器进行信息交换的方式。

1. 标准输入 / 输出数据流

java系统自带的标准数据流:java.lang.System:

java.lang.System   
public final class System  extends Object{   
   static  PrintStream  err;//标准错误流(输出)  
   static  InputStream  in;//标准输入(键盘输入流)  
   static  PrintStream  out;//标准输出流(显示器输出流)  
}

注意: (1)System类不能创建对象,只能直接使用它的三个静态成员。 (2)每当main方法被执行时,就自动生成上述三个对象。

2. System.out

System.out向标准输出设备输出数据,其数据类型为PrintStream。方法:

  • Void print(参数)
  • Void println(参数)

3. System.in

System.in读取标准输入设备数据(从标准输入获取数据,一般是键盘),其数 据类型为InputStream。方法:

  • int read() // 返回ASCII码。若,返回值=-1,说明没有读取到任何字节读取工作结束。
  • int read(byte[] b) // 读入多个字节到缓冲区b中,返回值是读入的字节数

4. System.err

System.err输出标准错误,其数据类型为PrintStream。可查阅API获得详细说明。

三、Input / outputStream

inputStream的典型实现FileInputStream例子:

//1、创建目标对象,输入流表示那个文件的数据保存到程序中。不写盘符,默认该文件是在该项目的根目录下
    //a.txt 保存的文件内容为:AAaBCDEF
File target = new File("io"+File.separator+"a.txt");
//2、创建输入流对象
InputStream in = new FileInputStream(target);
//3、具体的 IO 操作(读取 a.txt 文件中的数据到程序中)
    /**
     * 注意:读取文件中的数据,读到最后没有数据时,返回-1
     *  int read():读取一个字节,返回读取的字节
     *  int read(byte[] b):读取多个字节,并保存到数组 b 中,从数组 b 的索引为 0 的位置开始存储,返回读取了几个字节
     *  int read(byte[] b,int off,int len):读取多个字节,并存储到数组 b 中,从数组b 的索引为 0 的位置开始,长度为len个字节
     */
//int read():读取一个字节,返回读取的字节
int data1 = in.read();//获取 a.txt 文件中的数据的第一个字节
System.out.println((char)data1); //A
//int read(byte[] b):读取多个字节保存到数组b 中
byte[] buffer  = new byte[10];
in.read(buffer);//获取 a.txt 文件中的前10 个字节,并存储到 buffer 数组中
System.out.println(Arrays.toString(buffer)); //[65, 97, 66, 67, 68, 69, 70, 0, 0, 0]
System.out.println(new String(buffer)); //AaBCDEF[][][]

//int read(byte[] b,int off,int len):读取多个字节,并存储到数组 b 中,从索引 off 开始到 len
in.read(buffer, 0, 3);
System.out.println(Arrays.toString(buffer)); //[65, 97, 66, 0, 0, 0, 0, 0, 0, 0]
System.out.println(new String(buffer)); //AaB[][][][][][][]
//4、关闭流资源
in.close();

outputStream的典型实现FileOutputStream例子:

//1、创建目标对象,输出流表示把数据保存到哪个文件。不写盘符,默认该文件是在该项目的根目录下
File target = new File("io"+File.separator+"a.txt");
//2、创建文件的字节输出流对象,第二个参数是 Boolean 类型,true 表示后面写入的文件追加到数据后面,false 表示覆盖
OutputStream out = new FileOutputStream(target,true);
//3、具体的 IO 操作(将数据写入到文件 a.txt 中)
    /**
     * void write(int b):把一个字节写入到文件中
     * void write(byte[] b):把数组b 中的所有字节写入到文件中
     * void write(byte[] b,int off,int len):把数组b 中的从 off 索引开始的 len 个字节写入到文件中
     */
out.write(65); //将 A 写入到文件中
out.write("Aa".getBytes()); //将 Aa 写入到文件中
out.write("ABCDEFG".getBytes(), 1, 5); //将 BCDEF 写入到文件中
//经过上面的操作,a.txt 文件中数据为 AAaBCDEF

//4、关闭流资源
out.close();
System.out.println(target.getAbsolutePath());

一个复制文件的例子:

/**
 * 将 a.txt 文件 复制到 b.txt 中
 */
//1、创建源和目标
File srcFile = new File("io"+File.separator+"a.txt");
File descFile = new File("io"+File.separator+"b.txt");
//2、创建输入输出流对象
InputStream in = new FileInputStream(srcFile);
OutputStream out = new FileOutputStream(descFile);
//3、读取和写入操作
byte[] buffer = new byte[10];//创建一个容量为 10 的字节数组,存储已经读取的数据
int len = -1;//表示已经读取了多少个字节,如果是 -1,表示已经读取到文件的末尾
while((len=in.read(buffer))!=-1){
    //打印读取的数据
    System.out.println(new String(buffer,0,len));
    //将 buffer 数组中从 0 开始,长度为 len 的数据读取到 b.txt 文件中
    out.write(buffer, 0, len);
}
//4、关闭流资源
out.close();
in.close();

四、Reader / Writer

  1. 为什么要用字符流? 因为使用字节流操作汉字或特殊符号语言的时候容易乱码,因为汉字不止一个字节,为了解决这个问题,建议使用字符流。
  2. 什么情况下使用字符流? 一般可以用记事本打开的文件,我们可以看到内容不乱码的。就是文本文件,可以使用字符流。而操作二进制文件(比如图片、音频、视频)必须使用字节流

Writer的典型实现FileWriter例子:

//1、创建源
File srcFile = new File("io"+File.separator+"a.txt");
//2、创建字符输出流对象
Writer out = new FileWriter(srcFile);
//3、具体的 IO 操作
    /***
     * void write(int c):向外写出一个字符
     * void write(char[] buffer):向外写出多个字符 buffer
     * void write(char[] buffer,int off,int len):把 buffer 数组中从索引 off 开始到 len个长度的数据写出去
     * void write(String str):向外写出一个字符串
     */
//void write(int c):向外写出一个字符
out.write(65);//将 A 写入 a.txt 文件中
//void write(char[] buffer):向外写出多个字符 buffer
out.write("Aa帅锅".toCharArray());//将 Aa帅锅 写入 a.txt 文件中
//void write(char[] buffer,int off,int len)
out.write("Aa帅锅".toCharArray(),0,2);//将 Aa 写入a.txt文件中
//void write(String str):向外写出一个字符串
out.write("Aa帅锅");//将 Aa帅锅 写入 a.txt 文件中

//4、关闭流资源
/***
 * 注意如果这里有一个 缓冲的概念,如果写入文件的数据没有达到缓冲的数组长度,那么数据是不会写入到文件中的
 * 解决办法:手动刷新缓冲区 flush()
 * 或者直接调用 close() 方法,这个方法会默认刷新缓冲区
 */
out.flush();
out.close();

Reader的典型实现FileReader例子:

//1、创建源
File srcFile = new File("io"+File.separator+"a.txt");
//2、创建字符输出流对象
Reader in = new FileReader(srcFile);
//3、具体的 IO 操作
    /***
     * int read():每次读取一个字符,读到最后返回 -1
     * int read(char[] buffer):将字符读进字符数组,返回结果为读取的字符数
     * int read(char[] buffer,int off,int len):将读取的字符存储进字符数组 buffer,返回结果为读取的字符数,从索引 off 开始,长度为 len
     *
     */
//int read():每次读取一个字符,读到最后返回 -1
int len = -1;//定义当前读取字符的数量
while((len = in.read())!=-1){
    //打印 a.txt 文件中所有内容
    System.out.print((char)len);
}

//int read(char[] buffer):将字符读进字符数组
char[] buffer = new char[10]; //每次读取 10 个字符
while((len=in.read(buffer))!=-1){
    System.out.println(new String(buffer,0,len));
}

//int read(char[] buffer,int off,int len)
while((len=in.read(buffer,0,10))!=-1){
    System.out.println(new String(buffer,0,len));
}
//4、关闭流资源
in.close();

用字符流完成复制:

/**
* 将 a.txt 文件 复制到 b.txt 中
*/
//1、创建源和目标
File srcFile = new File("io"+File.separator+"a.txt");
File descFile = new File("io"+File.separator+"b.txt");
//2、创建字符输入输出流对象
Reader in = new FileReader(srcFile);
Writer out = new FileWriter(descFile);
//3、读取和写入操作
char[] buffer = new char[10];//创建一个容量为 10 的字符数组,存储已经读取的数据
int len = -1;//表示已经读取了多少个字节,如果是 -1,表示已经读取到文件的末尾
while((len=in.read(buffer))!=-1){
   out.write(buffer, 0, len);
}

//4、关闭流资源
out.close();
in.close();

五、包装流

  • 包含缓冲流,转换流对象流等等
  • 包装流隐藏了底层节点流的差异,并对外提供了更方便的输入\输出功能,让我们只关心这个高级流的操作
  • 使用包装流包装了节点流,程序直接操作包装流,而底层还是节点流和IO设备操作
  • 关闭包装流的时候,只需要关闭包装流即可

1. 缓冲流

1.1 概述

缓冲流:是一个包装流,目的是缓存作用,加快读取和写入数据的速度。

字节缓冲流:BufferedInputStreamBufferedOutputStream

字符缓冲流:BufferedReaderBufferedWriter

我们查看 缓冲流的 JDK 底层源码,可以看到,程序中定义了这样的 缓存数组,大小为 8192

BufferedInputStream: ![image-20200920101916672](1. IO输入输出流.assets/image-20200920101916672.png)

案情回放:我们在将字符输入输出流、字节输入输出流的时候,读取操作,通常都会定义一个字节或字符数组,将读取/写入的数据先存放到这个数组里面,然后在取数组里面的数据。这比我们一个一个的读取/写入数据要快很多,而这也就是缓冲流的由来。只不过缓冲流里面定义了一个 数组用来存储我们读取/写入的数据,当内部定义的数组满了(注意:我们操作的时候外部还是会定义一个小的数组,小数组放入到内部数组中),就会进行下一步操作。

1.2 例子

字节缓冲流:

//字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(
        new FileInputStream("io"+File.separator+"a.txt"));
//定义一个 字节 数组,用来存储数据
byte[] buffer = new byte[1024];
int len = -1;//定义一个整数,表示读取的字节数
while((len=bis.read(buffer))!=-1){
    System.out.println(new String(buffer,0,len));
}
//关闭流资源
bis.close();<br><br>

//字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(
        new FileOutputStream("io"+File.separator+"a.txt"));
bos.write("ABCD".getBytes());
bos.close();

字符缓冲流:

//字符缓冲输入流
BufferedReader br = new BufferedReader(
        new FileReader("io"+File.separator+"a.txt"));
         // 此处定义的是字符数组,不同与字节流
char[] buffer = new char[10];
int len = -1;
while((len=br.read(buffer))!=-1){
    System.out.println(new String(buffer,0,len));
}
br.close();

//字符缓冲输出流
BufferedWriter bw = new BufferedWriter(
        new FileWriter("io"+File.separator+"a.txt"));
bw.write("ABCD");
bw.close();

2. 转换流

2.1 概述

  • InputStreamReader:把字节输入流转换为字符输入流
  • OutputStreamWriter:把字节输出流转换为字符输出流
/**
 * 将 a.txt 文件 复制到 b.txt 中
 */
//1、创建源和目标
File srcFile = new File("io"+File.separator+"a.txt");
File descFile = new File("io"+File.separator+"b.txt");
//2、创建字节输入输出流对象
InputStream in = new FileInputStream(srcFile);
OutputStream out = new FileOutputStream(descFile);
//3、创建转换输入输出对象
Reader rd = new InputStreamReader(in);
Writer wt = new OutputStreamWriter(out);
//3、读取和写入操作
char[] buffer = new char[10];//创建一个容量为 10 的字符数组,存储已经读取的数据
int len = -1;//表示已经读取了多少个字符,如果是 -1,表示已经读取到文件的末尾
while((len=rd.read(buffer))!=-1){
    wt.write(buffer, 0, len);
}
//4、关闭流资源
rd.close();
wt.close();

2.2 转换流和子类的区别

发现有如下继承关系: OutputStreamWriter: |--FileWriter:

InputStreamReader: |--FileReader;

OutputStreamWriter和InputStreamReader是字符和字节的桥梁:也可以称之为字符转换流。字符转换流原理:字节流+编码表

  • InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"));//默认字符集。
  • InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"),"GBK");//指定GBK字符集。
  • FileReader fr = new FileReader("a.txt");

这三句代码的功能是一样的,其中第三句最为便捷。

注意:一旦要指定其他编码时,绝对不能用子类,必须使用字符转换流。什么时候用子类呢?

  1. 操作的是文件。
  2. 使用默认编码。

3. 内存流(数组流)

3.1 概述

把数据先临时存在数组中,也就是内存中,之后需要使用的时候直接从内存中获得即可。所以关闭内存流是无效的,关闭后还是可以调用这个类的方法。底层源码的 close()是一个空方法。

3.2 字节内存流

  • ByteArrayOutputStream
  • ByteArrayInputStream
//字节数组输出流:程序---》内存
ByteArrayOutputStream bos = new ByteArrayOutputStream();
//将数据写入到内存中
bos.write("ABCD".getBytes());
//创建一个新分配的字节数组。 其大小是此输出流的当前大小,缓冲区的有效内容已被复制到其中。
byte[] temp = bos.toByteArray();
System.out.println(new String(temp,0,temp.length));

byte[] buffer = new byte[10];
///字节数组输入流:内存---》程序
ByteArrayInputStream bis = new ByteArrayInputStream(temp);
int len = -1;
while((len=bis.read(buffer))!=-1){
    System.out.println(new String(buffer,0,len));
}

//这里不写也没事,因为源码中的 close()是一个空的方法体
bos.close();
bis.close();

3.3 字符内存流

  • StringReader
  • StringWriter
//字符串输出流,底层采用 StringBuffer 进行拼接
StringWriter sw = new StringWriter();
sw.write("ABCD");
sw.write("帅锅");
System.out.println(sw.toString());//ABCD帅锅

//字符串输入流
StringReader sr = new StringReader(sw.toString());
char[] buffer = new char[10];
int len = -1;
while((len=sr.read(buffer))!=-1){
    System.out.println(new String(buffer,0,len));//ABCD帅锅
}

4. 合并流

把多个输入流合并为一个流,也叫顺序流,因为在读取的时候是先读第一个,读完了在读下面一个流。

//定义字节输入合并流
SequenceInputStream seinput = new SequenceInputStream(
        new FileInputStream("io/a.txt"), new FileInputStream("io/b.txt"));
byte[] buffer = new byte[10];
int len = -1;
while((len=seinput.read(buffer))!=-1){
    System.out.println(new String(buffer,0,len));
}

seinput.close();

5. 对象流

详情涉及到序列化与反序列化,请查看相关文章:

IO学习笔记]三、序列化与反序列化


参考:www.cnblogs.com/furaywww/p/…