1、缓冲区的 API
要彻底理解缓冲区,必须知道缓冲区的四个属性,mark,position,limit,capacity,只需要跑一遍代码就知道了。
(1)分配一定大小的缓冲区
- //1.分配一个指定大小的缓冲区
- ByteBuffer buffer = ByteBuffer.allocate(10);
- System.out.println("———alocate");
- System.out.println("position:" + buffer.position());
- System.out.println("limit:" + buffer.limit());
- System.out.println("capacity:" + buffer.capacity());
运行结果:
- ———alocate———–
- position:0
- limit:10
- capacity:10
这里我们分配了 10 个字节的缓冲区,也就是在 ByteBuffer 的 final byte[] hb; 属性上开辟了 10 个字节的空间。
所以容量 capacity 为 10 , limit 可读写数据的最大位置 也是 10 ,position 为可以操作数据的位置为 0 。
(2)往缓冲区写数据
- // 2.写入数据到缓冲区
- String str = "abcde";
- System.out.println("————put————");
- buffer.put(str.getBytes(StandardCharsets.UTF_8));
- System.out.println("position:" + buffer.position());
- System.out.println("limit:" + buffer.limit());
- System.out.println("capacity:" + buffer.capacity());
运行结果:
- ————put————
- position:5
- limit:10
- capacity:10
这里我们往缓冲区写了 5 个字节的数据,那么 capacity 和 limit 都还是10,但是 position 为 5 了,因为前面已经写入了 5 个了
(3)切换成读数据的模式
- // 3.切换成读数据的模式
- buffer.flip();
- System.out.println("————flip————");
- System.out.println("position:" + buffer.position());
- System.out.println("limit:" + buffer.limit());
- System.out.println("capacity:" + buffer.capacity());
那我们现在想从缓冲区读取一些数据出来,就需要切换成 flip 模式,flip 会改变一些属性的值
运行结果:
- ————flip————
- position:0
- limit:5
- capacity:10
flip 会改变 position 的值为 0 ,并且 limit 为5,表示我要从头开始读,并且只能读到 5 的位置
(4)读取一些数据
- // 4. 读取数据
- System.out.println("————get————");
- byte[] dest = new byte[buffer.limit()];
- buffer.get(dest);
- System.out.println(new String(dest,0,dest.length));
- System.out.println("position:" + buffer.position());
- System.out.println("limit:" + buffer.limit());
- System.out.println("capacity:" + buffer.capacity());
运行结果:
- ————get————
- abcde
- position:5
- limit:5
- capacity:10
读取了数据之后,position 就变成 5 了,表示我已经读取到 5 了。
(5)重复读
- //5.rewind()
- buffer.rewind();
- System.out.println("————rewind————");
- System.out.println("position:" + buffer.position());
- System.out.println("limit:" + buffer.limit());
- System.out.println("capacity:" + buffer.capacity());
运行结果:
- ————rewind————
- position:0
- limit:5
- capacity:10
rewind 表示重复读取 buffer 里面的数据
(6)清除数据
- //6.clear()
- buffer.clear();
- System.out.println("————clear————");
- System.out.println("position:" + buffer.position());
- System.out.println("limit:" + buffer.limit());
- System.out.println("capacity:" + buffer.capacity());
运行结果:
- ————clear————
- position:0
- limit:10
- capacity:10
clear() 之后,position 回到了 0 ,limit 回到了 10,又可以重头开始写数据了,能写 10 个字节。
但是要注意的是,缓冲里面的数据并没有清空掉,数据还在里面,处于被“遗忘”状态。这几个指针回到了最初的状态。
(7)标记
这是第四个属性:mark。
mark 可以记录 position 的位置。可以通过 reset() 方法回到 mark 的位置。
- @Test
- public void test2() {
- // 分配 10 个字节
- String str = "abcde";
- ByteBuffer buffer = ByteBuffer.allocate(10);
- buffer.put(str.getBytes(StandardCharsets.UTF_8));
- // 切换到读模式,读取 2 个字节
- buffer.flip();
- byte[] dest = new byte[buffer.limit()];
- buffer.get(dest, 0, 2);
- System.out.println(new String(dest, 0, 2));
- System.out.println(buffer.position());
- // mark 一下记录当前位置
- buffer.mark();
- // 又读取两个字节
- buffer.get(dest, 2, 2);
- System.out.println(new String(dest, 2, 2));
- System.out.println(buffer.position());
- // reset,回到 mark 的位置
- buffer.reset();
- System.out.println(buffer.position());
- }
- 执行结果:
- “`tex
- ab
- 2
- cd
- 4
- 2
2、使用通道、缓冲区、选择器完成一个网络程序
(1)服务端
- @Test
- public void testServer() throws IOException {
- ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
- serverSocketChannel.configureBlocking(false);
- serverSocketChannel.bind(new InetSocketAddress(8989));
- Selector selector = Selector.open();
- serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
- while (selector.select() > 0) {
- Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
- while (iterator.hasNext()) {
- SelectionKey key = iterator.next();
- if (key.isAcceptable()) {
- SocketChannel socketChannel = serverSocketChannel.accept();
- socketChannel.configureBlocking(false);
- socketChannel.register(selector, SelectionKey.OP_READ);
- } else if (key.isReadable()) {
- SocketChannel channel = (SocketChannel) key.channel();
- ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
- int len = 0;
- while ((len = channel.read(byteBuffer)) > 0) {
- byteBuffer.flip();
- System.out.println(new String(byteBuffer.array(), 0, len));
- byteBuffer.clear();
- }
- }
- }
- iterator.remove();
- }
- }
1、首先使用 ServerSocketChannel.open(),打开一个通道,设置成非阻塞模式;
2、绑定到 8989 端口上;
3、把通道注册到选择器上;
4、while 循环,选择器上是否有事件,如果事件是客户端的连接事件,则打开一个 SocketChannel,注册成非阻塞模式,并且往选择器上注册一个读数据的事件;
5、当客户端发送数据过来的时候,就可以打开一个通道,读取缓冲区上的数据;
6、并且此时,服务端是可以同时接受多个客户端的请求的。
(2)客户端
- @Test
- public void testClient() throws IOException {
- SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8989));
- socketChannel.configureBlocking(false);
- ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
- byteBuffer.put(new Date().toString().getBytes(StandardCharsets.UTF_8));
- byteBuffer.flip();
- socketChannel.write(byteBuffer);
- byteBuffer.clear();
- socketChannel.close();
- }
1、客户端打开一个 SocketChannel,配置成非阻塞模式;
2、使用 ByteBuffer 发送数据(注意发送之前,要 flip);
3、关闭通道。