Java NIO
Java BIO
Java的BIO中的B值得是blocking,意思是阻塞。BIO是同步非阻塞的IO模型,当线程连接客户端时,若客户端没有发送新的数据,该线程就会阻塞,无法进行其他操作。因此一个服务端的线程只能处理一个客户端的连接,也就是说每出现一个客户端连接时,服务端就需要新建一个线程来处理该连接,当连接数过多时,就会造成线程过多使得资源占用过大的情况。
BIO测试代码:
public static void main(String[] args) throws IOException {
ExecutorService executorService = Executors.newCachedThreadPool();
ServerSocket serverSocket = new ServerSocket(2333);
System.out.println("服务器启动...");
while (true) {
System.out.println("等待连接...");
Socket socket = serverSocket.accept();
System.out.println("一个客户端链接...");
// 收到信息新建进程处理客户端发来的信息
executorService.execute(() -> {
byte[] bytes = new byte[1024];
try (InputStream inputStream = socket.getInputStream()) {
while (true) {
int read = inputStream.read(bytes);
if (read != -1) {
System.out.println(new String(bytes, 0, read));
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
System.out.println("关闭链接");
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
java NIO
Java NIO 是同步非阻塞的,即线程未收到消息时并不会像BIO一样阻塞,而可以去做其他事情。NIO服务端通过Channel通道和客户端连接,一个线程通过选择器管理多个通道,进而可以做到一个线程管理多个连接,使得在连接数目很大的时候性能大大强于BIO。
Java NIO是面向缓冲区的,缓冲区本质上是可以读写的内存块,NIO读写文件必须经过Buffer。
Buffer有三个需要了解的属性:
名称 | 意义 |
---|---|
postion | 缓冲区读写操作的指针,操作一次position+1,相当于下一次操作位置的下标 |
limit | 对缓冲区存取数据的限制,读写操作不能超过该限制,可以修改但不能超过limit |
capacity | Buffer的容量大小,创建时确定,存储数据大小不能超过该值 |
public static void main(String[] args) {
// 可以存放5个int类型的变量
IntBuffer intBuffer = IntBuffer.allocate(5);
// 在buffer中存放数据
IntStream.rangeClosed(0, 4).forEach(intBuffer::put);
// 读写切换
intBuffer.flip();
// 读出数据
while (intBuffer.hasRemaining()) {
System.out.println(intBuffer.get());
}
}
Buffer缓冲
Buffer的flip()
操作为读写转换,在执行该函数时,limit置为原来position的值,position置为0,从而达到读写切换的目的:
Channel通道
NIO的Channel类似于流,但可以同时进行读写操作,即可以从Buffer中读取数据,也可以写入数据到Buffer中:
/**
* 写入数据到文件
*/
public static void write() {
String msg = "Hello world!";
try (FileOutputStream fileOutputStream = new FileOutputStream("src\\main\\java\\cn\\bobasyu\\nio\\text.txt")) {
FileChannel fileChannel = fileOutputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put(msg.getBytes());
byteBuffer.flip();
fileChannel.write(byteBuffer);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 从文件读取数据
*/
public static void read() {
File file = new File("src\\main\\java\\cn\\bobasyu\\nio\\text.txt");
try (FileInputStream fileInputStream = new FileInputStream(file)) {
FileChannel fileChannel = fileInputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
fileChannel.read(byteBuffer);
String msg = new String(byteBuffer.array());
System.out.println(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
同时,Channel通道还支持复制操作:
/**
* 复制文件
*/
public static void copy() {
try (FileInputStream fileInputStream = new FileInputStream("src\\main\\java\\cn\\bobasyu\\nio\\text.txt");
FileOutputStream fileOutputStream = new FileOutputStream("src\\main\\java\\cn\\bobasyu\\nio\\text2.txt")) {
FileChannel fileChannel = fileInputStream.getChannel();
FileChannel fileChannel2 = fileOutputStream.getChannel();
fileChannel2.transferFrom(fileChannel, 0, fileChannel.size());
} catch (IOException e) {
e.printStackTrace();
}
}
Selector选择器
Java NIO一个线程通过Selector选择器管理多个Channel从而实现一个线程与多个客户端连接。
Selector可以监听四种事件:
Connect | SelectionKey.OP_CONNECT | selectionKey.isConnectable() |
Accept | SelectionKey.OP_ACCEPT | selectionKey.isAcceptable() |
Read | SelectionKey.OP_READ | selectionKey.isReadable() |
Write | SelectionKey.OP_WRITE | selectionKey.isWritable() |
Selector部分常用API:
名称 | 内容 |
---|---|
open() | 得到一个Selector对象 |
selectedKeys() | 得到所有的SelectionKey, 每一个Channe对应一个SelectionKey |
wakeUp() | 调用后使得阻塞的select()方法立即返回 |
select() | 阻塞到有注册的事件就绪为止 |
select(long timeout) | 在超时时间内阻塞到有注册的事件就绪为止 |
服务端样例代码:
public static void main(String[] args) throws Exception {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
Selector selector = Selector.open();
serverSocketChannel.socket().bind(new InetSocketAddress(8888));
// 设置为非阻塞
serverSocketChannel.configureBlocking(false);
// 把serverSocketChannel注册到Selector,关心事件为OP_ACCEPT
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
// 若没有事件发生
if (selector.select(1000) == 0) {
System.out.println("服务器一秒钟内无事发生...");
continue;
}
// 若已经获取到关注的事件
// 通过selectedKeys()反向获取通道
Set<SelectionKey> selectionKeySet = selector.selectedKeys();
Iterator<SelectionKey> selectionKeyIterator = selectionKeySet.iterator();
if (selectionKeyIterator.hasNext()) {
SelectionKey selectionKey = selectionKeyIterator.next();
// 如果是OP_ACCEPT事件, 即客户端已连接
if (selectionKey.isAcceptable()) {
System.out.println("客户端连接成功...");
// 获取该客户端的socketChannel
SocketChannel socketChannel = serverSocketChannel.accept();
// 将socketChannel 注册到Selector上, 关注事件为OP_READ, 并关联一个Buffer
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
// 如果是OP_READ事件,即收到数据
if (selectionKey.isReadable()) {
// 通过key获取channel
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
// 获取channel所关联的Buffer
ByteBuffer byteBuffer = (ByteBuffer) selectionKey.attachment();
socketChannel.read(byteBuffer);
System.out.println("客户端:" + new String(byteBuffer.array()));
}
// 手动一处SelectionKey, 防止重复操作
selectionKeyIterator.remove();
}
}
}
客户端:
public class NIOClient {
public static void main(String[] args) throws Exception {
SocketChannel socketChannel = SocketChannel.open();
// 设置非阻塞
socketChannel.configureBlocking(false);
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 8888);
if (!socketChannel.connect(inetSocketAddress)) {
while (!socketChannel.finishConnect()) {
System.out.println("连接中,可以进行其他操作...");
}
System.out.println("连接成功...");
Scanner scanner = new Scanner(System.in);
String msg = scanner.next();
// 根据字符串大小自动生成buffer大小
ByteBuffer byteBuffer = ByteBuffer.wrap(msg.getBytes());
// 发送数据,将buffer数据写入channel
socketChannel.write(byteBuffer);
}
}
}