java nio

java nio主要由Buffer缓冲区 、Channel通道和Selector选择器三大部分组成。

Buffer
Buffer为一个抽象类,其主要子类有ByteBuffer IntBuffer LongBuffer CharBuffer.Buffer有4个重要属性 position capacity limit mark
和clear() flip() rewind() reset() mark() 几个重要方法。
以Buffer的一个子类ByteBuffer为例说明这几个属性和方法,ByteBuffer对象有一个byte[] hb的数组,是真正存储Buffer数据的变量,其它成员变量只是起辅助作用。
为了更好地说明各成员变量的含义,需要从Buffer的两个模式,读取模式和写入模式来介绍,因为不同模式下这些变量的含义有所不同。

Buffer写入模式
刚创建一个ByteBuffer对象后,position=0, capacity 为byte[] hb数据的长度,创建之后不变,limit为还有多少空间剩余(即还有多少空间可以写入)。
Buffer数组是从 position位置开始写入元素,每写一个position值加1。limit= capacity,mark 存储Buffer的下标,并总是小于等于position的,当用mark(i)设置时,再用reset(),position变设为了i.
所以这些变量有如下大小关系
mark<=position<=limit<=capacity

                            

读模式中
position代表开始读的位置,limit为读数据的终止位置,也就是说,在读模式中position~limit是已经写入了数据,limit~capacity 还没有写入数据。

clear()方法:Buffer开始写数据时,先要把Buffer设置成写模式,也就是把 position置为0 limitl置为capacity,这个通过clear()方法来完成。
flip()方法:同样Buffer开始读数据时,先要把Buffer设置成读模式,即把position置为0 limitl置为position,,这个通过flip()方法来完成。
rewind()方法:Buffer.rewind()将position设回0,所以你可以重读Buffer中的所有数据。limit保持不变,仍然表示能从Buffer中读取多少个元素(byte、char等)。

clear()与compact()方法

一旦读完Buffer中的数据,需要让Buffer准备好再次被写入。可以通过clear()或compact()方法来完成。

如果调用的是clear()方法,position将被设回0,limit被设置成 capacity的值。换句话说,Buffer 被清空了。Buffer中的数据并未清除,只是这些标记告诉我们可以从哪里开始往Buffer里写数据。

如果Buffer中有一些未读的数据,调用clear()方法,数据将“被遗忘”,意味着不再有任何标记会告诉你哪些数据被读过,哪些还没有。

如果Buffer中仍有未读的数据,且后续还需要这些数据,但是此时想要先先写些数据,那么使用compact()方法。

compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。limit属性依然像clear()方法一样,设置成capacity。现在Buffer准备好写数据了,但是不会覆盖未读的数据。


mark()与reset()方法

通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position。


Channel

Channel为一个通道接口,其常用的子类有FileChannel DatagramChannel SocketChannel和ServerSocketChannel,通道具有传统IO流所没有的同读和写的功能

FileChannel是一个连接到文件的通道,可以通过文件通道读写文件。

具体用法参考如下代码:

public static void writeAndRead()throws IOException

    {

        RandomAccessFile aFile = new RandomAccessFile("/root/ss.txt", "rw");

        FileChannel inChannel = aFile.getChannel();

        ByteBuffer buf = ByteBuffer.allocate(10);

        buf.clear();

        String data="abcd5";

        buf.put(data.getBytes());

        //write data to channel

        buf.flip();

        while(buf.hasRemaining()){

            inChannel.write(buf);

        }

        //read data from channel

        buf.clear();

        int bytesRead=-1;

        long i=inChannel.position();

        inChannel.position(0);

        while((bytesRead=inChannel.read(buf))!=-1){

            byte[] newData=buf.array();

            String str=new String(newData,0,bytesRead);

            System.out.print(str);

            buf.clear();

        }

        inChannel.close();

        aFile.close();

    }



DatagramChannel 它是一个能收发UDP包的通道。因为UDP是无连接的网络协议,所以不能像其它通道那样读取和写入。它发送和接收的是数据包。


SocketChannel和ServerSocketChannel

SocketChannel和ServerSocketChannel类似于java.net的Socket和ServerSocket

其具体用法如下:

//服务器端代码

public class NIOServer {

    private Selector selector;

    public static void main(String[] args) throws IOException {

        NIOServer server = new NIOServer();

        server.initServer(8199);

        server.listen();

    }

    /**

     * 获得一个ServerSocket通道,并对该通道做一些初始化的工作

     * @param port  绑定的端口号

     * @throws IOException

     */

    public void initServer(int port) throws IOException {

        // 获得一个ServerSocket通道

        ServerSocketChannel serverChannel = ServerSocketChannel.open();

        // 设置通道为非阻塞

        serverChannel.configureBlocking(false);

        // 将该通道对应的ServerSocket绑定到port端口

        serverChannel.socket().bind(new InetSocketAddress(port));

        // 获得一个通道管理器

        this.selector = Selector.open();

        //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后,

        //当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。

        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

    }

    public void listen() throws IOException {

        System.out.println("服务端启动成功!");

        // 轮询访问selector

        while (true) {

            //当注册的事件到达时,方法返回;否则,该方法会一直阻塞

            selector.select();

            // 获得selector中选中的项的迭代器,选中的项为注册的事件

            Iterator ite = this.selector.selectedKeys().iterator();

            while (ite.hasNext()) {

                SelectionKey key = (SelectionKey) ite.next();

                //key.interestOps()

                // 删除已选的key,以防重复处理

                ite.remove();

                // 客户端请求连接事件

                if (key.isAcceptable()) {

                    ServerSocketChannel server = (ServerSocketChannel) key

                            .channel();

                    // 获得和客户端连接的通道

                    SocketChannel channel = server.accept();

                    // 设置成非阻塞

                    channel.configureBlocking(false);

                    //在这里可以给客户端发送信息哦

                    channel.write(ByteBuffer.wrap(new String("向客户端发送了一条信息").getBytes()));

                    //在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。

                    channel.register(this.selector, SelectionKey.OP_READ);

                    // 获得了可读的事件

                } else if (key.isReadable()) {

                    read(key);

                }

            }

        }

    }

public void read(SelectionKey key) throws IOException{

        // 服务器可读取消息:得到事件发生的Socket通道

        SocketChannel channel = (SocketChannel) key.channel();

        // 创建读取的缓冲区

        ByteBuffer buffer = ByteBuffer.allocate(100);

        int i=channel.read(buffer);

        byte[] data = buffer.array();

        String msg = new String(data).trim();

        System.out.println("服务端收到信息:"+msg);

        ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());

        channel.write(outBuffer);// 将消息回送给客户端

    }

}


//客户端代码

public class NIOClient {

    //通道管理器

    private Selector selector;

    public static void main(String[] args) throws IOException {

        NIOClient client = new NIOClient();

        client.initClient("localhost",8199);

        client.listen();

    }

    /**

     * 获得一个Socket通道,并对该通道做一些初始化的工作

     * @param ip 连接的服务器的ip

     * @param port  连接的服务器的端口号

     * @throws IOException

     */

    public void initClient(String ip,int port) throws IOException {

        // 获得一个Socket通道

        SocketChannel channel = SocketChannel.open();

        // 设置通道为非阻塞

        channel.configureBlocking(false);

        // 获得一个通道管理器

        this.selector = Selector.open();

        // 客户端连接服务器,其实方法执行并没有实现连接,需要在listen()方法中调

        //用channel.finishConnect();才能完成连接

        channel.connect(new InetSocketAddress(ip,port));

        //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。

        channel.register(selector, SelectionKey.OP_CONNECT);

    }

    /**

     * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理

     * @throws IOException

     */

    @SuppressWarnings("unchecked")

    public void listen() throws IOException {

        // 轮询访问selector

        while (true) {

            selector.select();

            // 获得selector中选中的项的迭代器

            Iterator ite = this.selector.selectedKeys().iterator();

            while (ite.hasNext()) {

                SelectionKey key = (SelectionKey) ite.next();

                // 删除已选的key,以防重复处理

                ite.remove();

                // 连接事件发生

                if (key.isConnectable()) {

                    SocketChannel channel = (SocketChannel) key

                            .channel();

                    // 如果正在连接,则完成连接

                    if(channel.isConnectionPending()){

                        channel.finishConnect();

                    }

                    // 设置成非阻塞

                    channel.configureBlocking(false);

                    //在这里可以给服务端发送信息哦

                    channel.write(ByteBuffer.wrap(new String("向服务端发送了一条信息").getBytes()));

                    //在和服务端连接成功之后,为了可以接收到服务端的信息,需要给通道设置读的权限。

                    channel.register(this.selector, SelectionKey.OP_READ);

                    // 获得了可读的事件

                } else if (key.isReadable()) {

                   // read(key);

                }

            }

        }

    }

    public void read(SelectionKey key) throws IOException{

        // 服务器可读取消息:得到事件发生的Socket通道

        SocketChannel channel = (SocketChannel) key.channel();

        // 创建读取的缓冲区

        ByteBuffer buffer = ByteBuffer.allocate(100);

        channel.read(buffer);

        byte[] data = buffer.array();

        String msg = new String(data).trim();

        System.out.println("client收到信息:"+msg);

        ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());

        channel.write(outBuffer);// 将消息回送给客户端

    }

}


如果  channel.read(buffer); 没有读取完channel中的数据,再下次select()时,会接着处理这个操作

java nio具体讲解可参考如下文章:http://ifeve.com/buffers/

java channel可参考这个:http://bbs.csdn.net/topics/330121065







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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值