IO

1、文件IO


JAVA-IO.png

使用 Off Heap 堆外内存,减少了堆内向堆外拷贝过程,效率要高一些,

堆内:JVM 堆内内存。

堆外:JVM堆外,JAVA 进程堆内。

mapped映射:是 mmap 调用的一个进程和内核共享的内存区域,且这个内存区域是 PageCache 到文件的映射。

性能表现 mapped > off heap > on heep

Java 普通IO,每次调用写操作,都会向 OS PageCache 溢写,而 Buffered IO(8KB),向其写入时,仅写入到 JVM 内存,知道写满之后,才会溢写到 PageCache。

对比来看,Buffered 少了很多 system callint 0x80,因此效率高很多。

ByteBuffer

内存分配

  • 堆内分配:ByteBuffer.allocate(1024)
  • 堆外分配:ByteBuffer.allocateDirect(1024)

函数

  • put:写入数据,并移动 position 指针。
ByteBuffer-PUT.png
  • flip:由写模式切换到读模式。读模式下调用,会使 limit 指向当前 position 位置,造成指针错乱。
ByteBuffer-FLIP.png
  • get:读取数据,并移动 position 指针。读操作应在 flip 操作后执行,否则可能读取到错误数据。
ByteBuffer-GET.png
  • compact:压缩数据,并切换到写模式。如果在写模式调用,数据会错乱,因为 limit 指向 了 tail。
ByteBuffer-COMPACT.png
  • mark - reset:标记 position 位置,重置 position 到 mark 位置,不会切换读写模式,读写模式都可用。
ByteBuffer-MARK-RESET.png
  • slice:创建一个新的字节缓冲区,其内容是给定缓冲区内容的共享子序列。

    新缓冲区的内容将从该缓冲区的当前位置开始。对该缓冲区内容的更改将在新缓冲区中可见,反之亦然。这两个缓冲区的位置,限制和标记值将是独立的。

    新缓冲区的位置将为零,其容量和限制将为该缓冲区中剩余的浮点数,并且其标记将不确定。当且仅当该缓冲区是直接缓冲区时,新缓冲区才是直接缓冲区;当且仅当该缓冲区是只读缓冲区时,新缓冲区才是只读缓冲区。

  • rewind:清除 mark 标记,并将 position 指针 重置为0。

  • clear:重置缓冲区,清除 mark 标记,重置 position 指针,重置 limit 指针,回归写模式。

RandomAccessFile

Mode 说明
r 打开文件仅仅是为了读取数据,如果尝试调用任何写入数据的操作都会造成返回IOException错误信息的问题。
rw 打开文件用于读写两种操作,如果文件本身并不存在,则会创建一个全新的文件。
rwd 打开文件用于读写两种操作,这点和 rw 的操作完全一致,但是只会在 cache 满,或者调用 RandomAccessFile.close() 的时候才会执行内容同步操作。
rws 在 rwd 的基础上对内容同步的要求更加严苛,每write修改一个byte都会直接修改到磁盘中。
RandomAccessFile raf = new RandomAccessFile(new File("/path/to/file"), "rw");
// 写数据
raf.write(new byte[5]);
// 移动指针
raf.seek(0L);
// 跳过字节
raf.skipBytes(1);
// 读数据 off:输入数组偏移量 len:读字节数
raf.read(new byte[10], 0, 10);
// 关闭并写盘
raf.close();

FileChannel

RandomAccessFile raf = new RandomAccessFile(new File("/path/to/file"), "rw");
FileChannel channel = raf.getChannel();
// Zero Copy
channel.transferTo(position, count, target);
channel.transferFrom(src, position, count);

MappedByteBuffer

  • 不通过系统调用,数据直接到达 PageCache。
  • mmap 的内存映射,依然受内核 pageCache 体系所约束。
  • DirectIO 是忽略内核的 PageCache,Java 没有直接使用内核 DirectIO 的支持,需要C程序JNI扩展库。
  • MappedByteBuffer 只是交给程序开辟自己的字节数组充当 PageCache,仍需要程序动用代码维护一致性/Dirty等一系列问题。
MapMode 说明
FileChannel.MapMpde.READ_ONLY Mode for a read-only mapping. 只读模式。
FileChannel.MapMpde.READ_WRITE Mode for a read/write mapping. 读写模式。
FileChannel.MapMpde.PRIVATE Mode for a private (copy-on-write) mapping. 堆内存更改不会写入文件。
RandomAccessFile raf = new RandomAccessFile(new File("/path/to/file"), "rw");
FileChannel channel = raf.getChannel();

MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_WRITE, 0, 4090);
// 其余操作函数可参考 ByteBuffer
map.put(new byte[10]);
// PC数据写入存储设备
buffer.force();

2、TCP网络IO


服务端 LISTEN 状态端口号,最多65535个。

服务端无需为 Client 连接分配随机端口号,只要四元组(源IP、源端口、目标IP、目标端口)不同,就可以确定一个连接。

三次握手是内核级的,ServerSocket 启动之后,客户端连接时,无需等待 server.accept() 即可完成三次握手,建立连接。

因为客户端连接是连接层业务,不需要等待应用层响应。

客户端连接建立之后,server.accept() 之前,OS 仅仅开辟资源,并不会分配 FD。

server.accept() 之后,OS 才会创建 FD,FD 在 Java 中体现为 Socket 对象。

ServerSocket配置

  • BACK_LOG:未 accept 的连接队列长度,超过此数量的连接,在 netstat 中体现为 SYN_RECV 状态。

  • SO_TIMEOUT:server.accept() 超时配置,超过阈值未收到连接请求,会跑出 SocketTimeoutException 异常,但并不会造成 server 崩溃。

  • RECEIVE_BUFFER_SIZE:最大接收窗口,默认 8192。最终数值由三次握手之后双方最小缓冲区为基准。

  • REUSE_ADDRESS:端口重用。如果禁用,在程序关闭后,端口可能会继续占用一段时间,如果此时立刻重启程序,会抛出异常。

    绑定到相同端口的程序,必须全部启用该配置,才可重用端口,设置此参数必须在程序绑定到一个本地端口之前调用。

Socker配置

  • KEEP_ALIVE:TCP连接空闲时是否需要向对方发送探测包,依赖于底层的TCP模块实现的,Java中只能设置是否开启,不能设置其详细参数,依赖于系统配置。

  • SO_TIMEOUT:在与此 Socket 关联的 InputStream 上调用 read() 将只阻塞此时间长度,超时将引发 SocketTimeoutException,但 socket 不会崩溃。

  • OOB_INLINE:TCP的紧急指针,一般不建议使用,且不同的TCP/IP实现不同。

    虽然 Socket.sendUrgentData() 的参数是 int 类型,但只有这个int类型的低字节被发送,其它的三个字节被忽略。

    客户端和服务端必须同时开启,否则无法使用 Socket.sendUrgentData() 发送数据。

  • RECEIVE_BUFFER_SIZE:最大接收窗口,默认 8192。最终数值由三次握手之后双方最小缓冲区为基准。

  • SEND_BUFFER_SIZE:最大发送窗口,默认 8192。最终数值由三次握手之后双方最小缓冲区为基准。

  • REUSE_ADDRESS:端口重用。如果禁用,在程序关闭后,端口可能会继续占用一段时间,如果此时立刻重启程序,会抛出异常。

    绑定到相同端口的程序,必须全部启用该配置,才可重用端口,设置此参数必须在程序绑定到一个本地端口之前调用。

  • SO_LINGER:控制 Socket 关闭行为,

    • 默认情况执行 Socket.close() 立即返回,但底层的连接不会立即关闭,会延迟一段时间,直到数据发送完成,才会真正的关闭 Socket,断开连接。
    • Socket.setSoLinger(true, 0):执行 Socket.close() 会立即返回,底层 Socket 立即关闭,所有未发送数据被丢弃。
    • Socket.setSoLinger(true, 3600):执行 Socket.close() 会进入阻塞状态,底层 Socket 尝试发送剩余数据,直到如下条件解除阻塞状态:
      1. 剩余数据发送完成。
      2. 剩余数据未发送完成,但阻塞超过 3600毫秒,立即返回,且丢弃剩余数据。
  • TCP_NO_DELAY:默认 false,为启用 Nagle算法。Nagle 算法的立意是,避免网络中充塞小封包,提高网络的利用率。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容