NIO基础API应用剖析

什么是NIO

  1. NIO是 New I/O的简称,与旧式的基于流的I/O方法相对,也被称为非阻塞IO,它是一套新的I/O标准,在jdk1.4中被发布

NIO的特性

  1. 原始的I/O是基于的流的方式,而NIO是基于块(Block)的,它以块为单位来对数据进行处理,因为本身硬盘上的文件就是使用块进行存储的,NIO对此进行了对应,所以NIO的性能上比原始I/O要好的多。
  2. 为所有的原始类型提供(buffer)缓存支持
  3. 增加通道(Channel)对象,作为新的原始I/O抽象(可以说是代替以往的流)
  4. 支持锁和内存映射文件的文件访问接口
  5. 提供了基于selector的异步网络I/O

NIO的核心组成部分

Buffer

  1. Buffer的本质实际是一块内存区域,通过native函数库分配堆外内存,然后通过存储在对堆中的DirectByteBuffer对象作为该内存的引用对象,这一实现将该内存区域与java运行时数据区进行分离,并且又通过堆内对象进行调用。
    • Buffer的子类实现:
      在这里插入图片描述
  2. Buffer中的3个重要的参数:
    • position(位置):
      • 写模式:当前缓冲区的位置,将从position的下一个位置写数据。
      • 读模式:当前缓冲区的读取的位置,将从此位置后读取数据
    • capactiy(容量)
      • 缓冲区的总容量上限
    • limit(上限)
      • 写模式:缓冲区的实际上限,它总是小于等于容量,通常情况下与容量是相等的
      • 读模式:代表可读取的容量,和上次写入的数据量相等。
  3. Buffer中的重要方法:
    • rewind():将position置为零,并清除标志位(mark),用于重新读取buffer中的数据。
    • clear():将position置零,同时将limit的值设置与容量capactiy相等,并清除标志mark。
    • flip():用于读写转化是使用,先将limit设置位position所在的位置,再将position置为零,并清除标记位mark
    • mark():可以标记Buffer中的一个特定position。
    • reset():跟mark()联合使用,调用该方法后可以恢复到mark标记的position位置上。

Channels

  1. Channels的主要实现:
    • FileChannel:从文件中读取数据
    • DatagramChannel:通过UDP协议读取网络中的数据
    • SocketChannel:通过TCP协议读取网络中的数据
    • ServerSocketChannel:可以监听新建来的TCP连接,像web服务器一样,对每一个新进来的连接创建一个SocketChannel
  2. Channels与流形式对比:
    • Channels跟流还是比较类似的,但是流的读写一般时单向的,而 Channels则可以读也可以写。
    • Channels支持异步的读取和写入,而流必须同步进行
    • Channels中的数据读取或写入都需要buffer来进行周转。
  3. API解析应用:
    • Channel的分散和聚集:
      • scatter(分散):Channels允许在读取通道数据的时,将数据读取到多个Buffer中。
        private static void scatterRead(File source)throws Exception{
        
            RandomAccessFile raf=null;
        
            try {
        
                raf = new RandomAccessFile(source, "rw");
        
                FileChannel channel = raf.getChannel();
        
                ByteBuffer buffer1 = ByteBuffer.allocate(1024);
        
                ByteBuffer buffer2 = ByteBuffer.allocate(8192);
        
                ByteBuffer[] array = new ByteBuffer[]{buffer1, buffer2};
        
                channel.read(array);
        
            }finally {  raf.close(); }
        
        }
        
      • Gather(聚集):Channels允许在往通道中写入数据时,写入多个Buffer中的数据。
        private static void aggregateWrite(File source)throws Exception{
        
            RandomAccessFile raf=null;
        
            try {
        
                raf = new RandomAccessFile(source, "rw");
        
                FileChannel channel = raf.getChannel();
        
                ByteBuffer buffer1 = ByteBuffer.allocate(1024);
                
                ByteBuffer buffer2 = ByteBuffer.allocate(2048);
        
                ByteBuffer[] array = new ByteBuffer[]{buffer1, buffer2};
        
                channel.write(array);
        
            }finally {  raf.close(); }
        
        }
        
    • FileChannel之间的数据传输
      • transferForm():
        private static void dataConversion1(File source,File target)throws Exception{
        
            RandomAccessFile from=null;
        
            RandomAccessFile to=null;
        
            try {
        
                from = new RandomAccessFile(source, "rw");
        
                to = new RandomAccessFile(target, "rw");
        
                FileChannel fromChannel=from.getChannel();
        
                FileChannel toChannel=to.getChannel();
        
                long count=fromChannel.size();
        
                toChannel.transferFrom(fromChannel,0,count);
        
            }finally {  from.close(); to.close(); }
        
        }
        
      • transferTo():
        private static void dataConversion2(File source,File target)throws Exception{
        
        	RandomAccessFile from=null;
        
            RandomAccessFile to=null;
        
            try {
        
                to = new RandomAccessFile(target, "rw");
        
                from = new RandomAccessFile(source, "rw");
        
                FileChannel toChannel=from.getChannel();
        
                FileChannel fromChannel=from.getChannel();
        
                long count=fromChannel.size();
        
                fromChannel.transferTo(0,count,toChannel);
        
            }finally {  from.close(); to.close(); }
        
        }
        
      • Buffer+FileChannel 的简单应用
           /**
             * 文件复制应用
             * */
            private static void copyFile(File source, File target){
        
                FileInputStream in=null;
        
                FileOutputStream out=null;
        
                try {
        
                    in=new FileInputStream(source);
        
                    out=new FileOutputStream(target);
        
                    FileChannel inChannel = in.getChannel();
        
                    FileChannel outChannel = out.getChannel();
        			//设置buffer的容量
                    ByteBuffer buffer=ByteBuffer.allocate(1024);
        
                    while (true){
        
                        try {
                            //在每次读取之间将缓冲区清空
                            buffer.clear();
        					//将输入通道中的数据读取到缓冲区中
                            int read = inChannel.read(buffer);
                            //当没有数据可获取时
                            if(read==-1) break;
                            //读写转换
                            buffer.flip();
        					//将缓冲区的数据写入到输出通道中
                            outChannel.write(buffer);
        
                        } catch (Exception e) {  e.printStackTrace();  }
        
                    }
        
                } catch (FileNotFoundException e) { e.printStackTrace();
        
                }finally {
        
                    try {
        
                        in.close();
        
                        out.close();
        
                    } catch (IOException e) {  e.printStackTrace(); }
                }
            }
        
    1. DatagramChannel

Selectors(选择器)

  1. 选择器允许单线程对多个通道进行管理,对于传统的IO模式而言,每一个IO都需要一个线程来进行处理,增大了服务器的负载,随着线程的增多线程之间的上下文切换开销也会变大,并且如果数据没有准备好线程还会进入阻塞状态,要等待数据准备好之后再对数据进行处理。而选择器的出现则解决了该问题,将通道注册到选择器中,一旦数据到达准备好之后再通知选择器对该数据进行处理,而当数据没有到达的时候线程不必等待可以去完成其他的任务。
    • 传统IO处理方式
      在这里插入图片描述
    • NIO处理方式
      在这里插入图片描述
  2. 运行原理:服务端将所有的Channel注册到Selector,Selector则负责监视这些channel的IO状态,任何一个或多个Channel准备好可用的IO操作后,Selector调用select()将返回有多少个Channel处于就绪状态,并通过selectedKeys()方法获取到这些Channel对应的SelectionKey集合,每一个SelectionKey都代表了一个Channel,再通过SelectionKey对某个channel进行IO操作。
  3. API解析
    • 方法调用:
      • select():阻塞到至少有一个Channel就绪后,返回就绪的Channel数量
      • select(long timeout):阻塞立即返回,没有就绪Channel则返回0,每隔timeout毫秒后再次阻塞获取。
      • selectNow():不会进行阻塞,不管是否有就绪的Channel;
      • selectorKeys():返回就绪Channel的SelectionKey集合
      • wakeUp():让Selector在调用select()后处于阻塞状态的线程立即返回不用等待Channel就绪后返回。
    • 状态说明:上面提到Selector会判断channel处于就绪状态才会进行处理,在面介绍下Channel的状态。
      • SelectionKey.OP_CONNECT:Channel成功连接服务器后进入连接就绪状态
      • SelectionKey.OP_ACCEPT:服务器监听到了客户端的channel,服务器可以接收该连接
      • SelectionKey.OP_READ:Channel中具有可读的数据后进入读就绪状态
      • SelectionKey.OP_WRITE:Channel等待数据写入进入写就绪状态
  4. Selector+ServerSocketChannel+Buffer的简单应用:

拓展

RandomAccessFile与内存映射文件

RandomAccessFile

  1. RandomAccessFile是用来对文件内容进行随机访问的类,你可以通过设置你需要访问的位置对文件内容定位,当你在对文件进行读写的时候就是从你设置的位置开始读写。
  2. RandomAccessFile设定的四种对文件的访问模式
    • r:以只读的方式打开文件,不可对文件进行写的操作
    • rw:可以对文件进行读写操作
    • rws:进行写操作时,同步刷新到磁盘,刷新内容和元数据(也就是该文件的注册信息之类)
    • rwd:进行写操作时,同步刷新到磁盘,刷新内容
  3. RandomAccessFile运用了很多关于输入流输出流的方法,但是并非是FileInputStream和FileOutputStream,而是自己从零开始,不与输入输出流之间产生联系,因为其内部需要实现在文件内进行随机读写,在行为上与输入输出流有底层有所不同,所以RandomAccessFile是一个独立的类。

内存映射文件(memory-mapped files)

  1. 上面提到的RandomAccessFile可以对文件进行随机访问,这样就可以对文件进行修改增加操作;而在jdk1.4后javaNIO通过内存映射文件取带了RandomAccessFile的绝大部分功能
  2. 内存映射文件通过将文件映射到内存中,通过修改内存数据映射到文件的修改;内存文件映射要求必须指明文件映射开始位置,和映射数据范围,所以你映射的可以是大文件中的一个小片段。但是文件最大不得超过2GB。
  3. 在内存映射之前,都必须通过RandomAccessFile来对文件进行输出,可能是在内存映射文件时因为指定位置的关系,所以通过RandomAccessFile对随机访问提供支持吧。
  4. 内存映射文件应用:
    private static void fileMappedMemory(File source){

        RandomAccessFile raf=null;

        try {

           raf=new RandomAccessFile(source,"rw");
			//获取通道对象
           FileChannel fc=raf.getChannel();
           //将文件映射到内存中
           MappedByteBuffer mbb=fc.map(FileChannel.MapMode.READ_WRITE,0,raf.length());

           while (mbb.hasRemaining()){ System.out.println(mbb.get()); }
           //往文件中写入数据
           mbb.put(0,(byte)98);

       } catch (FileNotFoundException e) {
           e.printStackTrace();
       } catch (IOException e) {
           e.printStackTrace();
       }finally {
           try {
               raf.close();
           } catch (IOException e) {
               e.printStackTrace();
           }
       }

   }

Pipe(管道)

  1. 介绍:NIO的管道,用于两个线程之间的单向数据连接。pipe具有一个source通道和一个sink通道,数据会先写入到sink通道中,从source通道中进行读取。
  2. pipe原理图:
    在这里插入图片描述
    3.实例应用:
    public class PipeTest {
    
        public static void main(String[] args) {
            try {
                Pipe pipe= Pipe.open();
    
                Thread td1=new Thread(new WriteThread(pipe));
    
                Thread td2=new Thread(new ReadThread(pipe));
    
                td2.start();
    
                td1.start();
    
            } catch (IOException e) {
                e.printStackTrace();
            }
    
        }
    
    }
    
    class WriteThread implements Runnable{
    
        private Pipe pipe;
    
        public WriteThread(Pipe pipe) {
            this.pipe = pipe;
        }
    
        @Override
        public void run() {
    
            Pipe.SinkChannel sinkChannel=null;
    
            try {
    
    	        sinkChannel=pipe.sink();
    	
    	        ByteBuffer buffer=ByteBuffer.allocate(1024);
    	              
    	        Thread.sleep(10000);
    	
    	        buffer.put(Thread.currentThread().getName().concat("写入数据").getBytes());
    	
    	        buffer.flip();
    	
    	        sinkChannel.write(buffer);
    
            } catch (IOException e) {
                e.printStackTrace();
            }catch (InterruptedException e) {
                   e.printStackTrace();
            }finally {
                try {
                    sinkChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
    
        }
    }
    class ReadThread implements Runnable{
    
        private Pipe pipe;
    
        public ReadThread(Pipe pipe) {
            this.pipe = pipe;
        }
    
        @Override
        public void run() {
    
            Pipe.SourceChannel sourceChannel=null;
    
            try {
    
            sourceChannel=pipe.source();
    
            ByteBuffer buffer=ByteBuffer.allocate(1024);
            //没有数据时会阻塞到有数据才进行执行
            sourceChannel.read(buffer);
    
            System.out.println(new String(buffer.array()));
    
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                try {
                    sourceChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
            }
    
        }
    }
    
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值