我是傲骄鹿先生,沉淀、学习、分享、成长。
如果你觉得文章内容还不错的话,希望不吝您的「一键三连」,文章里面有不足的地方希望各位在评论区补充疑惑、见解以及面试中遇到的奇葩问题
目录

缓冲区本质上是一个可以读写数据的内存块,可以理解为是一个容器对象(含数组),该对象提供了一组方法,可以更轻松地使用内存块,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。
Channel 提供从文件、网络读取数据的渠道,但是读取或者都必须经过 Buffer。
在 Buffer 子类中维护着一个对应类型的数组,用来存放数据:
- public abstract class IntBuffer extends Buffer implements Comparable
{ -
- // These fields are declared here rather than in Heap-X-Buffer in order to
- // reduce the number of virtual method invocations needed to access these
- // values, which is especially costly when coding small buffers.
- //
- final int[] hb; // Non-null only for heap buffers
- final int offset;
- boolean isReadOnly; // Valid only for heap buffers
-
- // Creates a new buffer with the given mark, position, limit, capacity,
- // backing array, and array offset
- //
- IntBuffer(int mark, int pos, int lim, int cap, // package-private
- int[] hb, int offset)
- {
- super(mark, pos, lim, cap);
- this.hb = hb;
- this.offset = offset;
- }
-
- // Creates a new buffer with the given mark, position, limit, and capacity
- //
- IntBuffer(int mark, int pos, int lim, int cap) { // package-private
- this(mark, pos, lim, cap, null, 0);
- }
- }
| Buffer 常用子类 | 描述 |
|---|---|
| ByteBuffer | 存储字节数据到缓冲区 |
| ShortBuffer | 存储字符串数据到缓冲区 |
| CharBuffer | 存储字符数据到缓冲区 |
| IntBuffer | 存储整数数据据到缓冲区 |
| LongBuffer | 存储长整型数据到缓冲区 |
| DoubleBuffer | 存储浮点型数据到缓冲区 |
| FloatBuffer | 存储浮点型数据到缓冲区 |
Buffer 中定义了四个属性来提供包含的数据元素。
- // Invariants: mark <= position <= limit <= capacity
- private int mark = -1;
- private int position = 0;
- private int limit;
- private int capacity;
| capacity | 容量,即可以容纳的最大数据量;在缓冲区被创建时候就被指定,无法修改 |
| limit | 表示缓冲区的当前终点,不能对缓冲区超过极限的位置进行读写操作,但极限是可以修改的 |
| position | 当前位置,下一个要被读或者写的索引,每次读写缓冲区数据都会改变该值,为下次读写做准备 |
| Mark | 标记当前 position 位置,当 reset 后回到标记位置。 |
NIO 的通道类似于流,但有如下区别:
常用的 Channel 有:FileChannel、DatagramChannel、SocketChannel、SocketServerChannel。
FileChannel 主要用来对本地文件进行 IO 操作,常见的方法有:
- public class NIOFileChannel {
-
- public static void main(String[] args) throws IOException {
- String str = "Hello,Java菜鸟程序员";
- //创建一个输出流
- FileOutputStream fileOutputStream = new FileOutputStream("hello.txt");
- //获取通道
- FileChannel channel = fileOutputStream.getChannel();
- //创建缓冲区
- ByteBuffer byteBuffer = ByteBuffer.allocate(100);
- //写入byteBuffer
- byteBuffer.put(str.getBytes());
- //切换模式
- byteBuffer.flip();
- //写入通道
- channel.write(byteBuffer);
- //关闭
- channel.close();
- fileOutputStream.close();
- }
- }
- public class NIOFileChannel {
- public static void main(String[] args) throws IOException {
- FileInputStream fileInputStream = new FileInputStream("hello.txt");
- FileChannel channel = fileInputStream.getChannel();
- ByteBuffer byteBuffer = ByteBuffer.allocate(100);
- channel.read(byteBuffer);
- System.out.println(new String(byteBuffer.array(), 0, byteBuffer.limit()));
- //Hello,Java菜鸟程序员
- channel.close();
- fileInputStream.close();
- }
- }
- public class NIOFileChannel {
-
- public static void main(String[] args) throws IOException {
- FileInputStream fileInputStream = new FileInputStream("hello.txt");
- FileOutputStream fileOutputStream = new FileOutputStream("world.txt");
- FileChannel inChannel = fileInputStream.getChannel();
- FileChannel outChannel = fileOutputStream.getChannel();
- ByteBuffer byteBuffer = ByteBuffer.allocate(1);
- while (inChannel.read(byteBuffer) != -1) {
- byteBuffer.flip();
- outChannel.write(byteBuffer);
- //清空重置
- byteBuffer.clear();
- }
- fileOutputStream.close();
- fileInputStream.close();
- }
- }
- public class NIOFileChannel {
-
- public static void main(String[] args) throws IOException {
- FileInputStream fileInputStream = new FileInputStream("hello.txt");
- FileOutputStream fileOutputStream = new FileOutputStream("world.txt");
- FileChannel inChannel = fileInputStream.getChannel();
- FileChannel outChannel = fileOutputStream.getChannel();
- //从哪拷贝,从几开始到几结束 对应的还有transferTo()方法.
- outChannel.transferFrom(inChannel, 0, inChannel.size());
- outChannel.close();
- inChannel.close();
- fileOutputStream.close();
- fileInputStream.close();
- }
- }
NIO 还支持通过多个 Buffer(即 Buffer 数组)完成读写操作,即Scattering(分散)和 Gathering(聚集)。
Scattering(分散):在向缓冲区写入数据时,可以使用 Buffer 数组依次写入,一个 Buffer 数组写满后,继续写入下一个 Buffer 数组。Gathering(聚集):从缓冲区读取数据时,可以依次读取,读完一个 Buffer 再按顺序读取下一个。Netty 的 I/O 线程 NioEventLoop 聚合了 Selector(选择器 / 多路复用器),可以并发处理成百上千个客户端连接。
当线程从某客户端 Socket 通道进行读写时,若没有数据可用,该线程可以进行其他任务。
线程通常将非阻塞 I/O 的空闲时间用于其他通道上执行 I/O 操作,所以单独的线程可以管理多个输入输出通道。
由于读写操作都是非阻塞的,就可以充分提高 I/O 线程的运行效率,避免由于频繁 I/O 阻塞导致的线程挂起。
一个 I/O 线程可以并发处理 N 个客户端连接和读写操作,这从根本上解决了传统同步阻塞 I/O 一连接一线程模型,架构性能、弹性伸缩能力和可靠性都得到极大地提升。
- public abstract class Selector implement Closeable{
-
- public static Selector open(); //得到一个选择器对象
-
- public int select(long timeout); //监控所有注册的通道,当其中的IO操作可以进行时,将对应的selectionkey加入内部集合并返回,参数设置超时时间
-
- public Set
selectionKeys(); //从内部集合中得到所有的SelectionKey -
- }
selector.select()://若未监听到注册管道中有事件,则持续阻塞selector.select(1000)://阻塞 1000 毫秒,1000 毫秒后返回selector.wakeup()://唤醒 selectorselector.selectNow(): //不阻塞,立即返回SelectionKey 中定义了四个操作标志位:OP_READ表示通道中发生读事件;OP_WRITE—表示通道中发生写事件;OP_CONNECT—表示建立连接;OP_ACCEPT—请求新连接。
- public class Server {
-
- public static void main(String[] args) throws IOException {
- //创建serverSocketChannel
- ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
- //绑定端口
- serverSocketChannel.socket().bind(new InetSocketAddress(6666));
- //设置为非阻塞
- serverSocketChannel.configureBlocking(false);
- //得到Selector对象
- try (Selector selector = Selector.open()) {
- //把ServerSocketChannel注册到selector,事件为OP_ACCEPT
- serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
- //如果返回的>0,表示已经获取到关注的事件
- while (selector.select() > 0) {
- Set
selectionKeys = selector.selectedKeys(); - Iterator
iterator = selectionKeys.iterator(); - while (iterator.hasNext()) {
- //获得到一个事件
- SelectionKey next = iterator.next();
- //如果是OP_ACCEPT,表示有新的客户端连接
- if (next.isAcceptable()) {
- //给该客户端生成一个SocketChannel
- SocketChannel accept = serverSocketChannel.accept();
- accept.configureBlocking(false);
- //将当前的socketChannel注册到selector,关注事件为读事件,同时给socket Channel关联一个buffer
- accept.register(selector, SelectionKey.OP_READ,ByteBuffer.allocate(1024));
- System.out.println("获取到一个客户端连接");
- //如果是读事件
- } else if (next.isReadable()) {
- //通过key 反向获取到对应的channel
- SocketChannel channel = (SocketChannel) next.channel();
- //获取到该channel关联的buffer
- ByteBuffer buffer = (ByteBuffer) next.attachment();
- while (channel.read(buffer) != -1) {
- buffer.flip();
- System.out.println(new String(buffer.array(), 0, buffer.limit()));
- buffer.clear();
- }
- }
- iterator.remove();
- }
- }
- }
- }
-
- }
- public class Client {
-
- public static void main(String[] args) throws IOException {
- //得到一个网络通道
- SocketChannel socketChannel = SocketChannel.open();
- //设置为非阻塞
- socketChannel.configureBlocking(false);
- //提供服务器端的IP和端口
- InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
- //连接服务器
- if (!socketChannel.connect(inetSocketAddress)) {
- while (!socketChannel.finishConnect()) {
- System.out.println("连接需要时间,客户端不会阻塞...先去吃个宵夜");
- }
- }
- //连接成功,发送数据
- String str = "hello,Java菜鸟程序员";
- ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
- socketChannel.write(byteBuffer);
- socketChannel.close();
- System.out.println("客户端退出");
- }
-
- }

系列文章持续更新,微信搜一搜「傲骄鹿先生 」,回复【面试】有准备的一线大厂面试资料。