【JAVA IO 详细介绍】

目录

一、什么是IO?

1.1 IO的介绍

           IO是指对数据流的输入和输出,也称为IO流。Java所有的I/O机制都是基于数据流进行输入输出,这些数据流表示了字符或者字节数据的流动序列。Java的I/O流提供了读写数据的标准方法。任何Java中表示数据源的对象都会提供以数据流的方式读写它的数据的方法。

1.2 流的介绍

1.2.1 流的特征

           IO流的好处是简单易用,缺点是效率较低。

1.2.2 数据流的特征

           数据流是一组有序,有起点和终点的字节的数据序列(如图),它包括输入流和输出流。
在这里插入图片描述

1.2.3 输入流的特征

           输入流是程序从输入流读取数据源。数据源包括外界(键盘、文件、网络…),即是将数据源读入到程序的通信通道。比较通俗的来讲就是外部的文件通过输入流,传入到咱们的程序当中,程序去读取输入流内容(如图)。
在这里插入图片描述

1.2.4 输出流的特征

           输出流是程序向输出流写入数据。将程序中的数据输出到外界(显示器、打印机、文件、网络…)的通信通道。比较通俗的来讲就是程序中的内容通过输出流,输出给外部(如图)。
在这里插入图片描述

二、Java IO类库的框架

2.1 Java IO的类型

           通过第一点描述得知,其实Java的IO类库总体来说其实并不复杂。从是读写的的维度看,Java IO可以分为

  1. 输入流:InputStream和Reader
  2. 输出流:OutputStream和Writer

从其处理流的类型的维度上看,Java IO又可以分为:

  1. 字节流:InputStream和OutputStream
  2. 字符流:Reader和Writer

下面这幅图就清晰的描述了JavaIO的分类:

字节流字符流
输入流InputStreamReader
输出流OutputStreamWriter

2.2 Java IO的类库

           上面我们介绍了Java IO中的四各类:InputStream、OutputStream、Reader、Writer,其实在我们的实际应用中,我们用到的一般是它们的子类,之所以设计这么多子类,目的就是让每一个类都负责不同的功能,以方便我们开发各种应用。各类用途汇总如下:

  • 文件访问
  • 网络访问
  • 内存缓存访问
  • 线程内部通信(管道)
  • 缓冲
  • 过滤
  • 解析
  • 读写文本 (Readers / Writers)
  • 读写基本类型数据 (long, int etc.)
  • 读写对象

下面按I/O类型来总体分类:

  1. Memory
    (1)从/向内存数组读写数据: CharArrayReader、 CharArrayWriter、ByteArrayInputStream、ByteArrayOutputStream
    (2)从/向内存字符串读写数据 StringReader、StringWriter、StringBufferInputStream
  2. Pipe管道:实现管道的输入和输出(进程间通信): PipedReader、PipedWriter、PipedInputStream、PipedOutputStream
  3. File 文件流:对文件进行读、写操作 :FileReader、FileWriter、FileInputStream、FileOutputStream
  4. ObjectSerialization 对象输入、输出:ObjectInputStream、ObjectOutputStream
  5. DataConversion数据流:按基本数据类型读、写(处理的数据是Java的基本类型(如布尔型,字节,整数和浮点数)):DataInputStream、DataOutputStream
  6. Printing:包含方便的打印方法 :PrintWriter、PrintStream
  7. Buffering缓冲:在读入或写出时,对数据进行缓存,以减少I/O的次数:BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream
  8. Filtering 滤流:在数据进行读或写时进行过滤:FilterReader、FilterWriter、FilterInputStream、FilterOutputStream
  9. Concatenation合并输入:把多个输入流连接成一个输入流 :SequenceInputStream
  10. Counting计数 :在读入数据时对行记数 :LineNumberReader、LineNumberInputStream
  11. Peeking Ahead:通过缓存机制,进行预读 :PushbackReader、PushbackInputStream
  12. Converting between Bytes and Characters:按照一定的编码/解码标准将字节流转换为字符流,或进行反向转换(Stream到Reader,Writer的转换类):InputStreamReader、OutputStreamWriter

下面就以这张图来大体了解一下这些类的继承关系
在这里插入图片描述

三、Java IO的基本用法

3.1 字节流InputStream/OutputStream

3.1.1 InputStream抽象类

           InputStream 为字节输入流,它本身为一个抽象类,必须依靠其子类实现各种功能。继承自InputStream 的流都是向程序中输入数据的,且数据单位为字节(8bit),InputStream是输入字节数据用的类,所以InputStream类提供了3种重载的read方法.以下是Inputstream类中的常用方法:

  1. public abstract int read( ):读取一个byte的数据,返回值是高位补0的int类型值。若返回值=-1说明没有读取到任何字节读取工作结束。
  2. public int read(byte b[ ]):读取b.length个字节的数据放到b数组中。返回值是读取的字节数。该方法实际上是调用下一个方法实现的 。
  3. public int read(byte b[ ], int off, int len):从输入流中最多读取len个字节的数据,存放到偏移量为off的b数组中。
  4. public int available( ):返回输入流中可以读取的字节数。注意:若输入阻塞,当前线程将被挂起,如果InputStream对象调用这个方法的话,它只会返回0,这个方法必须由继承InputStream类的子类对象调用才有用。
  5. public long skip(long n):忽略输入流中的n个字节,返回值是实际忽略的字节数, 跳过一些字节来读取 。
  6. public int close( ) :我们在使用完后,必须对我们打开的流进行关闭。

主要的子类:在这里插入图片描述

3.1.2 OutputStream抽象类

           OutputStream提供了3个write方法来做数据的输出,这个是和InputStream是相对应的。

  1. public void write(byte b[ ]):将参数b中的字节写到输出流。
  2. public void write(byte b[ ], int off, int len) :将参数b的从偏移量off开始的len个字节写到输出流。
  3. public abstract void write(int b) :先将int转换为byte类型,把低字节写入到输出流中。
  4. public void flush( ) : 将数据缓冲区中数据全部输出,并清空缓冲区。
  5. public void close( ) : 关闭输出流并释放与流相关的系统资源。

主要的子类:
在这里插入图片描述

3.1.3 文件输入流:FileInputStream类

           FileInputStream可以使用read()方法一次读入一个字节,并以int类型返回,或者是使用read()方法时读入至一个byte数组,byte数组的元素有多少个,就读入多少个字节。在将整个文件读取完成或写入完毕的过程中,这么一个byte数组通常被当作缓冲区,因为这么一个byte数组通常扮演承接数据的中间角色。

例:用字节流去读文件

    public static void main(String[] args) throws Exception {
        //在E盘中新建一个Test.txt文件,内容填入 “hello China!”
        File file = new File("E:\\Test.txt");
        byte[] byteArray = new byte[(int) file.length()];
        //因为媒介对象是文件,所以用到子类是FileInputStream
        InputStream is = new FileInputStream(file);
        int size = is.read(byteArray);
        System.out.println("大小:" + size + ";内容:" + new String(byteArray));
        //输出为:大小:14;内容:hello China!
        is.close();
    }
3.1.4 文件输出流:FileOutputStream类

           用来处理以文件作为数据输出目的数据流;或者说是从内存区读数据入文件

例:用字节流去写文件

    public static void main(String[] args) throws Exception {
        String hello = "hello China!";
        byte[] byteArray = hello.getBytes();
        File file = new File("E:/Test.txt");
        //因为媒介对象是文件,所以用到子类是FileOutputStream
        OutputStream os = new FileOutputStream(file);
        os.write(byteArray);
        //执行后,观察E盘是否新建了一个Test.txt的文本文件,文件内容为"hello China!"
        os.close();
    }
3.1.5 缓冲输入输出流 BufferedInputStream/ BufferedOutputStream

           在第一大点有提到就是流它的读取效率是比较低的,原始的InputStream对数据读取的过程都是一个字节一个字节操作的,而BufferedInputStream在其内部提供了一个buffer,在读数据时,会一次读取一大块数据到buffer中,这样比单字节的操作效率要高的多,特别是进程磁盘IO和对大量数据进行读写的时候。所以BufferedInputStream顾名思义,就是在对流进行写入时提供一个buffer来提高IO效率。
           BufferedInputStream: BufferedInputStream比FileInputStream多了一个缓冲区。它提供了一个缓冲数组,每次调用read方法的时候,它首先尝试从缓冲区里读取数据,若读取失败(缓冲区无可读数据),则选择从物理数据源(譬如文件)读取新数据(这里会尝试尽可能读取多的字节)放入到缓冲区中,最后再将缓冲区中的内容部分或全部返回给用户.由于从缓冲区里读取数据远比直接从物理数据源(譬如文件)读取速度快。BufferedInputStream的默认缓冲区大小是8192字节。当每次读取的数据量很小时,FileInputStream每次都是从硬盘读入,而BufferedInputStream大部分是从缓冲区读入。读取内存速度比读取硬盘速度快得多,因此BufferedInputStream效率高.
           使用BufferedInputStream十分简单,只要把普通的输入流和BufferedInputStream组合到一起即可。我们把上面的例子改造成用BufferedInputStream进行读文件,请看下面例子:

    public static void main(String[] args) throws Exception {
        //在E盘中新建一个Test.txt文件,内容填入 “hello China!”
        File file = new File("E:\\Test.txt");
        byte[] byteArray = new byte[(int) file.length()];
        //可以在构造参数中传入buffer大小
        InputStream is = new BufferedInputStream( new FileInputStream(file),2*1024);
        int size = is.read(byteArray);
        System.out.println("大小:" + size + ";内容:" + new String(byteArray));
        //输出为:大小:14;内容:hello China!
        is.close();
    }

BufferedOutputStream进行写文件,同理,请看下面例子:

    public static void main(String[] args) throws Exception {
        String hello = "hello China!";
        byte[] byteArray = hello.getBytes();
        File file = new File("E:/Test.txt");
        //可以在构造参数中传入buffer大小
        OutputStream os = new BufferedOutputStream(new FileOutputStream(file),2*1024);
        os.write(byteArray);
        //执行后,观察E盘是否新建了一个Test.txt的文本文件,文件内容为"hello China!"
        os.close();
    }

备注:关于如何设置buffer的大小,我们应根据我们的硬件状况来确定。对于磁盘IO来说,如果硬盘每次读取4KB大小的文件块,那么我们最好设置成这个大小的整数倍。因为磁盘对于顺序读的效率是特别高的,所以如果buffer再设置的大写可能会带来更好的效率,比如设置成44KB或84KB。
还需要注意一点的就是磁盘本身就会有缓存,在这种情况下,BufferedInputStream会一次读取磁盘缓存大小的数据,而不是分多次的去读。所以要想得到一个最优的buffer值,我们必须得知道磁盘每次读的块大小和其缓存大小,然后根据多次试验的结果来得到最佳的buffer大小。

3.2 字符流Writer/Reader

3.2.1 Reader抽象类

           Reader是用于读取字符流的抽象类。子类必须实现的方法只有 read(char[], int, int) 和 close()。但是,多数子类将重写此处定义的一些方法,以提供更高的效率和/或其他功能。

主要的子类:
在这里插入图片描述
例:用字符流去读文件

    public static void main(String[] args) throws Exception{
        //在E盘中新建一个Test.txt文件,内容填入 “hello China!”
        File file = new File("E:\\Test.txt");
        //因为媒介对象是文件,所以用到子类是FileReader
        Reader reader= new FileReader(file);
        char [] byteArray= new char[(int) file.length()];
        int size= reader.read( byteArray);
        System. out.println("大小:"+size +";内容:" +new String(byteArray));
        //输出为:大小:14;内容:hello China!
        reader.close();
    }
3.2.2 Writer抽象类

           写入字符流的抽象类。子类必须实现的方法仅有 write(char[], int, int)、flush() 和 close()。但是,多数子类将重写此处定义的一些方法,以提供更高的效率和/或其他功能。

主要的子类:
在这里插入图片描述
例:用字符流去写文件

    public static void main(String[] args)  throws Exception{
        String hello= new String("hello China!");
        File file= new File("E:\\Test.txt");
        //因为是用字符流来读媒介,所以对应的是Writer,又因为媒介对象是文件,所以用到子类是FileWriter
        Writer os= new FileWriter(file);
        os.write(hello);
        //执行后,观察E盘是否新建了一个Test.txt的文本文件,文件内容为"hello China!"
        os.close();
    }
3.2.3 BufferedReader和BufferedWriter

           BufferedReader、BufferedWriter 的作用基本和BufferedInputStream、BufferedOutputStream一致,具体用法和原理都差不多 ,只不过一个是面向字符流一个是面向字节流。同样,我们将改造字符流中的例4,给其加上buffer功能,例:

    public static void main(String[] args)  throws Exception{
        String hello= new String( "hello China!");
        File file= new File( "E:\\Test.txt");
        //因为是用字符流来读媒介,所以对应的是Writer,又因为媒介对象是文件,所以用到子类是FileWriter
        Writer os= new BufferedWriter(new FileWriter(file));
        os.write( hello);
        //执行后,观察E盘是否新建了一个Test.txt的文本文件,文件内容为"hello China!"
        os.close();
    }

四、如何选择IO流?

4.1 字节流与字符流的区别?

想要选择用哪种IO流,首先,肯定是要先区分字节流与字符流之间的差异,以下是我总结几点:

  1. 字节流和字符流的用法几乎完成全一样,区别在于字节流和字符流所操作的数据单元不同,字节流操作的单元是数据单元是8位的字节,字符流操作的是数据单元为16位的字符。
  2. 字节流一般用来处理图像、视频、音频、PPT、Word等类型的文件。字符流一般用于处理纯文本类型的文件,如TXT文件等,但不能处理图像视频等非文本文件。用一句话说就是:字节流可以处理一切文件,而字符流只能处理纯文本文件。
  3. 字节流本身没有缓冲区,缓冲字节流相对于字节流,效率提升非常高。而字符流本身就带有缓冲区,缓冲字符流相对于字符流效率提升就不是那么大了。

4.2 确定是数据源和数据目的(输入还是输出)

           输入:InputStream、Reader
           输出:OutputStream、Writer

4.3 明确操作的数据对象是否是纯文本

           纯文本:Reader,Writer
           非纯文本:InputStream,OutputStream

五、如何关闭流?

5.1 必须关闭的流

          当我们新建一个java流对象之后,不仅在内存中创建了一个相应类的实例对象,而且还占用了相应的系统资源,比如:文件句柄、端口、网络连接等。在内存中的实例对象,当没有引用指向的时候,java垃圾收集器会按照相应的策略自动回收,但是却无法对系统资源进行释放。所以,我们需要主动调用close()方法释放java流对象。在java7之后,可以使用try-with-resources语句来释放java流对象,从而避免了try-catch-finally语句的繁琐,尤其是在finally子句中,close()方法也会抛出异常。

关闭流通用写法:

OutputStream out = null;
try {
	out = new FileOutputStream("");
	// ...操作流代码
} catch (Exception e) {
	e.printStackTrace();
} finally {
	try {
		if (out != null) {
			out.close();
		}
	} catch (Exception e) {
		e.printStackTrace();
	}
}

5.2 内存流可以不用关闭

          有些stream是不需要关闭的,比如ByteArrayOutputStream、ByteArrayIntputStream, StringWriter,点进ByteArrayOutputStream的close()方法发现,里面实现代码是空的,如图:
在这里插入图片描述
          为什么会这样呢?因为ByteArrayOutputStream内部实现是一个byte[]数组,也就是基于内存的字节数据访问,并没有占有文件句柄、端口、网络连接,就算不关闭,用完之后垃圾回收器也会回收。即然是操作内存,就要考虑内存大小,如果字节流太大就要考虑内存溢出。
          但是,我这边还是建议,只要是流,我们就给他关闭以下,反正不吃亏,万一那天记混了,线上环境报错吃亏的就是咱们了!

参考:
https://guisu.blog.csdn.net/article/details/7418161
https://blog.csdn.net/suifeng3051/article/details/48344587

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值