java NIO

1、阻塞IO与非阻塞IO:
        阻塞IO:传统的socket每个请求都要创建一个线程Thread,在进行IO读写的时候,会阻塞直到有文件或者数据的读写才会继续进行,面向流,阻塞的

        非阻塞IO: 基于Reactor(响应式),创建Selector,将通道Channal注册到Selector,当真正  发生IO文件或数据读写时才去发生相应的处理,面向缓冲Buffer的,非阻塞的
 
2、3个核心组件:Channal,Buffer,Selector(还有其他组件,Pipe,FileLock)

        Channel通道(接口),通过Channel进行数据操作,是全双工的,可以读,也可以写(IO是单向的,比如InputStream输入流),Channel是一个接口,有4种实现(这4种实现基本上涵盖了文件IO、基于UDP和TCP的IO操作)
        Channel的实现:FileChannel(文件IO)
案例一:从一个文件Channel中读取数据
步骤:1、获取文件通道,有2种方式,可以直接通过文件IO获取,也可以通过RandomAccessFile获取

        //获取FileChannel的实例,打开一个channel(通过new FileInputStream的方式获取)
        FileChannel fileChannel1 = new FileInputStream("D:\\data\\nio\\test01.txt").getChannel();

        
        
        

        //通过随机文件读取方式获取FileChannel实例,通过游标指针随机获取文件内容
        //mode的取值:r(readOnly),rw(readAndWrite),rws(文件的读写内容和元数据每次都会同步到底层设备,也就是直接磁盘),rwd(文件的读写内容每次都会同步到底层设备)
        FileChannel fileChannel = new RandomAccessFile(filePath, "rw").getChannel();

        

2、创建Buffer并指定缓冲区的大小,一般是获取ByteBuffer

//创建一个缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);


3、读取数据

        StringBuilder stringBuilder = new StringBuilder();
        //通道被缓冲区读取数据(被read),直到读到的数据==-1
        int read;
        while ((read = channel.read(buffer)) != -1) {
            System.out.println("read:" + read);

            //转换方向?写到stringBuilder
            buffer.flip();
            while (buffer.hasRemaining()) {
                stringBuilder.append((char) buffer.get());
            }
            buffer.clear();
        }

4、关闭资源:

//关闭通道
channel.close();


案例二:将内容写入一个文件Channel中
1、获取文件通道

//打开一个channel
FileChannel fileChannel = new RandomAccessFile(filePath, "rw").getChannel();


2、创建Buffer,clear并且put(byte)

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

//清理一下
buffer.clear();


3、只要Buffer里面有数据就channel.write(buffer)

//将内容put到buffer
buffer.put(context.getBytes(StandardCharsets.UTF_8));
//转换方向?由读转为写
buffer.flip();
//如果buffer里面有内容,就写入通道
while (buffer.hasRemaining()) {
    fileChannel.write(buffer);
}

4、关闭资源:

fileChannel.close();

案例三:FileChannel之间的数据读写转换(不通过buffer)

通道之间的数据传输(不通过buffer,实现通道之间的零拷贝技术)transferTo和transferFrom

        FileChannel fileChannel1 = new RandomAccessFile(path1, "rw").getChannel();
        FileChannel fileChannel2 = new RandomAccessFile(path2, "rw").getChannel();

        long position = fileChannel2.position();
        long size = fileChannel2.size();
        //transferFrom数据从哪里转移过来的,channel1的数据是从channel2转移过来的,也就是说数据从channel2往channel1写入
        fileChannel1.transferFrom(fileChannel2, position, size);

        fileChannel1.close();
        fileChannel2.close();

FileChannel的其他重要方法:

force()是否强制写磁盘,

position()获取通道读取的位置,

size()获取文件大小,

truncate(int)截取对应字节的内容

Channel的另一种实现:Socket通道:

相关特点:

  1. 功能更强大,可以通过selector实现通道选择,一个线程可以管理多个socket,节约线程资源开销以及线程上下文切换
  2. DatagramChannel和SocketChannel实现了读和写的功能,但是ServerSocketChannel没有实现读写功能,也就是说ServerSocketChannel不能读写数据,主要负责监听socket的连接和创建新的SocketChannel,相当于一个监听器的功能,而SocketChannel实现了ByteChannel接口 继承了ReadableByteChannel, WritableByteChannel接口,具有读写功能(新版本ServerSocketChannel也可以读写)
  3. Channel与socket的关系,socket不能使用多种协议,而Channel可以
  4. Channel可以设置阻塞和非阻塞模式(一般服务端),而socket不能


实现ServerSocketChannel(基于TCP的服务端Channel)

        //静态方法创建Channel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //绑定端口
        serverSocketChannel.bind(new InetSocketAddress(888), 3000);
        //设置非阻塞
        serverSocketChannel.configureBlocking(false);

        ByteBuffer buffer=ByteBuffer.wrap("test".getBytes(StandardCharsets.UTF_8),0,1024);
        while (true) {
            System.out.println("等待socket客户端链接中。。。。");
            SocketChannel accept = serverSocketChannel.accept();
            //没有链接时打印null并sleep
            if (accept == null) {
                System.out.println("没有设备链接:" + null);
                Thread.sleep(2000);
            } else {
                //有链接时,打印远程客户端地址,写入buffer
                //获取socket
                Socket socket = accept.socket();
                //获取socket的地址
                SocketAddress remoteAddress = accept.getRemoteAddress();
                System.out.println("remoteAddress:" + remoteAddress);
                buffer.rewind();
                accept.write(buffer);
                serverSocketChannel.close();
            }
        }

实现SocketChannel(基于TCP的客户端Channel)

SocketChannel socketChannel = SocketChannel.open();

        socketChannel.connect(new InetSocketAddress("127.0.0.1", 8888));
        System.out.println(socketChannel.isConnected());
        System.out.println(socketChannel.isOpen());
        socketChannel.configureBlocking(false);
        System.out.println(socketChannel.isBlocking());
        System.out.println(socketChannel.isRegistered());

        socketChannel.configureBlocking(true);

  2.2   Buffer:通道中的数据从一个Buffer(缓存区)中读取,用于与Channel进行交互,实现数据的写入和读出,本质上,Buffer是在NIO的读写的时候开辟的一块内存区域,从数据结构的角度来看,就是一个数组:

 Buffer是一个抽象类,既然是数据类型中的数组,包含 capacity、limit和position,关系为:0 <= mark <= position <= limit <= capacity,缓冲区是非线程安全的,可以使用链式调用:buffer.flip().position(23).limit(42);有些缓冲区是只读的,有些是可读也可写的,如果只读的缓冲区,数据只能读,不能改变值,但可以mark,position,可以调用isReadOnly来判断缓冲区是否可读;几个重要方法:

Buffer在读模式下,position=0,limit=当前buffer内可读的数据的位置,capacity表示容量,固定的

Buffer在写模式下,position=下一个可写入的位置,limit=容量

clear,让buffer为新的序列的读取或put操作做好准备,并设置position = 0; limit = capacity; mark = -1;

flip,让buffer为新的序列的写入或get操作做好准备,并设置 limit = position; position = 0; mark = -1;读写模式转换

rewind:准备好重新读,倒带的意思,并且数据仍然在容器,limit不变,position=0;fileChannel.read(buffer);//从channel读取数据到buffer区,根据buffer的容量
buffer.flip();//转换读写模式,实际上是转换postion和limit的位置
buffer.remaining() > 0 //返回limit-position的值,>0说明buffer里面还有数据
hasRemaining() //返回position < limit的布尔值,用来判断buffer里面是否还有数据 buffer.get(); //获取数据
buffer.clear(); //清空数据,将position,limit恢复到初始状态,返回当前buffer,切换为写模式,实际上并未清除buffer的数据

buffer.compact();  //ByteBuffer才有该方法,Buffer没有,已读数据清空,未读完数据向前压缩,返回新缓冲区

buffer.get() //从缓冲区读取数据,实际上是获取缓冲区数组中某个index的值

buffer.rewind()  //position置为0,从头开始读的意思

缓冲区分片:在缓冲区切一片作为一个子缓冲区,现有缓冲区和子缓冲区数据是共享的

 //创建缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(10);
        //缓冲区写入内容
        for (int i = 0; i < buffer.capacity(); i++) {
            buffer.put((byte) i);
        }
        //创建子缓冲区,还是当前缓冲区,因为position方法返回的是this,只是把position置为新的position
        buffer.position(3);
        buffer.limit(7);
        //从缓冲区切出一个分片(子缓冲区)
        ByteBuffer sliceBuffer = buffer.slice();
        //改变子缓冲区的值
        for (int i = 0; i < sliceBuffer.capacity(); i++) {
            byte b = sliceBuffer.get();

            b *= 10;

            sliceBuffer.put(i, b);
        }
        //切换子缓冲区的读写模式(改为读模式)
        sliceBuffer.flip();
        //读取子缓冲区的数据
        while (sliceBuffer.hasRemaining()) {
            System.out.println(sliceBuffer.get());
        }

        //原缓冲区重新设置position和limit,不能使用flip()切换读写模式,因为分片时设置limit=limit-position=4
        buffer.position(0);
        //设置limit=原来的capacity
        buffer.limit(buffer.capacity());

        //读取原buffer的内容,实际上是原缓冲区+子缓冲区+原缓冲区的内容
        while (buffer.hasRemaining()) {
            System.out.println(buffer.get());
        }

只读缓冲区:只能读取数据内容,不能写入内容

//设置为只读缓冲区,返回一个新缓冲区,与原缓冲区一直,只是readOnly=true
        buffer.asReadOnlyBuffer();

直接缓冲区:

/**
 * 基本使用步骤:
 * 1、创建Buffer,clear一下
 * 2、往Buffer里面put数据
 * 3、flip一下,切换为读模式
 * 4、读取数据
 * 5、clear或compact
 */

Selector:选择器,也可以理解为多路复用器
1、与Channel的关系,用于检查一个或多个(NIO)Channel的状态是否处于可读、可写,可以实现一个线程管理多个通道(Channel),也就是多个网络连接,降低资源消耗
2、SelectableChannel,可选择的选择器,只有实现了SelectableChannel这个抽象类的的Channel,才能注册到选择器,

 


如SocketChannel继承了,可以注册到选择器,但FileChannel没继承,不能注册到选择器
3、一个通道可以注册到多个选择器,但是在每一个选择器上,只能注册一次,通道(Channel) -->继承SelectableChannel---->注册Selector
4、如何注册到选择器?
Channel.register(),并指定Selector感兴趣的事(可以理解为在Channel发生哪类事件时触发选择器的哪类Handler方法)

 

2、3个重要组件和概念:
3、管道,文件锁,

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值