Java IO分析(包括旧IO和NIO)


IO

1.最后操作都会交到Io(Posix)然后通过本地方法来操作读写,会根据FileDescriptor。写2.的基类为OutputStream,读的基类为InputStream

3.以Stream结尾的类主要操作byte。以Writer结尾的类主要操作char,char要经过CharsetEncoder编码,然后再转为byte,再交由Stream来传输

一.OutputStream:

1.FileOutputStream:首先,通过Posix.open()传过去文件路径和打开模式来获得一个FileDescriptor(也可直接传给FileOutputStream一个FIleDescriptor),当调用write()来写数据时,内部会把之前的FileDescriptor作为参数交给Posix来写。当需要关闭即close()时也要传该FileDescriptor

         2.BufferedOutputStream:内部持有一个OutputStream对象(构造函数传进来),并且有一个固定大小的缓冲byte数组,用来储存临时数据,当需要输出的数据大于缓冲数组的大小时才一次性把缓冲数组里的数据写出去。

         3.ByteArrayOutputStream:内部保持一个可变数组,当每次调用其write时,把数据保存在数组里,当调用其toArray时会返回之前储存的数据,当调用writeTo并传入一个OutputStream对象时,会把之前的数据数组通过这个OutputStream写出去。

         4.DataOutputStream:用于实现对所有数据类型的写出,注意到OutputStream每次只能发一个字节的数据,或以1字节为单位的数组,所以对于大于一个字节的数据类型得先转为以1字节为单位的数组再发送,数组固定长度为8个字节

byte与byte数组直接写出

boolean则转为1,或0写出

String转为byte数组再写出

char和short都是2个字节所以把其高位和低位分别放在数组的两个槽里再把数组发送出去

int和float使用数组的四个槽

long和double所以数组的8个槽

         5.ObjectOutputStream:所要写的对象必需实现Serializable接口,注意对象中的transient和static类型成员变量不会被读写

         6.PipedOutputStream与PipedInputStream用于线程间的通信,在通信前,它们必须先建立连接,在PipedIntputStream内部保持一个固定长度的buffer(环形),用于PipedOutputStream的写和PipedINputStream的读,当写的时候,如果buffer满了,则该线程要wait等待。当读的时候,如果buffer为空,则线程要wait等待

二.Writer:

         1.OutputStreamWriter:用于把char根据指定的CharsetEncoder译码器转为byte,再使用内部的OutputStream发送出去。其内部持有一个byte的缓冲区。

         2.FileWriter:继承OutputStreamWriter,只是把里面的OutputStream指定为FileOutputStream而已。

         3.BufferedWriter:内部有一个char数组缓存已经持有一个Writer,数据写进来时先放在数组里,当缓存满时或调用flush()时,调用Writer把缓冲写出去。(然而我个人觉得没有这个类的必要,因为OutputStreamWriter内部已经使用了缓存机制了)

         4.CharArrayWriter:参考ByteArrayOutputStream,不同之处在于把OutputStream替换为Writer,byte替换为char。

三.InputStream

         1.FileInputStream:首先,通过Posix.open()传过去文件路劲和打开模式来获得对应的FileDescriptor,当调用write(),close()等都需要把该FIleDescriptor作为参数

         2.BufferedInputStream:内部封装了一个InputStream,采用可变数组来储存io流里读取到的数据,而每次从该InputStream读取数据的时候都是先从数组缓冲区里读取,从而避免了频繁地操作io口

         3.ByteArrayInputStream:在初始化该类的对象的时候把缓冲交给该对象,然后从这个对象里面读取。

         4.DataInputStream:把输入流中的符号变成对应的基本数据类型。

NIO

*使用NIO的步骤:

         1.创建buffer。

         2.创建selector。

         3.创建channel(可多个)。把channel设置为非阻塞,并把channel注册到selector,同时传递给selector自己感兴趣的方面。

         4.调用selector的select()等待返回。

         5.当selector的select()返回时,调用selector的selectedKeys()可获得得到响应的channel的SelectionKey。然后就可以对该channel执    行相应的操作了,如读buffer,写buffer到channel。注意,处理后的SelectionKey要remove掉,避免下次再误会再使用。

1.通道channel是 I/O 传输发生时通过的入口,而缓冲区buffer是这些数据传输的来源或目标

一.Buffer:

                     API:

1.capacity表示缓存数组的大小;

         2.limit表示缓存数组中可用数组长度的大小,往往等于capacity;

         3.position表示当前指针位置跟put(),get()有关;

         4.mark表示可用恢复的位置

5.put()把数据放进当前position所在的位置,然后把position向后移动一位,即position总是指向即将要put()进数据的位置。

6.get()把position当前位置的数据返回,并使position往后移动一位,注意使用get()之前要调用Buffer的flip()

7.flip():limit = position;position =0;mark = UNSET_MARK;flip后position位置在数组的最前面,这时候可以来put进来的顺序来读数据。*注意,在对Buffer读写之前要调用其flip()

                     8.BIG_ENDIAN:Android采用的是大端字节顺序。

                     9.remaining()=length()=limit-position

                     类:

1.CharBuffer:使用allocate()返回的是它的子类CharArrayBuffer,内部以一个char数组做缓冲。

2.ByteBuffer:最小单元的数据,使用allocate()返回的是它的子类ByteArrayBuffer,内部以一个byte数组做缓冲,提供了把ByteBuffer变成其他基本数据类型的Buffer,如CharBuffer。

二.Channel:道只接收 ByteBuffer 作为参数

 

 

API:

1.write(ByteBuffer):传进去Buffer时,会交由系统Posix来写,这时候是从buffer

的position开始写,写buffer的remaining个数据。所以,在把buffer传给channel时,一般需要调用buffer的flip()来让position移到缓存区开始处

2 .write(ByteBuffer,int):从文件指定位置开始写

3.position(long):把文件光标移动到指定位置

4.position():返回文件当前光标位置

类:

一.SelectableChannel:只有继承该类才的channel才能调用自身的register方法注册到selector上

1.configureBlocking(boolean):设置当前通道是否是阻塞通道,如果要把通道注册到selector上则必须设置为false非阻塞。

2.SelectionKey register(Selector selector, int operations):把该channel注册到指定的selector上,并指定该通感兴趣的操作operations

         二.FileLock:代表文件上一片被锁的区域,FileLock可以被独占或共享

三.FileChannel:不能直接new,FileChannel的创建必须与唯一的FileDFileDescriptor绑定,其读写模式与file被打开时指定的一样。FileChannel不继承自SelectableChannel,所以不能注册到selector上。

    四.SocketChannel:继承了SelectableChannel,所以可以注册到selector上。

1. SocketChannel open(SocketAddress address):该方法会返回一个SocketChannelImpl(SocketChannel的真正实现类),并自动调用其connect()方法连接到目的地址

五.ServerSocketChannel:

         六.SocketChannelImpl:

                   1.SocketChannel的真正实现类。

                   2.内部持有一个FileDescriptor对象,然后与Posix的各种socket交换都需要通过FileDescriptor来进行身份确认。

                   3.内部持有Socket的实例。可以当做普通socket操作。

         七.ServerSocketChannelImpl:

                   1.ServerSocketChannel的真正实现类。

                   2.内部持有ServerSocket的实例。可以当做普通ServerSocket操作                 

         八.SelectableChannel:为抽象类,实现它的是抽象类AbstractSelectableChannel。

                   1.内部持有一个该Channel要注册到的SelectorProvider,

                   2.内部持有一个List<SelectionKey>keyList,保存着SelectionKey,

                   3.当把一个SelectableChannel注册到Selector上时,会调用Selector的register()方法把自己注册进去,得到对应的SelectionKey,并          把其保存到keyList里

                   4.当从selector中注销SelectionKey时会在改类中吧其从keyList中移除

 

三 .Selector:

API:

         1.

         类:

一. SelectorProvider:

                   1.不能直接new,可通过调用其provider()方法得到其实例(SelectorProviderImpl)

         二.SelectorProvider:

                   1.其内部只是简单实现SelectorProvider的方法。

         三.Selector:

                   1不能直接new,可.调用其open()可以得到一个Selector得到其实例(SelectorImpl)。其内部实现是先调用SelectorProvider的                      provider(),得到SelectorProviderImpl,再调用其openSelector()得到SelectorImpl。

         四.SelectorImpl:

                   1.内部有一个Set<SelectionKey>cancelledKeysSet 保存着cancel的SelectionKey。

                   2.内部有一个Set<SelectionKeyImpl>mutableKeys 保存着注册进来的Channel的SelectionKey

                   3.内部有一个Set<SelectionKey>unmodifiableKeys 对外公开的mutableKeys,不可修改的,否则会抛出异常

                   4.内部有一个Set<SelectionKey>selectedKeys 为每次系统通知Selector有响应的Channel,也是select()方法从阻塞回来后带来的通            知。对外界公开的对象,不可添加,但可以删除和查询。表示响应的SelectionKey。

                   5.内部有一个Set<SelectionKey>mutableSelectedKeys 其实这个才是真正selectedKeys的内容,不对外公开。

                   6.内部有一个UnsafeArrayList<StructPollfd>pollFds

 

                   6.当一个channel调用其register()时,会调用其内部的Selector的register()把其注册进来,而在Selector的内部则根据该Channel          生成对应的SelectorKey,并储存在mutableKeys里。

                   7.select():执行该方法会产生阻塞,直到至少有一个Channel准备好有响应。

                            会执行以下操作

                            a.doCannel:把cancelledKsysSet里面的SelectionKey从mutableKeys里移除;把cancelledKsysSet里面的SelectionKey从其                                     Channel的keyList里移除;把cancelledKsysSet里面的SelectionKey从mutableSelectedKeys里移除;

                            当select()从阻塞返回时,可以调用Selector的selectedKeys()返回有响应的Channel的selectionKey

         五.SelectionKey:

                   1.内部持有一个Selector,

                   2.内部持有一个对应的channel,调用其channel()可得到。

                   2.当调用其cancel()时,会把自身添加到Selector的cancelledKeySet里,

 

IO与NIO的区别之处

1.旧IO是阻塞的,NIO可以是非阻塞的。

2.旧IO是基于流Stream的,每次只能一个字节慢慢读写。NIO是基于缓冲Buffer和通道Channel的,可以一次性写入和读取一大块数据,操作系统对块操作做了许多优化,比如虚拟内存等。NIO可以拥有文件的视图,即可以在文件的任何位置读写。

3.旧IO在并发情况下需要开启对应的多线程,在线程上下文切换中需要消耗资源,而NIO只需一个线程,他根据每个IO对应的FileDescriptor成批地向系统发出请求和得到响应。

4.JVM在处理数据时,往往会采用DMA来进行大块数据的传送(微机原理,或操作系统知识),而在旧IO中,会把数据分成一小部分一小部分(byte)来运输,而在NIO中,则支持大块数据的运输,速度上肯定会提升。

5.IO缓冲区操作简图

6.JVM是常规进程,处于用户空间

关注微信公众号

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com