013-java中的IO操作-InputStream/Reader、OutputStream/Writer

邮差的信 提交于 2020-02-23 11:48:19

一、概述

  IO流用来处理设备之间的数据传输,上传文件和下载文件,Java对数据的操作是通过流的方式,Java用于操作流的对象都在IO包中。

  流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。

  注:java把所有的传统的流类型都放到在java.io包下,用于实现输入和输出功能。

1.1、IO流分类

  按照流的不同方向分为:输入流和输出流,

    凡是含有in的,都可理解为输入流,输入流即从文件读取到程序,只能从中读取数据,而不能向其写入数据,

    凡是含有out的,都可以理解为输出流,输出流即从程序输出到文件,只能向其写入数据,而不能向其读取数据。

    输入,输出都是从程序运行所在的内存的角度来划分的。

  按照实现功能不同可以分为:节点流和处理流。

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

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

  按照数据类型【单位分】:字节流:InputStream(字节输入流)、OutputStream(字节输出流);字符流:Reader(字符输入流)、Writer(字符输出流);因为数据编码的不同,而有了对字符进行高效操作的流对象。本质其实就是基于字节流读取时,去查了指定的码表。

1.1.1、字节流与字符流区别

  本质区别只有一个:字节流是原生的操作,而字符流是经过处理后的操作。

  在进行网络数据传输、磁盘数据保存所保存所支持的数据类型只有:字节。

  而所有磁盘中的数据必须先读取到内存后才能进行操作,而内存中会帮助我们把字节变为字符。字符更加适合处理中文。  

  读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。

  处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。

  字节流在操作的时候本身是不会用到缓冲区的,是文件本身的直接操作的;而字符流在操作的时候下后是会用到缓冲区的,是通过缓冲区来操作文件,

  结论:优先选用字节流。首先因为硬盘上的所有文件都是以字节的形式进行传输或者保存的,包括图片等内容。

  首先必须要明确一点,通过任何终端(网络、文件)读取或者输出的数据都一定是字节,字符是经过内存处理后的数据。

    字符输入:字节(磁盘)–> 自动转换为 –>字符(内存);

    字符输出:字符(内存)–> 自动转换为–>字节(磁盘);

  在利用字符流输出的时候,所有的内容实际上都只是输出到了缓冲区中(内存)。在使用close()方法关闭的时候会将我们缓冲区的数据进行输出,如果没有关闭,那么就将无法进行输出,此时可以利用flush()方法进行强制的输出。

  字符使用到了缓冲区,而字节流没有使用到缓冲区。

  如果处理中文使用字符流,其他的任何数据都使用字节流。

1.2、操作流程

  1、创建File类对象 ,主要是指明要操作的文件路径;
  2、根据字节流或字符流的子类实例化父类对象 ;
  3、进行数据的读取或写入操作;
  4、关闭流(close());

  注意:对于IO操作属于资源处理,所有的资源处理操作(IO操作、数据库操作、网络)最后必须要进行关闭。

1.3、流原理浅析

  java Io流共涉及40多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java Io流的40多个类都是从如下4个抽象类基类中派生出来的。

    InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。

    OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

  对于InputStream和Reader而言,它们把输入设备抽象成为一个”水管“,这个水管的每个“水滴”依次排列,如图15.5所示: 

  字节流和字符流的处理方式其实很相似,只是它们处理的输入/输出单位不同而已。输入流使用隐式的记录指针来表示当前正准备从哪个“水滴”开始读取,每当程序从InputStream或者Reader里面取出一个或者多个“水滴”后,记录指针自定向后移动;除此之外,InputStream和Reader里面都提供了一些方法来控制记录指针的移动。

  

  对于OutputStream和Writer而言,它们同样把输出设备抽象成一个”水管“,只是这个水管里面没有任何水滴,如图15.6所示:

  当执行输出时,程序相当于依次把“水滴”放入到输出流的水管中,输出流同样采用隐示指针来标识当前水滴即将放入的位置,每当程序向OutputStream或者Writer里面输出一个或者多个水滴后,记录指针自动向后移动。 

     

  以上显示了java Io的基本概念模型,除此之外,Java的处理流模型则体现了Java输入和输出流设计的灵活性。处理流的功能主要体现在以下两个方面。

  • 性能的提高:主要以增加缓冲的方式来提供输入和输出的效率。
  • 操作的便捷:处理流可能提供了一系列便捷的方法来一次输入和输出大批量的内容,而不是输入/输出一个或者多个“水滴”。

  处理流可以“嫁接”在任何已存在的流的基础之上,这就允许Java应用程序采用相同的代码,透明的方式来访问不同的输入和输出设备的数据流。图15.7显示了处理流的模型。

    

1.4、java输入/输出流体系中常用的流的分类表  

分类字节输入流字节输出流字符输入流字符输出流
抽象基类 InputStream OutputStream Reader Writer
访问文件 FileInputStream FileOutputStream FileReader FileWriter
访问数组 ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter
访问管道 PipedInputStream PipedOutputStream PipedReader PipedWriter
访问字符串     StringReader StringWriter
缓冲流 BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter
转换流     InputStreamReader OutputStreamWriter
对象流 ObjectInputStream ObjectOutputStream    
抽象基类 FilterInputStream FilterOutputStream FilterReader FilterWriter
打印流   PrintStream   PrintWriter
推回输入流 PushbackInputStream   PushbackReader  
特殊流 DataInputStream DataOutputStream    

注:表中粗体字所标出的类代表节点流,必须直接与指定的物理节点关联:斜体字标出的类代表抽象基类,无法直接创建实例。

二、常用IO流基础用法

2.1、 IO体系的基类(InputStream/Reader,OutputStream/Writer)

  字节流和字符流的操作方式基本一致,只是操作的数据单元不同——字节流的操作单元是字节,字符流的操作单元是字符。

2.1.1、InputStream和Reader输入流

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

  a、在InputStream里面包含如下3个方法。

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

  b、在Reader中包含如下3个方法。

  • int read(); 从输入流中读取单个字符(相当于从图15.5所示的水管中取出一滴水),返回所读取的字符数据(字节数据可直接转换为int类型)。
  • int read(char[] b)从输入流中最多读取b.length个字符的数据,并将其存储在字节数组b中,返回实际读取的字符数。
  • int read(char[] b,int off,int len); 从输入流中最多读取len个字符的数据,并将其存储在数组b中,放入数组b中时,并不是从数组起点开始,而是从off位置开始,返回实际读取的字符数。

  直到read(char[] chuf)或者read(byte[] b)方法返回-1,即表明到了输入流的结束点。 

  c、InputStream和Reader提供的一些移动指针的方法:

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

2.1.2、OutputStream和Writer输出流

  两个流都提供了如下三个方法:

  • void write(int c); 将指定的字节/字符输出到输出流中,其中c即可以代表字节,也可以代表字符。
  • void write(byte[]/char[] buf); 将字节数组/字符数组中的数据输出到指定输出流中。
  • void write(byte[]/char[] buf, 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的字符输出到指定输出流中。

2.2、使用

以下包含内容:

  2.2.1、基础文件流【FileInputStream/FileReader ,FileOutputStream/FileWriter】

  2.2.2、缓冲流【BufferedInputStream/BufferedReader, BufferedOutputStream/BufferedWriter】

  2.2.3、转换流的使用(InputStreamReader/OutputStreamWriter)

  2.3.4、对象流的使用(ObjectInputStream/ObjectOutputStream)

  2.3.5、字节数组内存流【ByteArrayInputStream、ByteArrayOutputStream

  2.3.6、打印流【PrintStream、PrintWriter】

  2.3.7、随机访问流【RandomAccessFile】

  2.3.8、合并流【SequenceInputStream】  

  2.3.9、数据流【DataInputStream、DataOutputStream】

  2.3.10、管道流【PipedInputStream(字节输入流),PipedOutStream(字节输出流),PipedReader(字符输入流),PipedWriter(字符输出流)】

  2.3.11、异步流【NIO】

2.2.1、基础文件流【FileInputStream/FileReader ,FileOutputStream/FileWriter】

  参看代码:common 的 common-iostream

  注:上面程序最后使用了fis.close()来关闭该文件的输入流,与JDBC编程一样,程序里面打开的文件IO资源不属于内存的资源,垃圾回收机制无法回收该资源,所以应该显示的关闭打开的IO资源。Java 7改写了所有的IO资源类,它们都实现了AntoCloseable接口,因此都可以通过自动关闭资源的try语句来关闭这些Io流。 

  注: 使用java的io流执行输出时,不要忘记关闭输出流,关闭输出流除了可以保证流的物理资源被回收之外,可能还可以将输出流缓冲区中的数据flush到物理节点中里(因为在执行close()方法之前,自动执行输出流的flush()方法)。java很多输出流默认都提供了缓存功能,其实我们没有必要刻意去记忆哪些流有缓存功能,哪些流没有,只有正常关闭所有的输出流即可保证程序正常。

2.2.2、缓冲流【BufferedInputStream/BufferedReader, BufferedOutputStream/BufferedWriter】

  参看代码:common 的 common-iostream

  注意:当我们使用处理流套接到节点流上的使用的时候,只需要关闭最上层的处理就可以了。java会自动帮我们关闭下层的节点流。

2.2.3、转换流的使用(InputStreamReader/OutputStreamWriter)

  java使用System.in代表输入。即键盘输入,但这个标准输入流是InputStream类的实例,使用不太方便,而且键盘输入内容都是文本内容,所以可以使用InputStreamReader将其包装成BufferedReader,利用BufferedReader的readLine()方法可以一次读取一行内容

public class InputStreamReaderDemo {
    public static void main(String[] args) throws IOException {
        try {
            // 将System.in对象转化为Reader对象
            InputStreamReader reader = new InputStreamReader(System.in);
            //将普通的Reader包装成BufferedReader
            BufferedReader bufferedReader = new BufferedReader(reader);
            String buffer = null;
            while ((buffer = bufferedReader.readLine()) != null) {
                // 如果读取到的字符串为“exit”,则程序退出
                if (buffer.equals("exit")) {
                    System.exit(1);
                }
                //打印读取的内容
                System.out.print("输入内容:" + buffer);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        }
    }
}

  上面程序将System.in包装成BufferedReader,BufferedReader流具有缓存功能,它可以一次读取一行文本——以换行符为标志,如果它没有读到换行符,则程序堵塞。等到读到换行符为止。运行上面程序可以发现这个特征,当我们在控制台执行输入时,只有按下回车键,程序才会打印出刚刚输入的内容。

2.3.4、对象流的使用(ObjectInputStream/ObjectOutputStream)

  参看代码:common 的 common-iostream中ObjectStreamDemo

  注:ObjectInputStream(对象输入流)继承于InputStream,ObjectOutputStream(对象输出流)继承于OutputStream。

  对象流是将对象的基本数据和图形实现持久存储。ObjectOutputStream实际是在对流进行序列化操作,ObjectInputStream实际是在对流进行反序列化操作,要实现序列化,必须实现Serializable接口,否则是无法进行序列化和反序列化的,

  如果对象中的属性加了transient和static关键字的话,则该属性不会被序列化。

  常用的方法是:readObject(),从ObjectInputStream读取对象;writeObject(Object obj),将指定的对象写入ObjecctOutputStream;close(),关闭该流

2.3.5、字节数组内存流【ByteArrayInputStream、ByteArrayOutputStream

  参看代码:common 的 common-iostream   

  ByteArrayInputStream(内存输入流)继承于InputStream,ByteArrayOutputStream(内存输出流)继承于OutputStream。

  内存流是关不掉的,一般用来存放一些临时性的数据,理论值是内存大小。

2.3.6、打印流【PrintStream、PrintWriter】

  参看代码:common 的 common-iostream中PrintStreamAndWriterDemo 

2.3.7、随机访问流【RandomAccessFile】

  参看代码:common 的 common-iostream中RandomAccessFileDemo

2.3.8、合并流【SequenceInputStream】

  参看代码:common 的 common-iostream中SequenceInputStreamDemo

2.3.9、数据流【DataInputStream、DataOutputStream】

  DataOutputStream数据输出流允许应用程序将基本Java数据类型写到基础输出流中,而DataInputStream数据输入流允许应用程序以机器无关的方式从底层输入流中读取基本的Java类型.

  参看代码:common 的 common-iostream中DataStreamDemo

2.3.10、管道流【PipedInputStream(字节输入流),PipedOutStream(字节输出流),PipedReader(字符输入流),PipedWriter(字符输出流)】

  在java中,PipedOutputStreamPipedInputStream分别是管道输出流和管道输入流。

  它们的作用是让多线程可以通过管道进行线程间的通讯。在使用管道通信时,必须将PipedOutputStream和PipedInputStream配套使用。

  使用管道通信时,大致的流程是:我们在线程A中向PipedOutputStream中写入数据,这些数据会自动的发送到与PipedOutputStream对应的PipedInputStream中,进而存储在PipedInputStream的缓冲中;此时,线程B通过读取PipedInputStream中的数据。就可以实现,线程A和线程B的通信。  

  参看代码:common 的 common-iostream中pipedstream

2.3.11、异步流【NIO】

  io流大体分为BIO,AIO,NIO,常用的基本就是BIO,也就是之前文章所介绍的那些,接下来我们来大概了解一下NIO。

  传统io是靠字节或字符来传输,nio是靠块传输,也就是一个一个的buffer速度相对于较快。传统io是阻塞型io,nio是非阻塞型io。多适用于进行流畅的网络读写操作。

  参看代码:common 的 common-iostream中NIODemo

 

  

2.4、何为NIO,和传统Io有何区别

  我们使用InputStream从输入流中读取数据时,如果没有读取到有效的数据,程序将在此处阻塞该线程的执行。其实传统的输入里和输出流都是阻塞式的进行输入和输出。 不仅如此,传统的输入流、输出流都是通过字节的移动来处理的(即使我们不直接处理字节流,但底层实现还是依赖于字节处理),也就是说,面向流的输入和输出一次只能处理一个字节,因此面向流的输入和输出系统效率通常不高。 

  从JDk1.4开始,java提供了一系列改进的输入和输出处理的新功能,这些功能被统称为新IO(NIO)。新增了许多用于处理输入和输出的类,这些类都被放在java.nio包及其子包下,并且对原io的很多类都以NIO为基础进行了改写。新增了满足NIO的功能。 

  NIO采用了内存映射对象的方式来处理输入和输出,NIO将文件或者文件的一块区域映射到内存中,这样就可以像访问内存一样来访问文件了。通过这种方式来进行输入/输出比传统的输入和输出要快的多。

  JDk1.4使用NIO改写了传统Io后,传统Io的读写速度和NIO差不了太多。 

2.5、文件拷贝

  参看代码:common 的 common-iostream中CopyFileDemo

  1024字节数组复制并用了缓冲流 的方式效率最高  

2.6、小结

  • 如果是操作二进制文件那我们就使用字节流,如果操作的是文本文件那我们就使用字符流。
  • 尽可能的多使用处理流,这会使我们的代码更加灵活,复用性更好。

 

 

 

 

 

 

 

 

地方

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!