java NIO-Channel

2022/2/13 17:17:58

本文主要是介绍java NIO-Channel,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

目录

1 概述

2 阻塞IO/NIO

2.1 阻塞IO

2.2 NIO

 2.3 NIO核心组件

2.3.1 Channel

2.3.2 Buffer

2.3.3 Selector

3 Channel

3.1 FileChannel

3.1.1 将数据读取到buffer中 

3.1.2 向fileChannel中写数据

 3.1.3 FileChannel的其他方法

3.2 Socket通道

3.2.1 ServerSocketChannel

 3.2.2 SocketChannel

3.2.3 DatagramChannel

4 Scatter/Gather


1 概述

        之前听朋友说,他们公司有一个业务场景对于IO的操作要求较高,项目组长让他用NIO来完成这个需求,他一听一脸茫然的问组长:啥是NIO啊?项目组长听后对他挥挥手说:“起开起开,我来”。朋友后来和我分享这个事情,对于都是菜鸡的我们来说,我也不知道啥叫NIO。于是虎年伊始,我决定来学学这个NIO。以免有一天我的项目组长对我说,你起开起开,我来。

        Java NIO是Java1.4之后引入的一个全新的API,它可以替代标准的IO操作,NIO既支持面向缓冲区的操作,同时也是基于通道的操作 ,它可以用更高效的方式进行文件的读写操作。鉴于NIO的内容较多,我决定写几篇博客分别来记录它。

2 阻塞IO/NIO

2.1 阻塞IO

        在进行同步I/O操作时,如果读取数据,代码会阻塞直至有可供读取的数据,同样写入数据将会阻塞直至数据能够写入。传统的Server/Client模式会基于TPR(Thread per Request),服务器会为每一个客户端建立一个线程,由该线程单独负责处理一个客户请求,这种模式带来的问题就是线程数量的增多会增大服务器的开销。大多数时候都会采用线程池模型,并且设置线程池的最大线程数量,但这同样不能够完全解决问题,如果最大线程数是100,而有100个用户在进行大文件下载,那么101个用户的请求就会被线程池拒绝处理(线程池会执行拒绝策略)

2.2 NIO

        NIO中的非阻塞I/O采用了基于Reactor模式的工作方式,I/O调用不会被阻塞,相反是注册感兴趣的特定I/O事件,如可读数据到达,新的套接字连接等等,在发生特定的事件时,系统在通知我们。NIO中实现非阻塞I/O的核心对象就是Selector。Selector就是注册各种I/O事件地方,而且当我们感兴趣的事件发生时,就是这个对象发生我们的事件

         如图:如果通道1,通道2,通道3等任何注册事件发生的时候,可以从Selector中获得相应的SelectorKey,同时从SelectorKey中可以找到发生的事件和发生具体的SelectableChannel,以此来获得客户端发送来的数据。

        注意:非阻塞指的是IO事件本身不阻塞,但是获取IO事件的select()方法是需要阻塞等待的。区别在于阻塞的IO会阻塞在IO操作上,NIO的阻塞是发生在事件获取上,没有事件就没有IO,所以就说IO不阻塞了。也就是说IO阻塞其实是看IO是否发生,发生了才会阻塞,没有发生就说不上IO阻塞了。所以NIO的本质是延迟IO操作到真正发生IO的时候,而不是以前只要IO流打开了就一直等待IO操作。

 2.3 NIO核心组件

        Java NIO由Channels,Buffers,Selectors三个核心部分组成。虽然NIO中除此之外还有很多类和组件,但这三个是核心API。其它,如Pipe和File和Lock,只不过是与三个核心组件共同使用的工具类。

2.3.1 Channel

        Channel和IO流中的stream流差不多是一个等级的,只不过stream是单向的,如:InputStream,outputStream,而Channel是双向的,既可以用来读操作,又可以用来写操作。NIO中的Channel主要实现有:FileChannel(文件IO)、DatagramChannel(UDP)、SocketChannel(TCP)和ServerSocketChannel(Server和Client)。

1)FileChannel(文件IO):从文件中读写数据

2)DatagramChannel(UDP):能通过UDP读写网络中的数据

3)SocketChannel(TCP):能通过TCP读写网络中的数据

4)ServerSocketChannel(Server和Client):可以监听新进来的TCP连接,像web服务器那样,对每一个新进来的连接都会创建一个SocketChannel

2.3.2 Buffer

        NIO中的关键Buffer实现有:ByteBuffer,CharBuffer,DoubleBuffer,FloatBuffer,IntBuffer,LongBuffer,ShortBuffer。分别对应基本数据类型byte,char,double,float,int,long,short。

2.3.3 Selector

        Selector运行单线程处理多个Channel,如果你的应用打开了多个通道,但每个的流量都很低,使用Selector就会很方便。比如向Selector注册Channel,然后调用它的select(),这个方法会一直阻塞到某个注册的通道有事件就绪,一旦这个方法返回,线程就可以处理这些事情。

3 Channel

        channel是一个通道,可以通过它读取和写入数据。通道和流的区别在于,通道是双向的流是单向的(一个流必须是InputStream或者OutputStream的子类),而且通道可以用于读写或者同时用于读写。因为通道是全双工的,所以它可以比流更好的映射底层操作系统的API.

        NIO中通过channel封装了对数据源的操作,通过channel我们可以操作数据源,但又可以不用关心数据源的物理结构。这个数据源可以是多种的,可以是文件,也可以是网络socket,在大多数应用中,channel与文件描述或者socket是一一对应的。channel可用于在字节缓冲区和位于通道另一侧的实体之间有效的传输数据。

channel是一个对象,可以通过它读取和写入数据,通道就像是流,所有的数据都通过Buffer对象来处理,永远不会将字节直接写入通道中,相反是将数据写入包含一个或者多个字节的缓冲区,同样,我们不会直接从通道中读取字节,而是将数据读入缓冲区,再从缓冲区写这个字节

3.1 FileChannel

FileChannel类可以实现常用的read,write,以及scatter/gather操作,同时也提供了很多专用于文件的新方法

3.1.1 将数据读取到buffer中 

自定义一个TXT文件01.txt

 public static void main(String[] args) throws Exception {
        //创建FileChannel
        RandomAccessFile accessFile = new RandomAccessFile("C:\\Users\\LiuBuJun\\Desktop\\liubujun\\01.txt","rw");
        FileChannel channel = accessFile.getChannel();

        //创建buffer
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        //读取数据到buffer中
        int bytesRead = channel.read(buffer);
        // bytesRead = -1 到达文件末尾
        while (bytesRead != -1 ) {
            System.out.println("读取了:"+bytesRead);
            //将数据从buffer取出来 .flip()反转读写模式
            buffer.flip();
            while (buffer.hasRemaining()) {
                System.out.println((char)buffer.get());
            }
            //清除缓存区内容
            buffer.clear();
           bytesRead =  channel.read(buffer);
        }
        accessFile.close();
        System.out.println("over");


    }

读取结果: 

3.1.2 向fileChannel中写数据

    public static void main(String[] args) throws Exception {
        //创建FileChannel
        RandomAccessFile accessFile = new RandomAccessFile("C:\\Users\\LiuBuJun\\Desktop\\liubujun\\00.txt","rw");
        FileChannel channel = accessFile.getChannel();

        //创建buffer
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        //准备要写入的数据
        String str = "hello world";
        buffer.clear();
        //写入内容
        buffer.put(str.getBytes());
        buffer.flip();
        //因不清楚能一次性写入多少数据,所以需要放在循序里面
        while (buffer.hasRemaining()) {
            channel.write(buffer);
        }
        //关闭
        channel.close();
        System.out.println("写入完成");
    }

写入结果:

 3.1.3 FileChannel的其他方法

position:如果需要在FileChannel的某个特定位置进行数据的读写,可以通过调用position()方法获取FileChannel的当前位置。也可以通过调用position(long pos)方法设置FileChannel的当前位置。

如果将位置设置在文件结束之后,然后试图从文件通道中读取数据,该方法将返回-1(文件结束标志)

如果将位置设置在文件结束之后,然后向通道中写数据,文件将撑大到当前位置并写入数据。这可能导致“文件空洞”,磁盘上物理文件中写入的数据间有空隙

size: FileChannel实例的size()方法将返回该实例所关联文件的大小

truncate:可以使用fileChannel.truncate()方法截取一个文件。截取文件时,文件指定长度的后面部分将会删除。如:fileChannel.truncate(1024)就是截取文件的前1024个字节

force: FileChannel.force()方法将通道里尚未写入磁盘的数据强制写到磁盘。出于性能方面的考虑。操作系统会先将数据写在内存中,所以无法保证写入到FileChannel里的数据一定会及时的写到磁盘上。要保证这一点,需要调用force()。force()方法上有一个Boolean类型的参数,指名是否将文件元数据(权限信息)写到磁盘上。

transferTo和transferFrom:通道之间的数据传输,如果两个通道中有一个是FileChannel,那你可以将数据从一个channel传输到另外一个channel。

将fromChannel通道中的数据传输到toChannel时:transferFrom()方法

    public static void main(String[] args) throws Exception {
        //创建两个FileChannel
        RandomAccessFile aFile = new RandomAccessFile("C:\\Users\\LiuBuJun\\Desktop\\liubujun\\00.txt","rw");
        FileChannel fromChannel1 = aFile.getChannel();

        RandomAccessFile bFile = new RandomAccessFile("C:\\Users\\LiuBuJun\\Desktop\\liubujun\\02.txt","rw");
        FileChannel toChannel1 = bFile.getChannel();

        //将fromChannel通道中的数据传输到toChannel
        long position = 0;
        long size = fromChannel1.size();
        toChannel1.transferFrom(fromChannel1,position,size);

        aFile.close();
        bFile.close();
        System.out.println("over");
    }

将fromChannel通道中的数据传输到toChannel时:transferFrom()方法

    public static void main(String[] args) throws Exception {
        //创建两个FileChannel
        RandomAccessFile aFile = new RandomAccessFile("C:\\Users\\LiuBuJun\\Desktop\\liubujun\\00.txt","rw");
        FileChannel fromChannel1 = aFile.getChannel();

        RandomAccessFile bFile = new RandomAccessFile("C:\\Users\\LiuBuJun\\Desktop\\liubujun\\02.txt","rw");
        FileChannel toChannel1 = bFile.getChannel();

        //将fromChannel通道中的数据传输到toChannel
        long position = 0;
        long size = fromChannel1.size();
        fromChannel1.transferTo(0,size,toChannel1);

        aFile.close();
        bFile.close();
        System.out.println("over");
    }

3.2 Socket通道

新的socket通道类可以运行非阻塞模式并且是可选择的,可以激活大程序巨大的可伸缩性和灵活性。

socket通道特点:

1)DatagramChannel和SocketChannel实现定义读和写功能的接口而ServerSocketChannel不实现。ServerSocketChannel负责监听传入的连接和创建新的SocketChannel对象,它本身从不传输数据。

2)socket通道类在被实例化之前都会创建一个对等socket对象。对等socket可以通过调用socket()方法从一个通道上获取。

3.2.1 ServerSocketChannel

ServerSocketChannel是一个基于通道的socket监听器。它同我们所熟悉的Java.net.ServerSocket执行相同的任务,不过它增加了通道语义,因此能够在非阻塞模式下运行。

测试ServerSocketChannel监听链接:

    public static void main(String[] args) throws Exception {
        //端口号
        int port = 8888;

        ByteBuffer buffer = ByteBuffer.wrap("hello wrold".getBytes());
        //打开ServerSocketChannel
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //绑定
        ssc.socket().bind(new InetSocketAddress(port));
        //设置非阻塞模式
        ssc.configureBlocking(false);

        while (true) {
            System.out.println("正在等待链接");
            //监听新进的链接
            SocketChannel sc = ssc.accept();
            if (sc == null ) {
                System.out.println("null");
                Thread.sleep(2000);
            }else {
                System.out.println("链接来自:"+sc.socket().getRemoteSocketAddress());
                buffer.rewind(); //指针0
                sc.write(buffer);
                sc.close();
            }
        }
    }

访问链接后:

 3.2.2 SocketChannel

java NiO中的SocketChannel是一个连接到TCP网络套接字的通道。

SocketChannel特征

1)SocketChannel是用来连接Socket套接字

2)SocketChannel主要用途用来处理网络I/O的通道

3)SocketChannel是基于TCP连接传输

4)SocketChannel实现了可选择通道,可以被多路复用的

3.2.3 DatagramChannel

DatagramChannel是一个无连接的,对应的是UDP。DatagramChannel可以发送单独数据报给不同的地址,同样也可以接收来自任意地址的数据包。UDP不存在真正意义上的连接。

package com.liubujun.nio;

import org.junit.Test;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.charset.Charset;

/**
 * @Author: liubujun
 * @Date: 2022/2/13 14:49
 */


public class DatagramChannelDemo {

    @Test
    public void sendDatagram() throws Exception {
        DatagramChannel sendChannel = DatagramChannel.open();
        InetSocketAddress sendAddress = new InetSocketAddress("127.0.0.1", 9999);

        //发送
        while (true) {
            ByteBuffer buffer = ByteBuffer.wrap("发送hello".getBytes("UTF-8"));
            sendChannel.send(buffer,sendAddress);
            System.out.println("发送完成");
            Thread.sleep(1000);
        }
    }

    @Test
    public void receiveDatagram() throws Exception {
        DatagramChannel receiveChannel = DatagramChannel.open();
        InetSocketAddress receiveAddress = new InetSocketAddress(9999);

        //绑定
        receiveChannel.bind(receiveAddress);

        ByteBuffer receiveBuffer = ByteBuffer.allocate(1024);
        while (true) {
            receiveBuffer.clear();
            SocketAddress socketAddress = receiveChannel.receive(receiveBuffer);
            receiveBuffer.flip();
            System.out.println(socketAddress.toString());
            System.out.println(Charset.forName("UTF-8").decode(receiveBuffer));
        }

    }

}

 

4 Scatter/Gather

分散(scatter):从channel中读取是指在读操作时将读取的数据写入多个buffer中。因此,Channel将从Channel中读取的数据分散到多个Buffer

聚集(Gather):写入Channel是指在写操作时将多个buffer的数据写入同一个Channel。因此,Channel将多个Buffer中的数据聚集后发送到Channel



这篇关于java NIO-Channel的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程