Netty03——NIO的核心组件

一、缓冲区Buffer

 缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(数组),该对象提供了一组方法,可以更轻松地使用内存块,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。Channel 提供从文件、 网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer。
在这里插入图片描述
 在 NIO 中,Buffer 是一个顶层父类,它是一个抽象类,常用的子类如下:
  ①ByteBuffer:存储字节数据到缓冲区
  ②ShortBuffer:存储short型数据到缓冲区
  ③CharBuffer:存储字符数据到缓冲区
  ④IntBuffer:存储整数数据到缓冲区
  ⑤LongBuffer:存储长整型数据到缓冲区
  ⑥DoubleBuffer:存储小数到缓冲区
  ⑦FloatBuffer:存储小数到缓冲区
 可以看出对于 Java 中的基本数据类型(boolean除外),都有一个 Buffer 类型与之 相对应,最常用的自然是 ByteBuffer 类(二进制数据),该类的主要方法如下:

public abstract class ByteBuffer {
    //缓冲区创建相关api 
    public static ByteBuffer allocateDirect(int capacity);// 创建直接缓冲区 
    public static ByteBuffer allocate(int capacity);// 设置缓冲区的初始容量
    public static ByteBuffer wrap(byte[] array);// 把一个数组放到缓冲区中使用
    //构造初始化位置offset和上界length的缓冲区 
    public static ByteBuffer wrap(byte[] array, int offset, int length);
    // 缓存区存取相关API 
    public abstract byte get();// 从当前位置position上get,get之后,position会自动+1 
    public abstract byte get(int index);//从绝对位置get 
    public abstract ByteBuffer put(byte b);// 从当前位置上添加,put之后,position会自动+1 
    public abstract ByteBuffer put(int index, byte b);// 从绝对位置上put 
}

 Buffer类定义了所有的缓冲区都具有的四个属性来提供关于其所包含的数据元素的信息:

属性描述
capacity容量,即可以容纳的最大数据量;在缓冲区创建时被设定并且不能改变
limit表示缓冲区的当前终点,不能对缓冲区超过极限的位置进行读写操作。极限是会修改的
position位置,下一个要被读或写的元素的索引, 每次读写缓冲区数据时都会改变该值, 为下次读写作准备
mark标记
二、通道Channel

 NIO的通道类似于流,但有些区别:
  ①通道可以同时进行读写,而流只能读或者只能写;
  ②通道可以实现异步读写数据;
  ③通道可以从缓冲区读数据,也可以写数据到缓冲区;
 BIO 中的 Stream 是单向的,例如 FileInputStream 对象只能进行读取数据的操作,而 NIO 中的通道 (Channel)是双向的,可以读,也可以写。Channel在NIO中是一个接口

public interface Channel extends Closeable{} 

 常用的 Channel 类有:FileChannel(用于文件数据的读写)、 DatagramChannel(用于 UDP 数据的读写)、ServerSocketChannel 和 SocketChannel(类似 ServerSocket 和 Socket, 用于 TCP 的数据读写)。
 1、FileChannel
  FileChannel主要用来对本地文件进行 IO 操作,常见的方法有:

//从通道读取数据并放到缓冲区中
public int read(ByteBuffer dst);
//把缓冲区的数据写到通道中
public int write(ByteBuffer src);
//从目标通道中复制数据到当前通道
public long transferFrom(ReadableByteChannel src, long position, long count);
//把数据从当前通道复制给目标通道
public long transferTo(long position, long count, WritableByteChannel target);

 示例1:向本地文件写入数据

public class NIOFileChannel {
    public static void main(String[] args) throws Exception {
        String str = "Hello, world!";
        FileOutputStream fileOutputStream = new FileOutputStream("d:\\file.txt");
        FileChannel fileChannel = fileOutputStream.getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        byteBuffer.put(str.getBytes());
        byteBuffer.flip();
        fileChannel.write(byteBuffer);
        fileOutputStream.close();
    }
}

 示例2:从本地文件读数据

public class NIOFileChannel {
    public static void main(String[] args) throws Exception {
        File file = new File("d:\\file.txt");
        FileInputStream fileInputStream = new FileInputStream(file);
        FileChannel fileChannel = fileInputStream.getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
        fileChannel.read(byteBuffer);
        System.out.println(new String(byteBuffer.array()));
        fileInputStream.close();
    }
}

 示例3:只用一个Buffe实现文件复制

public class NIOFileChannel {
    public static void main(String[] args) throws Exception {
        FileInputStream fileInputStream = new FileInputStream("1.txt");
        FileChannel fileChannel01 = fileInputStream.getChannel();
        FileOutputStream fileOutputStream = new FileOutputStream("2.txt");
        FileChannel fileChannel02 = fileOutputStream.getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(512);
        while (true) {
            int read = fileChannel01.read(byteBuffer);
            if (-1 == read) {
                break;
            }
            byteBuffer.flip();
            fileChannel02.write(byteBuffer);
            // 不要忘了读写的转换
            byteBuffer.flip();
        }
        fileInputStream.close();
        fileOutputStream.close();
    }
}

 示例4:使用件transferFrom拷贝文件

public class NIOFileChannel {
    public static void main(String[] args) throws Exception {
        FileInputStream fileInputStream = new FileInputStream("d:\\a.jpg");
        FileOutputStream fileOutputStream = new FileOutputStream("d:\\b.jpg");
        FileChannel sourceChannel = fileInputStream.getChannel();
        FileChannel destChannel = fileOutputStream.getChannel();
        destChannel.transferFrom(sourceChannel, 0, sourceChannel.size());
        sourceChannel.close();
        destChannel.close();
        fileInputStream.close();
        fileOutputStream.close();
    }
}

 2、关于Buffer 和 Channel的注意事项和细节
 ①ByteBuffer 支持类型化的put 和 get, put 放入的是什么数据类型,get时就应该使用相应的数据类型来取出,否则可能报 BufferUnderflowException 异常

ByteBuffer buffer = ByteBuffer.allocate(64);
buffer.putInt(100);
buffer.putLong(9);
buffer.putChar('H');
buffer.putShort((short) 4);

buffer.flip();

int anInt = buffer.getInt();
long aLong = buffer.getLong();
char aChar = buffer.getChar();
short aShort = buffer.getShort();

 ②可以将一个普通Buffer 转成只读Buffer

ByteBuffer buffer = ByteBuffer.allocate(64);
for (int i = 0; i < 64; i++) {
    buffer.put((byte) i);
}
buffer.flip();
// 只读
ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
while (readOnlyBuffer.hasRemaining()) {
    System.out.println(readOnlyBuffer.get());
}
readOnlyBuffer.put((byte) 100);// ReadOnlyBufferException

 ③NIO 还提供了 MappedByteBuffer, 可以让文件直接在内存(堆外的内存)中进行修改, 而如何同步到文件由NIO 来完成

RandomAccessFile randomAccessFile = new RandomAccessFile("1.txt", "rw");
FileChannel channel = randomAccessFile.getChannel();
// 参数说明:mode=FileChannel.MapMode.READ_WRITE-映射到直接内存的模式,这里是可读可写
// position=0-可以直接修改的起始位置
// size=5-映射到内存的大小,即将多少个字节映射到内存,和position一起就限制了文件可被修改的范围
// MappedByteBuffer的本质是DirectByteBuffer
MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);
mappedByteBuffer.put(0,(byte)'H');
mappedByteBuffer.put(3,(byte)'9');
mappedByteBuffer.put(5,(byte)'Y');// IndexOutOfBoundsException
randomAccessFile.close();

 ④前面的示例都是通过一个Buffer 完成的,NIO 还支持通过多个 Buffer (即 Buffer 数组) 完成读写操作,即 Scattering 和 Gathering

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);
// 绑定7000端口
serverSocketChannel.socket().bind(inetSocketAddress);
// Buffer数组
ByteBuffer[] byteBuffers = new ByteBuffer[2];
byteBuffers[0] = ByteBuffer.allocate(5);
byteBuffers[1] = ByteBuffer.allocate(3);
// 接收客户端数据
SocketChannel socketChannel = serverSocketChannel.accept();
int messageLength = 8;
while (true) {
    int byteRead = 0;
    while (byteRead < messageLength) {
        // 向buffer数据中读数据直到读满
        long l = socketChannel.read(byteBuffers);
        byteRead += l;
        System.out.println("byteRead=" + byteRead);
        Arrays.asList(byteBuffers).stream().map(buffer -> "position=" + buffer.position() + ",limit=" + buffer.limit()).forEach(System.out::println);
    }
    // 读写转换
    Arrays.asList(byteBuffers).forEach(buffer -> buffer.flip());
    long byteWrite = 0;
    while (byteWrite < messageLength) {
        long l = socketChannel.write(byteBuffers);
        byteWrite += l;
    }
    Arrays.asList(byteBuffers).forEach(buffer -> buffer.clear());
    System.out.println("byteRead=" + byteRead + ",byteWrite=" + byteWrite + ",messageLength=" + messageLength);
}
三、选择器Selector

 Java 的 NIO,用非阻塞的 IO 方式;可以用一个线程,处理多个客户端连接,就会用到Selector。Selector 能够检测多个注册的通道上是否有事件发生(注意:多个Channel可以以事件的方式注册到同一个Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个线程去管理多个通道,也就是管理多个客户端连接或请求。只有在连接/通道真正有读写等事件发生时,才会进行读写,这就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程,也就避免了多线程之间的上下文切换等。
在这里插入图片描述
Netty 的 IO 线程 NioEventLoop 聚合了 Selector(选择器, 也叫多路复用器),可以同时并发处理成百上千个客户端连接。当线程从某客户端 Socket 通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入和 输出通道。由于读写操作都是非阻塞的,这就可以充分提升 IO 线程的运行效率,避免由于频繁 I/O 阻塞导致的线程挂起。一个 I/O 线程可以并发处理 N 个客户端连接和读写操作,这从根本上解决了传统同步阻塞 I/O 一连接一线程的模型,使架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。
 Selector的常用方法:

public abstract class Selector implements Closeable { 
	//得到一个选择器对象
	public static Selector open(); 
	//监控所有注册的通道,当其中有IO操作可以进行时,将对应的 SelectionKey 加入到内部集合中并返回,参数用来 设置超时时间
	public int select(long timeout);
	//从内部集合中得到所有的 SelectionKey 
	public Set<SelectionKey> selectedKeys();
}

 NIO中的 ServerSocketChannel 功能类似 ServerSocket,SocketChannel 的功能类似 Socket。  Selector 相关方法说明:
  selector.select(); //阻塞
  selector.select(1000);//阻塞1000毫秒,在1000毫秒后返回;
  selector.wakeup();//唤醒selector
  selector.selectNow();//不阻塞,立马返还

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值