Selector、Channel和Buffer的关系

- 每个Channel都会对应一个Buffer
- Selector对应一个线程,一个线程对应多个channel(连接),channel注册到selector
- 程序切换到哪个channel 是由事件决定的,Event是一个重要概念
- Selector会根据不同的事件,在各个通道上切换
- Buffer就是一个内存块,底层是一个数组
- 数据的读取/写入是通过Buffer,BIO中要么是输入流或者输出流,不能双向的,但是NIO的Buffer是可以读和写,需要flip方法切换
- Channel是双向的,可以返回底层操作系统的情况,比如Linux 底层的操作系统通道就是双向的
Buffer
基础代码
import java.nio.IntBuffer;
/**
* 举例说明Buffer使用
*/
public class BasicBuffer {
public static void main(String[] args) {
// 创建一个Buffer,大小为5,即可以存在5个int
IntBuffer intBuffer = IntBuffer.allocate(5);
// 向Buffer 存数据
for (int i = 0; i < intBuffer.capacity(); i++) {
intBuffer.put(i * 2);
}
// 从Buffer读取数据
// 将buffer转换,读写切换
intBuffer.flip();
while(intBuffer.hasRemaining()) {
System.out.println(intBuffer.get());
}
}
}
缓冲区(Buffer): 缓冲区本质上是一个可以读写数据的内存块,可以理解成一个容器对象(含数组),该对象提供了一组方法,可以更轻松得使用内存块,缓冲区对象内置一些机制,能够跟踪和记录缓冲区的状态变化情况。 Channel 提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由Buffer
Buffer类及其子类
1. Buffer是一个顶级父类,是一个抽象类

2. Buffer类定义了所有的缓冲区都具有的四个属性来提供关于其包含的数据元素的信息
| 属性 | 描述 |
| capacity | 容量,即可以容纳的最大数据量;在缓冲区创建时被设定并且不能改变 |
| limit | 表示缓冲区的当前终点,不能对缓冲区超过极限的位置进行读写操作。且极限是可以修改的 |
| position | 位置,下一个要被读或写的元素的索引,每次读写缓冲区数据时都会改变该值,为下一次读写做准备 |
| mark | 标记 |
执行 allocate 初始化后: position指向0, limit 和 capacity 置为5

执行flip反转后: poostion 数值会给予 limit, 表示最大读取位置
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}

3. Buffer类相关方法
| 方法名 | 作用 |
| public final int capacity() | 返回此缓冲区的容量 |
| public final int position() | 返回此缓冲区的位置 |
| public final Buffer position(int newPosition) | 返回此缓冲区的位置 |
| public final int limit() | 返回此缓冲区的限制 |
| public final Buffer limit(int newLimit) | 设置此缓冲区的限制 |
| public final Buffer mark() | 在此缓冲区的位置设置标标记 |
| public final Buffer reset() | 将此缓冲区的位置重置为以前标记的位置 |
| public final Buffer clear() | 清楚此缓冲区,即将各个标记恢复到初始状态,但是数据并没有真正擦除 |
| public final Buffer flip() | 反转此缓冲区 |
| public final Buffer rewind() | 重绕此缓冲区 |
| public final int remaining() | 返回当前位置与限制之间的元素数 |
| public final boolean hasRemaining() | 告知在当前位置和限制之间是否有元素 |
| public abstract boolean isReadOnly() | 告知此缓冲区是否为只读缓冲区 |
| public abstract boolean hasArray() | 告知此缓冲区是否具有可访问的底层实现数组 |
| public abstract Object array() | 返回此缓冲区的底层实现数组 |
| public abstract int arrayOffset() | 返回此缓冲区的底层实现数组中第一个缓冲区元素的偏移量 |
| public abstract boolean isDirect() | 告知此缓冲区是否为直接缓冲区 |
4. 每个基本数据类型(除boolean外),都有一个Buffer类型预置对应,最常用的是ByteBuffer类
| 方法名 | 作用 |
| public static ByteBuffer allocateDirect(int capacity) | 创建直接缓冲区 |
| public static ByteBuffer allocate(int capacity) | 设置缓冲区的初始容量 |
| public static ByteBuffer wrap(byte[] array,int offset, int length) | 构造初始化位置offset和上界length的缓冲区 |
| public static ByteBuffer wrap(byte[] array) | 把一个数组放到缓冲区中使用 |
| public abstract byte get() | 从当前位置position上get, get之后,position会自动+1 |
| public abstract ByteBuffer put(byte b) | 从当前位置上put, put之后,position会自动+1 |
| public abstract byte get(int index) | 从绝对位置get |
| public abstract ByteBuffer put(int index, byte b) | 从绝对位置上put |
注意事项:
- ByteBuffer 支持类型化 put 和 get,put 放入什么类型,get 就应该使用相应的数据类型来取出,否则会造成乱码或 BufferUnderFlowException
import java.nio.ByteBuffer;
public class NIOByteBufferPutGet {
public static void main(String[] args) {
// 创建一个Buffer
ByteBuffer buffer = ByteBuffer.allocate(64);
// 类型化方式放入数据
buffer.putInt(100);
buffer.putLong(9);
buffer.putChar('学');
buffer.putShort((short) 4);
// 取出
buffer.flip();
System.out.println(buffer.getShort());
System.out.println(buffer.getInt());
System.out.println(buffer.getLong());
// 内存溢出
System.out.println(buffer.getLong());
}
}
普通的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 < 64; i++) {
buffer.put((byte) i);
}
// 读取
buffer.flip();
// 得到一个只读的buffer
ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
System.out.println(readOnlyBuffer.getClass());
//读取
while (readOnlyBuffer.hasRemaining()) {
System.out.println(readOnlyBuffer.get());
}
// ReadOnlyBufferException
readOnlyBuffer.put((byte) 9527);
}
}
NIO 还提供了MappedByteBuffer: 可以让文件直接在内存(堆外的内存)中进行修改,而如何同步到文件由NIO来完成
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
/**
* 可以让文件直接在内存(堆外内存)中修改,操作系统不需要拷贝一次
* @author doubily
*/
public class NIOMappedByteBuffer {
public static void main(String[] args) throws IOException {
RandomAccessFile randomAccessFile = new RandomAccessFile("D:/file.txt", "rw");
// 获取对应的通道
FileChannel channel = randomAccessFile.getChannel();
// READ_WRITE 使用的读写模式
// 0: 可以直接修改的起始位置
// 5: 映射内存的大小,即将文件的多少个字节映射到内存
// 可以直接修改的范围就是0<= x < 5
// 实际类型 DirectByteBuffer
MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);
mappedByteBuffer.put(0, (byte) '9');
mappedByteBuffer.put(1, (byte) '5');
mappedByteBuffer.put(2, (byte) '2');
mappedByteBuffer.put(3, (byte) '7');
randomAccessFile.close();
System.out.println("修改成功");
}
}
NIO 支持多个Buffer 完成读写操作 : Scattering 和 Gatering
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
/**
* @author doubily
* Scattering: 将数据写入buffer时, 可以采用Buffer数组,依次写入[分散]
* Gathering: 从Buffer读取数据时,可以采用Buffer数组,依次读
*/
public class ScatteringGathering {
public static void main(String[] args) throws IOException {
// 使用ServerSocketChannel 和 SocketChannel 网络
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
InetSocketAddress inetSocketAddress = new InetSocketAddress(9527);
// 绑定端口到socket,并启动
serverSocketChannel.socket().bind(inetSocketAddress);
// 创建buffer数组
ByteBuffer[] byteBuffers = new ByteBuffer[2];
byteBuffers[0] = ByteBuffer.allocate(5);
byteBuffers[1] = ByteBuffer.allocate(3);
// 等待客户端连接(telnet)
SocketChannel socketChannel = serverSocketChannel.accept();
// 假定从客户端接收8个字节
int messageLength = 8;
// 循环的读取
while(true) {
int byteRead = 0;
while (byteRead < messageLength) {
long l = socketChannel.read(byteBuffers);
byteRead += l;
System.out.println("byteRead= " + byteRead);
// 使用流打印,看看当前的这个Buffer的position和limit
Arrays.stream(byteBuffers)
.map(buffer-> "position="+ buffer.position() + ", limit=" + buffer.limit())
.forEach(System.out::println);
}
// 将所有的buffer进行flip
Arrays.asList(byteBuffers).forEach(Buffer::flip);
// 将数据读出显示到客户端
long byteWrite = 0;
while (byteWrite < messageLength) {
long l = socketChannel.write(byteBuffers);
byteWrite += l;
}
// 将所有的buffer,进行clear
Arrays.asList(byteBuffers).forEach(Buffer::clear);
System.out.println("byteRead:=" + byteRead + " byteWrite=" + byteWrite + ", messageLength" + messageLength);
}
}
}
执行效果图:



2207

被折叠的 条评论
为什么被折叠?



