NIO

non-blocking io —— 非阻塞IO

NIO 三大组件

  • channel
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// FileChannel 
// 1.输入输出流 2.RandomAccessFile
try(FileChannel channel = new FileIuputStream("xxx.txt").getChannel()){
// 准备缓冲区
ByteBuffer buffer = ByteBuffer.allocater(10);
while(true){
// 从channel 读取数据,向 buffer 写入
if(channel.read(buffer) == -1) break;
// 打印 buffer 内容
buffer.flip(); // 切换读模式
while(buffer.hasRemaining()){
byte b = buffer.get();
log.debug("字节{}",(char)b)
}
buffer.clear(); // 切换写模式
}
}catch (IOException e){

}
  • buffer

  • selector

ByteBuffer

  1. ByteBuffer 使用

    • 向buffer写入数据,例如调用 channel.read(buffer)
    • 调用flip()切换至读模式
    • 从buffer读取数据,例如调用buffer.get()
    • 调用clear()或compact()切换至写模式
    • 重复1~4步骤
  2. ByteBuffer 结构

    • capacity 容量
    • position 起始位置
    • limit 限制大小
  3. ByteBuffer 方法

    • 分配空间

      • allocate() -java 堆内存,读写效率较低,受GC影响

      • allocateDirect() -直接内存,读写效率高(少一次拷贝),不受GC影响,分配效率低

    • 向buffer写入数据

      • 调用channel.read()

      • 调用buffer.put()

    • 从buffer读取数据

      • 调用channel.write()

      • 调用buffer.get()

        • get()会让position指针后移,重复读取:调用rewind()重置position 为0或者get(i)获取索引i的内容
        • get(i)不会改变position位置
        • mark & reset:mark记录position位置,reset回到mark记录的位置
    • 字符串转ByteBuffer

    1
    2
    3
    4
    5
    6
    7
    // 1.字符串str转为 ByteBuffer
    ByteBuffer buffer1 = ByteBuffer.allocate(16);
    buffer1.put(str.getBytes());
    // 2.Charset
    ByteBuffer buffer2 = StandardCharsets.UTF_8.encode(str);
    // 3.wrap 字节数组跟ByteBuffer转换
    ByteBuffer buffer3 = ByteBuffer.wrap(str.getBytes());
    • ByteBuffer转字符串
    1
    2
    3
    4
    5
    // ByteBuffer转为字符串
    buffer1.flip();
    str = StandardCharsets.UTF_8.decode(buffer1).toString();
    // Charset类型:会自动重置position,不用切换读模式
    str = StandardCharsets.UTF_8.decode(buffer2).toString();

文件编程

FileChannel

FileChannel只能工作在阻塞模式下

  1. 获取

不能直接打开FileChannel,必须通过FilelnputStream、FileOutputStream或者RandomAccessFile来获取FileChannel,它们都有getChannel方法

  • 通过FilelnputStream获取的channel只能读
  • 通过FileOutputStream获取的channel只能写
  • 通过RandomAccessFile是否能读写根据构造RandomAccessFile时的读写模式**(r / w)**决定
  1. 读取

会从channel读取数据填充ByteBuffer,返回值表示读到了多少字节,-1表示到达了文件的末尾

1
int len = channel.read(buffer);
  1. 写入

写入的正确姿势如下,SocketChannel

1
2
3
4
5
6
7
ByteBuffer buffer =...
buffer.put(...);/存入数据
buffer.fIip(O;//切换读模式

while(buffer.hasRemaining()){
channel.write(buffer);
}

在while中调用channel.write是因为write方法并不能保证一次将buffer中的内容全部写入channel

  1. 关闭

channel必须关闭,不过调用了FilelnputStream、FileOutputStream或者RandomAccessFile的close方法会间接地调用channel的close方法

  1. 位置

获取当前位置

1
long pos = channel.position();

设置当前位置

1
2
long newPos = ...;
channel.position(newPos);

设置当前位置时,如果设置为文件的末尾

  • 这时读取会返回-1
  • 这时写入,会追加内容,但要注意如果p0sit0超过了文件末尾,再写入时在新内容和原末尾之间会有空洞(00)
  1. 大小

使用size方法获取文件的大小

  1. 强制写入

操作系统出于性能的考虑,会将数据缓存,不是立刻写入磁盘。可以调用force(true)方法将文件内容和元数据(文件的权限等信息)立刻写入磁盘

两个Channel传输数据

transferTo()

Path

Files

  • 拷贝文件 (跟transferTo一样效率较高)
1
Files.copy(source,target);

walkFileTree();

网络编程

阻塞模式 vs 非阻塞模式

阻塞方法,线程停止运行

  • 阻塞
  • 非阻塞
  • 多路复用

单线程可以配合Selector完成对多个Channel可读写事件的监控,这称之为多路复用

  • 多路复用仅针对网络IO、普通文件IO没法利用多路复用

  • 如果不用Selector的非阻塞模式,线程大部分时间都在做无用功,而Selector能够保证:

    有可连接事件时才去连接

    有可读事件才去读取

    有可写事件才去写入

限于网络传输能力,Channel未必时时可写,一旦Channel可写,会触发Selector的可写事件

Selector

  • accept -会在有连接请求时触发

  • connect -是客户端,连接建立后触发

  • read -可读事件

  • write -可写事件

ServerSocketChannel是用来连接的,SocketChannel是用来处理客户端发的消息的

select():没有事件发生,阻塞,有事件,线程才会恢复运行。在事件未处理时,它不会阻塞。事件发生后要么处理要么取消。selectionKey .cancel()取消

Selector 最大的好处是可以通过阻塞方法select()拿到SelectionKey 判断时间类型从而做出不同的逻辑处理


Netty