Java NIO

##初探Java NIO

以前只是听说过,没有进行深入学习,昨天趁着周末时间学习一下,今天来总结作为学习java NIO的备忘录吧。java NIO的全程是java New IO,这里涉及到IO,此时在我们脑海想回到IO,这个IO与之前的IO有什么不同才会体现New特性呢?我将从三个方面来探讨IO与NIO的区别。
第一、之前的IO是一种面向流式的数据操作,而NIO是面向数据缓冲的操作。
第二、IO是一种阻塞式的,而NIO是非阻塞式的。这里的阻塞与非阻塞就涉及到线程与锁了。当一个线程对一个流进行处理时会获取流所属的对象锁,这样其他线程想要获取流必须等待直到当前的线程释放掉锁。而NIO是面向缓冲的数据操作,所有的数据必须先存放到缓冲区,这样其他线程可以共享缓冲区数据,可以同时去处理缓冲区中的数据。所以NIO是非阻塞式。
第三、NIO有selector选择器,这个是NIO最核心的东西,而IO没有。对于selector下面将详细介绍。
以上是IO与NIO的区别。我想你已经从区别中了解到NIO的特点了吧。首先NIO是面向缓冲的数据操作,所以说NIO有Buffer数据缓存。另外NIO最核心的特点就是选择器Selector.最后一点就是所有的操作必须通过数据通道Channel。一般不能直接对数据缓存进行操作,必须通过Channel对象去写入或读取数据到buffer。

Buffer中的三剑客:capacity、limit、position

上面已经知道所有对缓冲区的直接操作只能通过Channel对象。所以说,buffer只能与Channel进行数据交换。另外最关键的一点就是Channel如何读取或写入数据到buffer中,这个就涉及到Buffer的运行机制。buffer的运行机制靠position、limit、Capacity这3个关键参数。我们还是从源码看起吧。

这里写图片描述
从源码中可以看出Buffer这个类是一个抽象类,另外定义了四个参数。既然是抽象类,那必定有其实现的子类,通过源码知道其子类主要是基本数据类型的buffer以及基本数据类型buffer的子类,其底层的数据存储是基于数组。下面说说三个参数的作用:

  • capacity
    用于初始化数组的大小,一旦初始化就保持不变。

  • limit
    指定还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)

  • position
    指定了下一个将要被写入或者读取的元素索引,它的值由get()/put()方法自动更新,在新创建一个Buffer对象时,position被初始化为0。
    我们还是举个简单的例子来说明三个参数的作用。

package com.frank.nio;

import java.nio.Buffer;
import java.nio.IntBuffer;

/**
 * Created by sunzhongwei on 2016/12/10.
 * @author sunzhongwei
 * QQ:843055018
 * 微信:F451209123
 */
public class TestIntBuffer {
    public static void main(String[] args) {
        // 分配新的int缓冲区,参数为缓冲区容量
        // 创建buffer对象,初始化容量capacity,其容量为10,此时position也初始化为0,limit为10
        IntBuffer buffer = IntBuffer.allocate(10);
        output("第一步:初始化",buffer);
        for (int i = 0; i < buffer.capacity()-2; ++i) {
            // 将给定整数写入此缓冲区的当前位置,当前位置position递增
            buffer.put(i);
        }
        output("第二步:写入数据之后",buffer);
        // 重设此缓冲区,将限制limit设置为当前位置,然后将当前位置position设置为0
        buffer.flip();
        output("第三步:flip方法调用之后",buffer);
        // 查看在当前位置position和限制位置limit之间是否有元素
        while (buffer.hasRemaining()) {
            // 读取此缓冲区当前位置position的整数,然后当前位置position递增
            int j = buffer.get();
            System.out.print(j + "  ");
        }
        System.out.println();
        output("第四步:读完数据之后",buffer);
    }

    public static void output(String step, Buffer buffer) {
        System.out.println(step + " : ");
        System.out.print("capacity: " + buffer.capacity() + ", ");
        System.out.print("position: " + buffer.position() + ", ");
        System.out.println("limit: " + buffer.limit());
        System.out.println();
    }
}

输出结果为:

这里写图片描述
通过测试代码一目了然,不用过多解释。 注意点:当数据存储之后,如果要读取buffer中的数据,则必须要执行flip()方法。当然自己也可以写代码实现参数状态的变动。

Buffer其他特性

上面对Buffer中的关键参数做了分析,下面再了解一下Buffer的其他特性。

  • Wrap
    除了通过allocate()来指定缓冲区的容量,也可以直接将一个现有的数组,包装为缓冲区对象,只需要调用wrap方法即可。

  • 缓冲区分片
    缓冲区分片的概念是指通过现有的buffer对象创建其与关联的子缓冲区。子缓冲区与父缓冲区数据共享。创建方法是调用slice()方法可以创建一个子缓冲区。实例如下:

 ByteBuffer buffer = ByteBuffer.allocate( 10 );
        //向缓冲区中put数据
        for (int i=0; i<buffer.capacity()-2; ++i) {
            buffer.put( (byte)i );
        }
        // 创建子缓冲区,通过设置position与limit来确定子缓冲区中的内容
        buffer.position( 3 );
        buffer.limit( 7 );
        ByteBuffer slice = buffer.slice();
        // 改变子缓冲区的内容
        for (int i=0; i<slice.capacity(); ++i) {
            byte b = slice.get( i );
            b *= 100;
            slice.put( i, b );
        }
        buffer.position( 0 );
        buffer.limit( buffer.capacity() );

        while (buffer.remaining()>0) {
            System.out.println( buffer.get() );
        }
  • 只读缓冲区
    通过调用缓冲区的asReadOnlyBuffer()方法,将任何常规缓冲区转 换为只读缓冲区,这个方法返回一个与原缓冲区完全相同的缓冲区,并与原缓冲区共享数据,只不过它是只读的。如果原缓冲区的内容发生了变化,只读缓冲区的内容也随之发生变化。

  • 直接缓冲区
    直接缓冲区是为加快I/O速度,使用一种特殊方式为其分配内存的缓冲区。要分配直接缓冲区,需要调用allocateDirect()方法,而不是allocate()方法,使用方式与普通缓冲区并无区别。

  • 内存映射文件I/O
    内存映射文件I/O是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的I/O快的多。内存映射文件I/O是通过使文件中的数据出现为 内存数组的内容来完成的,这其初听起来似乎不过就是将整个文件读到内存中,但是事实上并不是这样。一般来说,只有文件中实际读取或者写入的部分才会映射到内存中。

##Selector选择器

在谈selector之前,我先得了解Channel通道,Channel通道也是NIO的组成部分。那么问题来了,什么是Channel通道?或者Channel通道具体是什么概念?在回答这个问题之前,我们先来了解一下进程、线程、协程。进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度(标准线程是的)。协程和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。协程和线程的区别是:协程避免了无意义的调度,由此可以提高性能,但也因此,程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力。三者直接的关系是一个进程可以都有多个线程,而一个线程可以有多个协程。而Channel属于协程范围。Channel是线程中数据传输的通道。既然有了通道,那么Selector选择器就是来协调Channel通道的(前提是:Channel必须设置成非阻塞)。
###Selector创建

// 创建Selector对象
Selector selector= Selector.open();
 // 创建可选择通道,并配置为非阻塞模式
ServerSocketChannel server = ServerSocketChannel.open();
server.configureBlocking(false);
// 绑定通道到指定端口
ServerSocket socket = server.socket();
InetSocketAddress address = new InetSocketAddress(port);
socket.bind(address);
// 向Selector中注册感兴趣的事件
server.register(selector, SelectionKey.OP_ACCEPT);

源码中:SelectionKey.OP_ACCEPT表示事件类型,注册的事件类型共有4种:Connect(OP_CONNECT)、Accept(OP_ACCEPT)、Read(OP_READ)、Write(OP_WRITE)。
关于Selector更多信息可以点击:http://ifeve.com/selectors/ 我看完这篇博文,感觉作者写得很好!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值