前言:
Java NIO(new inputsteam outputstream)使用通道、缓冲来操作流。它和传统IO的区别在去:1.NIO是面向缓冲、通道的,传统IO是面向流的;2.NIO通道中既可以写又可以读,传统IO只能是单向的;3.NIO可以设置成异步的,传统IO只能是阻塞的。
NIO 的核心类是 Channel(通道)、Buffer(缓冲)、Selector(选择器)。
第一章:Channel
Channel的特点是:既可以从通道中读取数据,又可以写数据到通道;通道的读写可以是异步的;通道的数据不能直接调用,要先读取到一个Buffer中,写入的时候也要通过Buffer。
Channel的实现包括:FileChannel(文件数据读写通道);DatagramChannel(UDP连接数据读写通道);SocketChannel(TCP连接数据读写通道);ServerSocketChannel(监听TCP连接,创建SocketChannel)
测试代码:文件读写
private static void readFile(String name) {
File file = new File(name);
if (!file.exists()) {
System.out.println("没有此文件: " + name);
}
try (FileInputStream inputStream = new FileInputStream(file))
{
FileChannel channel =
inputStream.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(11);
while (channel.read(buffer) != -1) {
buffer.flip();
byte[] bytes = new byte[buffer.limit()];
buffer.get(bytes);
buffer.clear();
System.out.print(new String(bytes, "utf-8"));
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static
void writeFile(String
name) {
try (FileOutputStream outputStream = new FileOutputStream(new File(name))) {
FileChannel channel =
outputStream.getChannel();
ByteBuffer buf;
for (int i = 0; i < 100; i++) {
String str = "test num :
" + i + "\r\n";
buf = Charset.forName("utf-8").encode(str);
channel.write(buf);
}
} catch (IOException e) {
e.printStackTrace();
}
}
|
测试代码:端口监听
private static void testServerSocket() throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(9090));
SocketChannel socketChannel = serverSocketChannel.accept();
ByteBuffer buf = ByteBuffer.allocate(1024);
int read = socketChannel.read(buf);
System.out.println(read);
buf.flip();
byte[] bytes = new byte[buf.limit()];
buf.get(bytes);
buf.clear();
System.out.println(new String(bytes, "utf-8"));
socketChannel.close();
}
|
运行代码,使用 telnet 127.0.0.1 9090 建立连接
第二章:Buffer
Buffer 和IO中的缓存类一样,内部维护了一个数组,Buffer有三个主要的属性:capacity,position,limit和两个状态,读状态和写状态
下图表示Buffer在写状态和读状态的模型
Buffer的用法见代码:
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(12); //初始化buffer,使用charbuffer为例
print("write", buffer); //初始时是写模式,数据从pos写入
String str = "testBuffer";
buffer.put(str.getBytes()); //使用10个字节填充buffer
print("write", buffer);
buffer.flip();//切换成读模式
print("read", buffer);
buffer.get(new byte[3]);//读取三个字节
print("read", buffer);
buffer.compact();//切换成写模式,将没有被读取的数据放到起始位置
print("write", buffer);
buffer.flip();//切换成读模式
print("read", buffer);
buffer.clear();//切换成写模式,直接从最开始写入
print("write", buffer);
}
private static
void print(String
str, Buffer buffer) {
String sb = str + " , pos:
" + buffer.position() +
" , lim: " + buffer.limit()
+
" , cap: " +
buffer.capacity();
System.out.println(sb);
}
|
需要注意的方法是 flip 、compact、clear
当然Buffer还有更多的方法调用比如经典的 mark和reset 可以从源码进行理解学习!
第三章:Scatter/Gather
Java
NIO 支持scatter/gater 操作,用于描述从Channel中读取数据和写入数据到Chann中。分散 (scatter) 可以从Channel中读取数据到多个Buffer中。聚集 (gatter)可以将多个Buffer中的数据写入到Channel中。
代码展示:
String fileName = "test.txt";
try (FileOutputStream
outputStream = new FileOutputStream(new File(fileName))) {
FileChannel channel =
outputStream.getChannel();
ByteBuffer head = ByteBuffer.allocate(4);
ByteBuffer body = ByteBuffer.allocate(12);
head.put("head".getBytes());
body.put("this is
body".getBytes());
head.flip();
body.flip();
ByteBuffer[] message = {head, body};
channel.write(message);
} catch
(IOException
e) {
e.printStackTrace();
}
try (FileInputStream
inputStream = new FileInputStream(new File(fileName))) {
FileChannel channel =
inputStream.getChannel();
ByteBuffer head = ByteBuffer.allocate(4);
ByteBuffer body = ByteBuffer.allocate(12);
ByteBuffer[] message = {head,
body};
//向buffer中写入数据,上一个写完才会写下一个
channel.read(message);
System.out.println(new String(head.array()));
System.out.println(new String(body.array()));
} catch
(IOException
e) {
e.printStackTrace();
}
|
第四章:通道之间的数据传输
在Java NIO中如果两个通道中有一个是FileChannel 那么这两个Channel是可以相互通信的,因为FileChannel 有transferFrom()从另一个通道拷贝数据到自身和tansferTo()拷贝数据到另一个通道中的操作;
代码展示如下:
public static void main(String[] args) throws InterruptedException {
/*测试数据冲socket中写入到文本中
new
Thread(ChannelCopy::copyToFile).start();
Thread.sleep(2000);
new Thread(() -> {
try (Socket socket = new
Socket("127.0.0.1", 9090);
OutputStream outputStream
= socket.getOutputStream();) {
outputStream.write("test copy socket data to
file".getBytes());
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}).start();*/
new Thread(ChannelCopy::copyToSocket).start();
Thread.sleep(2000);
new Thread(() -> {
try (Socket socket = new Socket("127.0.0.1", 9090);
InputStream inputStream =
socket.getInputStream()) {
byte[] bytes = new byte[100];
inputStream.read(bytes);
System.out.println(new String(bytes));
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
//获取一个socket连接
private static SocketChannel getSocket() throws IOException {
ServerSocketChannel channel =
ServerSocketChannel.open();
channel.bind(new InetSocketAddress(9090));
return channel.accept();
}
//从socket中获取数据写入到文本中
private static void copyToFile() {
String fileName = "test.txt";
try (FileOutputStream outputStream = new FileOutputStream(new File(fileName)))
{
SocketChannel socket = getSocket();
FileChannel channel =
outputStream.getChannel();
channel.transferFrom(socket, 0, 100);
} catch (IOException e) {
e.printStackTrace();
}
}
//将数据从文本中写入到socket中
private static void copyToSocket() {
String fileName = "test.txt";
try (FileInputStream outputStream = new FileInputStream(new File(fileName)))
{
SocketChannel socket = getSocket();
FileChannel channel =
outputStream.getChannel();
channel.transferTo(0, 100, socket);
} catch (IOException e) {
e.printStackTrace();
}
}
|
第五章:Selector的使用
Selector
主要用于获取满足状态的Channel进行操作。主要步骤如下:
- 开启一个选择器;
- 开启一个Channel,这个Channel要继承自 SelectableChannel
- 设置Channel的为非阻塞状态
- Channel注册Selector 和指定的状态,状态包含 accept,read,write,connect
- (可选)为Channel绑定特定对象,用于操作
- Selector进行选择,注意超时和唤醒
- Selector 获取满足条件的对象 以SelectedKey的对象集合返回
- 遍历SelectedKey的集合,每个Selected可以获取Channel和Selector
- 通过判断SelectedKey的不同状态,进行对应操作
- Selector 不使用时可以关闭,但不会关闭Channel
代码展示
new Thread(() -> {
try {
//开启一个Selector
Selector sel_ser = Selector.open();
//开启服务器server
ServerSocketChannel server = ServerSocketChannel.open();
//设置Channel为非阻塞状态
server.configureBlocking(false);
server.bind(new InetSocketAddress(9091));
server.register(sel_ser, SelectionKey.OP_ACCEPT);
while (true) {
//监听满足的channel
sel_ser.select();
Iterator<SelectionKey> iterator = sel_ser.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (key.isAcceptable()) {
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
SocketChannel socket = channel.accept();
socket.configureBlocking(false);
socket.register(sel_ser, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
iterator.remove();//移除,不然会引起异常
}
if (key.isWritable()) {
SocketChannel socket = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1000);
buffer.put(("message come from server " + new Date() + "\n").getBytes());
buffer.flip();
socket.write(buffer);
}
if (key.isReadable()) {
SocketChannel socket = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(100);
socket.read(buffer);
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
System.out.println(new String(bytes).trim());
}
}
Thread.sleep(2000);
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}).start();
try {
//创建Selector 注册在两个SocketChannel上
Selector cli_ser = Selector.open();
SocketChannel socket_1 = SocketChannel.open(new InetSocketAddress(9091));
SocketChannel socket_2 = SocketChannel.open(new InetSocketAddress(9091));
socket_1.configureBlocking(false);
socket_2.configureBlocking(false);
socket_1.register(cli_ser, SelectionKey.OP_WRITE | SelectionKey.OP_READ);
socket_2.register(cli_ser, SelectionKey.OP_WRITE | SelectionKey.OP_READ);
//监听Select
while (true) {
cli_ser.select();
Iterator<SelectionKey> iterator = cli_ser.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (key.isWritable()) {
SocketChannel socket = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(100);
buffer.put(("message come from client " + new Date() + "\n").getBytes());
buffer.flip();
socket.write(buffer);
}
if (key.isReadable()) {
SocketChannel socket = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1000);
socket.read(buffer);
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
System.out.println(new String(bytes).trim());
}
}
Thread.sleep(2000);
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
|