Java IO

我的梦境 提交于 2020-01-08 12:11:07

java IO

1.IO相关概念

1.1什么是IO

所谓IO即input和output的缩写,是对数据的流入和流出的一种抽象,编程中很常见的一个概念。

1.2什么是流

体会一下这几个词:水流(静止的水想必没人会叫水流),物流,人流(此人流非彼人流 = =!),可以发现流的特点:动态的,可转移

的,从一处到另一处的

1.3 java io

java为了我们调用方便,而屏蔽输入/输出源和流动细节,抽象出的用于解决数据流动问题的类体系,这就是java的io流

1.4 输入流和输出流

用于读取的流称为输入流,用于写入的流称为输出流。输入输出的概念一般针对内存来说的

1.5 字节流和字符流

输入输出流可操作的最小单位来区分字节流和字符流,最小单位是一个字节(8bit)的为字节流,

最小单位是一个字符(16bit)的为字符流。

1.6 节点流和包装(处理)流

1)节点流偏向实现细节,直接与细节打交道,比如FileInputStream,而包装(处理)流偏功能,以目标功能为抽象,比如PrintStream。

2)区分节点流和包装(处理)流最简单的一个方式:处理流的构造方法中需要另一个流作为参数,而节点流构造方法则是具体的物理节点,如上FileInputStream构造法中需要一个文件路径或者File对象,

而PrintStream构造方法中则需要一个流对象

3)包装流使用了装饰器模式(什么是装饰器模式?传送门),包装流对节点流进行了一系列功能的强化包装,让包装后的流拥有了更多的操作手段或更高的操作效率,而隐藏节点流底层的复杂性。

1.7 低级流和高级流

低级流和高级流对应的概念即对应上面的节点流和包装(处理)流概念

1.8 普通流和缓冲流

普通流和缓冲流主要是针对读写性能上提出的相对概念。普通流与缓冲流的区别在于一个一个数据的流

动还是一堆一堆数据的流动。

1.9 bio,nio,aio

bio:阻塞式IO模型:服务端启动,等待客户端的连接,在客户端连接到服务端后,服务端启动一个线程去监听客户端消息,客户端发送消息,并等待服务端返回(客户端一直阻塞),

服务端收到消息,将消息返回给客户端,此时一次交互完成。若还需交互,则不释放连接,客户端再次将消息发送给服务端,并等待返回,若不需要交互,则客户端释放连接。

nio: 同步非阻塞的 IO 模型。同步是指线程不断轮询 IO 事件是否就绪,非阻塞是指线程在等待 IO 的时候,可以同时做其他任务。同步的核心就是 Selector,Selector 代替了线程本身轮询 IO 事件,避免了阻塞同时减少了不必要的线程消耗;非阻塞的核心就是通道和缓冲区,当 IO 事件就绪时,可以通过写道缓冲区,保证 IO 的成功,而无需线程阻塞式地等待。

aio:异步非阻塞IO、是在JDK 1.7的时候才推出的模型。     是利用了本地操作系统的IO操作处理模式,当有IO操作产生之后,会启动有一个单独的线程,它将所有的IO操作全部交由系统完成,只需要知道返回结果即可。     主要的模式是基于操作回调的方式来完成处理的,如果以烧水为例:在烧水的过程之中你不需要去关注这个水的状态,在烧水完成后才进行通知。

发展历程:bio(jdk1.0) -> nio(jdk1.4) -> aio(jdk1.7)

下面我们来说说bio

bio类体系

1.1 体系图

这里写图片描述

 

1.2 从数据来源或者操作对象角度看,IO类还可以划分为:

  1. 文件(file):FileInputStream、FileOutputStream、FileReader、FileWriter
  2. 数组([]): 

    字节数组(byte[]):ByteArrayInputStream、ByteArrayOutputStream

  3. 字符数组(char[]):CharArrayReader、CharArrayWriter

  4. 管道操作:PipedInputStream、PipedOutputStream、PipedReader、PipedWriter
  5. 基本数据类型:DataInputStream、DataOutputStream
  6. 缓冲操作:BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter
  7. 打印:PrintStream、PrintWriter
  8. 对象序列化反序列化:ObjectInputStream、ObjectOutputStream
  9. 转换:InputStreamReader、OutputStreWriter
  10. 字符串(String):StringBufferInputStream、StringBufferOutputStream、StringReader、StringWriter

 

2.BIO体系中的类该怎么使用?

要回答这个问题,我们需要将这个问题进行拆分为下面四个问题

2.1 什么时候该用输入流,什么时候用输出流?

从流中读取信息使用输入流(xxxInputStream/xxxReader),写入信息使用输出流(xxxOutputStream/xxxWriter)

2.2 什么时候该用字节流,什么时候用字符流?

处理纯文本数据时使用字符流(xxxReader/xxxWriter),处理非纯文本时使用字节流(xxxStream)。最后其实不管什么类型文件都可以用字节流处理,包括纯文本,但会增加一些额外的工作量。所以还是按原则选择最合适的流来处理

2.3 什么时候该用节点流,什么时候用包装(处理)流?

不管你用什么包装(处理)流,都需要先使用节点流获取对应节点的数据流,然后根据具体需求来选择相应的包装(处理)流来对节点流进行包装修饰,从而获取相应的功能

2.4 什么时候该用普通流,什么时候用缓冲流?

一般如果对数据流不做加工处理,而是单纯的读写,如数据转移(拷贝,上传,下载),则需要使用缓冲流来提高性能,当然你也可以自己使用buff数组来提高读写效率。

2.5 使用小结

1)判断操作的数据类型

纯文本数据:读用Reader系,写用Writer系

非纯文本数据:读用InputStream系,写用OutputStream系

如果纯文本数据只是简单的复制,下载,上传,不对数据内容本身做处理,那么使用Stream系

2)判断操作的物理节点

内存:ByteArrayXXX

硬盘:FileXXX

网络:http中的request和response均可获取流对象,tcp中socket对象可获取流对象

键盘(输入设备):System.in

显示器(输出设备):System.out

3)搞清读写顺序,一般是先获取输入流,从输入流中读取数据,然后再写到输出流中。

4)是否需增加特殊功能,如需要用缓冲提高读写效率则使用BufferedXXX,如果需要获取文本行号,则使用LineNumberXXX,如果需要转换流则使用InputStreamReader和OutputStreamWriter,如果需要写入和读取对象则使用ObjectOutputStream和ObjectInputStream

3.使用IO流一些注意点

3.1 关于流读写性能问题

流的读写是比较耗时的操作,因此为了提高性能,便有缓冲的这个概念(,在java bio中使用缓冲一般有两种方式。一种是自己申明一个缓冲数组,利用这个数组来提高读写效率;另一种方式是使用jdk提供的处理流BufferedXXX类。下面我们分别演示不使用缓冲读写,使用自定义的缓冲读写,使用BufferedXXX缓冲读写一个文件。

下面写一个使用BufferedXXX类自定义大小缓冲来读写文件的例子

    /**
     * @param src 被拷贝的文件对象
     * @param dest 拷贝目的地文件对象
     * @param size 自定义缓冲区大小
     */
    public static void copyByBufferedStream(File src,File dest,int size) {
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        long start = System.currentTimeMillis();
        try{
            bis = new BufferedInputStream(new FileInputStream(src));
            bos = new BufferedOutputStream(new FileOutputStream(dest));
            int b = 0;
            byte[] buff = new byte[size];
            while( (b = bis.read(buff))!=-1){//数据读入缓冲区
                bos.write(buff,0,b);//将缓存区数据写入输出流中
            }
            bos.flush();
        }catch(IOException e){
            e.printStackTrace();
        }finally{
            close(bos,bis);
        }
        System.out.println("使用BufferedXXXStream拷贝大小"+getSize(src)+"的文件使用了缓冲数组耗时:"+(System.currentTimeMillis()-start)+"毫秒");
    }

3.2 如何判断文件是否读写完毕?
我们一般在处理文件,一般是一边从输出流中读数据,然后将读出的部分进行处理,最后将处理好的数据写入到输出流中。那么要将一个文件完整的处理完,我们必须知道什么时候已经读到文件的末尾了。
一般来说可以根据read方法返回的值,如果返回了-1表示没有可读取的字节了。
另一种是使用available()方法查看还有多少可供读取的,当输入流每读一个字节,available()返回的值便减小1,这种模式很像游标的模式,但要注意的是available的适用场景是非阻塞读取,如本地文件读取,如果是网络io使用该方法,可能你拿到的值就不对了。

3.3 关于网络流中使用available()方法的问题
当你在网络io中,比如你用socket编程时获取到的流进行读写时,会发现使用available方法有问题,原因是网络io的特点是:
1.非实时性。你调用available()方法判断剩余流的大小时,远端数据可能还未发送,或者要发送的数据处于队列中,因此通过available()拿到的可用长度可能是0
2.非连续性。由于网络数据传输中,一般会分段多次发送,available仅仅能返回本次的可用长度。
鉴于以上两个特点,使用available判断网络io还有多少数据可读是不合适的,因此解决该问题一般采用自定义协议,比如文件大小,文件名等信息放入流的头几个字节中,接收方根据收到的头信息来解析出对法传送的文件大小,根据大小来判断还剩多少字节需要读取,是否读取完毕。

3.4 关于flush()的问题

为什么缓冲输出流写数据结束需要调用flush方法?我们以BufferedOutputStream的write(int b)方法源码为例,源码如下:

    public synchronized void write(int b) throws IOException {
        if (count >= buf.length) {
            flushBuffer();
        }
        buf[count++] = (byte)b;
    }

BufferedOutputStream在write时候,只有count >= buf.length,即缓冲区数据填满的时候才会自动调用flushBuffer()将缓冲区数据进行写入,也就是说如果缓冲区数据未满则将不会写入,这时我们需人为的调用flush()方法将未满的缓冲区数据进行写入。

 

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