IO系列第二章——NIO (Buffer&&Channel)

2 NIO

2.1 NIO概念

Java NIO(全称java non-blockingIO): 同步非阻塞,服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求就进行处理。

NIO和BIO的作用和目的相同,但是实现方式不同。BIO以流的方式处理数据,而NIO以块的方式处理数据,因此效率要高很多。

NIO是在Java 1.4开始引入了NIO框架(java.nio包) ,java提供了一系列改进的输入输出的新特性,这些统称NIO,也有人成为New IO.

NIO提供了Channel、Selector、 Buffer等新的抽象 ,可以构建多路复用IO程序,同时提供更接近操作系统底层的高性能数据操作方式。传统BIO基于字节流和字符流进行操作,而NIO基于Channe(通道)和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区,或者从缓冲区写入到通道中。Selector用于监听多个通道的事件,因此使用单个线程就可以监听多个数据通道。

2.2 工作原理

在这里插入图片描述
1、一个线程一个selector,一个线程对应多个channel(连接),每个Channel对应一个Buffer。
2、多个channel可以注册到一个selector,事件决定selector切换到哪一个channel。
3、数据的读写通过Buffer,BIO中的流是单向的,要么输入流要么输出流,NIO的Buffer是可双向读写,通过flip方法切换即可。
4、channel也是双向的,可以返回底层操作系统的情况,例如Linux,底层的操作系统通道就是双向的。

2.3 使用场景

NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器。

2.3.1 NIO核心— 缓冲区 Buffe

概念
缓冲区(Buffer) :缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存,这块内存被包装成NIO Buffer对象,可以理解成是一个容器,是一个特殊的数组,该对象提供了一组方法,用来方便的访问该块内存。
在这里插入图片描述
Channel提供从文件或网络读取数据的渠道,但是读取或者写入的数据都是经过Buffer。

2.3.2 Buffer类及其子类

在NIO中,Buffer是一个顶级父类,也是一个抽象类,有很多的子类。
在这里插入图片描述

2.3.3 Buffer中的属性

在这里插入图片描述
在这里插入图片描述

2.3.4 Buffer中的方法

在这里插入图片描述

2.3.5 Buffer的基本用法

使用Buffer读写数据一般遵循以下四个步骤:
1、创建缓冲区,写入数据到Buffer
2、调flip()方法将缓冲区改成读取模式
3、从Buffer中读取数据
4、调用clear()方法或者compact()方法

虽然java中的基本数据类型都有对应的Buffer类型与之对应(Boolean除外),但是使用频率最高的是ByteBuffer类。所以先介绍一下ByteBuffer中的常用方法。

ByteBuffer中常用方法

在这里插入图片描述

allocate(int):创建间接缓冲区:在堆中开辟,易于管理,垃圾回收器可以回收,空间有限,读写文件 速度较慢。 allocateDirect(int):创建直接缓冲区:不在堆中,物理内存中开辟空间,空间比较大,读写文件速度 快,缺点:不受垃圾回收器控制,创建和销毁耗性能。

2.4 案例

2.4.1 案例1:ByteBuffer的常用方法一

package testIO;
import java.nio.ByteBuffer;
/**
 * Buffer的基本用法
 */
public class BufferTest01 {
    public static void main(String[] args) {
// 1、创建缓冲区,写入数据到Buffer
        ByteBuffer buffer=ByteBuffer.allocate(1024);//创建指定容量的间接缓冲区

 ByteBuffer buffer1=ByteBuffer.allocateDirect(1024);//创建指定容量的直接缓冲区
//写入数据的方式1
//buffer.put("hi,dude".getBytes());
//写入数据的方式2
        buffer.put((byte) 'h');
        buffer.put((byte) 'i');
        buffer.put((byte) ',');
        buffer.put((byte) 'd');
        buffer.put((byte) 'u');
        buffer.put((byte) 'd');
        buffer.put((byte) 'e');
// 2、调flip()方法将缓冲区改成读取模式
        buffer.flip();
// 3、从Buffer中读取数据的
// 方式1:单个自己的读取
/*while(buffer.hasRemaining()) {
byte b = buffer.get();
System.out.println((char) b);
}*/
//读取数据的方式2:
        byte[] data=new byte[buffer.limit()];
        buffer.get(data);
        System.out.println(new String(data));
// 4、调用clear()方法或者compact()方法
        buffer.clear();
        buffer.compact();
    }
}

clear(): position将被置为0,limit被设置成capacity的值。可以理解为Buffer被清空了,但 是Buffer中的数据并未清除,只是这些标记告诉我们可以从哪里开始往Buffer里写数据。如果Buffer中 有一些未读的数据,调用clear()方法,未读数据将“被遗忘”,意味着不再有任何标记会告诉你哪些数据 被读过,哪些还没有.
compact(): 将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后 面。limit属性依然像clear()方法一样,设置成capacity。现在Buffer准备好写数据了,但是不会覆 盖未读的数据。

2.4.2 案例2:ByteBuffer的常用方法二

ByteBuffer 支持类型化的put和get, put放入的是什么数据类型,get取出来依然是什么类型, 否则可能出现BufferUnderflowException 异常。

        import java.nio.ByteBuffer;
public class BufferTest02 {
    public static void main(String[] args) {
//1、创建buffer
        ByteBuffer buffer=ByteBuffer.allocate(1024);
//2、写入数据:按照类型化方式
        buffer.putChar('K');
        buffer.putLong(1024L);
        buffer.putInt(512);
        buffer.putShort((short) 0);
//3、读写切换
        buffer.flip();
//4、读取数据
        System.out.println(buffer.getChar());
        System.out.println(buffer.getLong());
        System.out.println(buffer.getInt());
        System.out.println(buffer.getShort());
    }
}

2.4.3 案例3:一个普通Buffer转成只读Buffer .

import java.nio.ByteBuffer;
public class ReadOnlyBuffer {
    public static void main(String[] args) {
//创建一个Buffer
        ByteBuffer buffer=ByteBuffer.allocate(64);
//循环放入数据
        for (int i=0;i<buffer.capacity();i++){
            buffer.put((byte) i);
        }
//读写切换
        buffer.flip();
//得到一个只读的buffer
        ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
        System.out.println("readOnlyBuffer类型:"+readOnlyBuffer.getClass());
//读取数据
        while(readOnlyBuffer.hasRemaining()){
            System.out.println(readOnlyBuffer.get());
        }
//写入数据会抛出异常--ReadOnlyBufferException
        readOnlyBuffer.put((byte) 66);
    }
}

在这里插入图片描述

2.5 NIO核心— 通道 Channel

2.5.1 Channel介绍

通道(Channel) :类似于BIO中的stream,例如FileInputStream对象,用来建立到目标(文件,网络套接字,硬件设备等)的一个连接,
但是也有区别:
既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。

通道可以异步地读写。

通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。
在这里插入图片描述

2.5.2 Channel的实现

常用的Channel类有: FileChannel、DatagramChannel、ServerSocketChannel 和SocketChannel.
FileChannel 从文件中读写数据。
DatagramChannel 能通过UDP读写网络中的数据。
SocketChannel 能通过TCP读写网络中的数据。
ServerSocketChannel可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。
在这里插入图片描述

2.5.3 FileChannel

FileChannel主要用来对本地文件进行读写操作,但是FileChannel是一个抽象类,所以我们实际用的更多的是其子类FileChannelImpl,

常用的API
在这里插入图片描述

2.6 案例

2.6.1 写入数据到文件

在这里插入图片描述

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;

import java.nio.channels.FileChannel;
/**
 * 写出数据到本地文件中
 * @Author wanglina
 * @Version 1.0
 */
public class FileChannelTest01 {
    public static void main(String[] args) throws IOException {

        String msg="hi,xiaoli";
        String fileName="channel01.txt";
//创建一个输出流
        FileOutputStream fileOutputStream=new FileOutputStream(fileName);
//获取同一个通道--channel的实际类型是FileChannelImpl
        FileChannel channel=fileOutputStream.getChannel();
//创建一个缓冲区
        ByteBuffer buffer=ByteBuffer.allocate(1024);
//将信息写入缓冲区中
        buffer.put(msg.getBytes());
//从第一个数据进行操作,把position移会俩   对缓冲区读写切换
        buffer.flip();
//将缓冲区中的数据写到到通道中
        int num=channel.write(buffer);
        System.out.println("写入完毕!"+num);
        fileOutputStream.close();
    }
}

在这里插入图片描述

2.6.2读取文件中的数据

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * 从本地文件中读取数据
 * @Author wanglina
 * @Version 1.0
 */
public class FileChannelTest02 {
    public static void main(String[] args) throws IOException {
        File file=new File("channel01.txt");
        //创建输入流
        FileInputStream fileInputStream=new FileInputStream(file);
//获取通道
        FileChannel channel = fileInputStream.getChannel();
//创建缓冲区
        ByteBuffer buffer=ByteBuffer.allocate((int) file.length());
//将通道中的数据读取buffer中
        channel.read(buffer);
//将buffer中的字节数组转换为字符串输出
        System.out.println(new String(buffer.array()));
        fileInputStream.close();
    }
}

2.6.3、文件的复制

方式1:非transferFrom方式

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
 * 实现文件的复制
 * @Author wanglina
 * @Version 1.0
 */
public class FileChannelTest03 {
    public static void main(String[] args) throws IOException {
//准备好要复制的源文件和目标文件
        File file=new File("channel01.txt");
        File fileCopy=new File("channelCopy01.txt");
//创建输入和输出流
        FileInputStream fileInputStream=new FileInputStream(file);
        FileOutputStream fileOutputStream=new FileOutputStream(fileCopy);
//获取两个通道
        FileChannel inChannel = fileInputStream.getChannel();
        FileChannel outChannel = fileOutputStream.getChannel();
//创建缓冲区
        ByteBuffer buffer=ByteBuffer.allocate(1024);
        int len=0;
        while(true){
//将标志位重置
            buffer.clear();
            len=inChannel.read(buffer);
            System.out.println("len="+len);
            if(len==-1){
                break;
            }
//读写切换
            buffer.flip();
//将buffer中的数据写入到了通道中
            outChannel.write(buffer);
        }
        System.out.println("复制完毕!OK!");
        inChannel.close();
        outChannel.close();
        fileInputStream.close();
        fileOutputStream.close();
    }
}

方式2:使用transferFrom方式拷贝

import java.io.*;
import java.nio.channels.FileChannel;
/**
 * 文件的复制--方式2 transferFrom
 * @Author wanglina
 * @Version 1.0
 */
public class FileChannelTest04 {
    public static void main(String[] args) throws IOException {
//要复制的文件和目标文件
        File file=new File("channel01.txt");
        File fileCopy=new File("channel02.txt");
//创建输入流和输出流
        FileInputStream fileInputStream=new FileInputStream(file);
        FileOutputStream fileOutputStream=new FileOutputStream(fileCopy);
//获取两个通道
        FileChannel inChannel=fileInputStream.getChannel();
        FileChannel outChannel=fileOutputStream.getChannel();
//使用transferFrom复制--两个方式适合大文件的复制
        outChannel.transferFrom(inChannel, 0,inChannel.size());
//使用transferTo复制---注意方向
//inChannel.transferTo(0, inChannel.size(),outChannel);
        System.out.println("复制完毕!OK");
        inChannel.close();
        outChannel.close();
        fileInputStream.close();
        fileOutputStream.close();
    }
}

这里说一下Byte 存储的最大容量差不多是几十MB
byte占用1个字节 可表示范围 -128-127 ,为什么有负数可以参考补码的知识。那么当
byte []bytes=new byte[1024];
在这里插入图片描述
通过这个输出就可以看出来,byte数组,每次存储一个字符或者数字,通过ASCII存储在对应的数组位置上,一个位置一个字节。因为我写的过程突然不太了解这个byte的原理,所以仔细的想了一下。关于Selector的内容放在下一篇了。这个篇幅太长了。

持续学习欢迎关注公众号
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凌晨里的无聊人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值