Java IO 模型
文章目录
3.5.5 文件copy
public class NIOChannelCopy {
public static void main(String[] args) throws Exception {
String filePath = "files//1.txt";
FileInputStream inputStream = new FileInputStream(new File(filePath));
FileChannel inputStreamChannel = inputStream.getChannel();
FileOutputStream fileOutputStream = new FileOutputStream("files//2.txt");
FileChannel outputStreamChannel = fileOutputStream.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(32);
while (true){
// 清空缓存
buffer.clear();
// 读取数据到缓存
int read = inputStreamChannel.read(buffer);
if (read == -1){
break;
}
// 读写反转
buffer.flip();
// 将缓存中的数据写到 file
outputStreamChannel.write(buffer);
}
outputStreamChannel.close();
fileOutputStream.close();
inputStreamChannel.close();
inputStream.close();
}
}
public static void main(String[] args) throws Exception{
FileInputStream fileInputStream = new FileInputStream(new File("files//1.txt"));
FileChannel inputStreamChannel = fileInputStream.getChannel();
FileOutputStream fileOutputStream = new FileOutputStream("files//2.txt");
FileChannel outputStreamChannel = fileOutputStream.getChannel();
outputStreamChannel.transferFrom(inputStreamChannel,0,inputStreamChannel.size());
outputStreamChannel.close();
fileOutputStream.close();
inputStreamChannel.close();
fileInputStream.close();
}
3.5.6 Buffer 补充
Buffer 读写类型要一致
public class BufferType {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(32);
buffer.put("123".getBytes(StandardCharsets.UTF_8));
buffer.putInt(1);
buffer.putChar('3');
buffer.flip();
System.out.println(buffer.getInt());
System.out.println(buffer.getInt());
System.out.println(buffer.getInt());
// 825373440
// 256
// Exception in thread "main" java.nio.BufferUnderflowException
// at java.base/java.nio.Buffer.nextGetIndex(Buffer.java:651)
// at java.base/java.nio.HeapByteBuffer.getInt(HeapByteBuffer.java:402)
// at com.bak.BufferType.main(BufferType.java:23)
//
}
}
MappedByteBuffer 直接在内存中操作文件
public class NIOMappedByteBuffer {
public static void main(String[] args) throws Exception{
RandomAccessFile randomAccessFile = new RandomAccessFile("files//1.txt","rw");
FileChannel channel = randomAccessFile.getChannel();
MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, channel.size());
mappedByteBuffer.put(0,(byte) 'Q');
channel.close();
randomAccessFile.close();
}
}
NIO 还支持通过多个 Buffer (即 Buffer 数组) 完成读写操作,即 Scattering和 Gathering
public class NIOScatteringAndGathering {
public static void main(String[] args)throws Exception {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9099));
ByteBuffer[] buffers = new ByteBuffer[2];
buffers[0] = ByteBuffer.allocate(3);
buffers[1] = ByteBuffer.allocate(2);
SocketChannel socketChannel = serverSocketChannel.accept();
int messagelen = 5;
while (true){
int readLen = 0;
while (readLen < messagelen){
long read = socketChannel.read(buffers);
readLen+=read;
System.out.println("readLen = " + readLen);
}
// 将所有的buffer进行flip
Arrays.stream(buffers).map(r -> r.flip());
long writeLen = 0;
while (writeLen < messagelen){
long write = socketChannel.write(buffers);
writeLen+=write;
System.out.println("w = " + writeLen);
}
Arrays.stream(buffers).map(r -> r.clear());
}
}
}
3.6 选择器(Selector)
3.6.1 基本介绍
1.一个线程通过 selector 处理多个客户端
2.selector 能够检测其注册通道上是否发生事件,如果有事件则会根据不同的事件做相应的处理
3.通过 selector 在不同的通道上切换,就达到使用一个线程处理多个客户端
4.只有注册的通道真正发生读写请求时,才会进行读写
5.使用单线程处理多个客户端,避免了多线之间的切换开销
3.6.2 NIO Socket demo
public class NIOSelectorServer {
public static void main(String[] args) throws Exception{
// 获取一个 selector
Selector selector = Selector.open();
// 服务器启动 socket
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1",9099));
// 设置为非阻塞模式
serverSocketChannel.configureBlocking(false);
// 将 channel 注册到 selector
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("selector 上注册的所有 channel size = " + selector.keys().size());
while (true){
// channel 无事件
if (selector.select(1000) == 0){
System.out.println("server 等待了 1s,无连接...");
continue;
}
// channel 有事件
// 获取所有有事件的 channel
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
// 遍历所有 channel 根据 channel 的事件类型进行处理
while (iterator.hasNext()){
SelectionKey key = iterator.next();
// 连接事件
if (key.isAcceptable()){
// 新接入的连接,为其创建一个 channel
SocketChannel socketChannel = serverSocketChannel.accept();
System.out.println("client 端 port = " + socketChannel.getRemoteAddress() + " 连接成功,并为其生成一个 channel = " + socketChannel.hashCode());
// 将 channel 设置为非阻塞模式
socketChannel.configureBlocking(false);
// 将 socketChannel 注册到 selector,并将通道关注事件设置为 OP_READ 同时为通道创建一个 buffer
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
// 读事件
if (key.isReadable()){
// 从 buffer 中读取数据
// 获取 key 关联的 channel
SocketChannel channel = (SocketChannel) key.channel();
// 获取 key 关联的 buffer
ByteBuffer buffer = (ByteBuffer) key.attachment();
channel.read(buffer);
System.out.println("from client port = " + channel.getRemoteAddress() + " 读取到的数据:" + new String(buffer.array()));
}
// 移除处理完的 channel
iterator.remove();
}
}
}
}
public class NIOSelectorClient {
public static void main(String[] args) throws Exception{
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 9099);
if (!socketChannel.connect(inetSocketAddress)){
while (!socketChannel.finishConnect()){
System.out.println("client端正在连接server端....");
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
System.out.println("client 连接 server 端成功.....");
String data = "hello,netty";
ByteBuffer buffer = ByteBuffer.wrap(data.getBytes(StandardCharsets.UTF_8));
// 将数据写入 buffer 发送到 socket
socketChannel.write(buffer);
System.in.read();
}
}
3.6.3 Selector Api
public static Selector open() 获取一个 selector 对象
public abstract int selectNow() 立即返回 不阻塞
public abstract int select() 阻塞
public abstract int select(long timeout) 阻塞 等待 xxx 毫秒
监控所有注册的通道,当其有 IO 操作时,将返回对应的 SelectionKey
public abstract Set keys() 获取 selector 上注册的所有 channel 的
public abstract Set selectedKeys() 获取 selector 上有事件发生的 channel
public abstract Selector wakeup() 唤醒 selector
3.6.4 SelectionKey
SelectionKey 表示 Selector 和 网络通道的注册关系
OP_READ = 1 | 读操作 |
OP_WRITE = 4 | 写操作 |
OP_CONNECT = 8 | 建立连接 |
OP_ACCEPT = 16 | 有新的连接 |
public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << 2;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_ACCEPT = 1 << 4;
public abstract Selector selector() 得到与之关联的 Selector
public abstract SelectableChannel channel() Channel
public final Object attachment() 得到与之关联的共享数据
public abstract int interestOps()
public abstract SelectionKey interestOps(int ops) 设置或修改监听事件
public final boolean isAcceptable() 是否可accept
public final boolean isReadable() 是否可读
public final boolean isWritable() 是否可写
3.6.5 ServerSocketChannel
ServerSocketChannel 在服务器监听新的客户端 socket 连接
public static ServerSocketChannel open() 得到一个ServerSocketChannel通道
public abstract ServerSocketChannel bind(SocketAddress local, int backlog)
public final ServerSocketChannel bind(SocketAddress local) 设置服务器端端口 号
public final SelectableChannel configureBlocking(boolean block) 设置阻塞或非阻塞模式,false表示采用非阻塞
public abstract SocketChannel accept() 接受一个连接,返回代表这个连接的通道对 象
public final SelectionKey register(Selector sel, int ops) 注册一个选择器并设置 监听事件
3.6.6 SocketChannel
SocketChannel,网络IO通道,负责读写操作。NIO把缓冲区的数据写入通道,或者把通道里的数据读到缓冲区
public static SocketChannel open(SocketAddress remote) 得到一个SocketChannel通道
public final SelectableChannel configureBlocking(boolean block) 设置阻塞或非阻塞 模式,取值false表示采用非阻塞模式
public abstract boolean connect(SocketAddress remote) 连接服务器
public abstract boolean finishConnect() 如果上面的方法连接失败,接下来就要通过该方法 完成连接操作
public abstract int write(ByteBuffer src) 往通道里写数据
public abstract int read(ByteBuffer dst) 从通道里读数据
public final SelectionKey register(Selector sel, int ops,Object att) 注册一个选择器并 设置监听事件,最后一个参数可以设置共享数据
public final void close() 关闭通道
3.6.7 NIO Socket demo 分析
1.服务器端通过 ServerSocketChannel 绑定 网络通信地址
2.ServerSocketChannel 注册到 selector 上,并设置关注事件
3.selector 通过 select() 方法监听 ,返回有事件发生的通道数
4.根据 channel 的事件做,指定的处理
5.如果是 accept 事件,为其初始化,创建 channel buffer 并将 channel 注册到 selector