贵阳网站网站建设,wordpress贷款主题,网站费计入什么科目,h5个人博客网站模板一.引言
何为IO
涉及计算机核心(CPU和内存)与其他设备间数据迁移的过程#xff0c;就是I/O。数据输入到计算机内存的过程即输入#xff0c;反之输出到外部存储#xff08;比如数据库#xff0c;文件#xff0c;远程主机#xff09;的过程即输出。 I/O 描述了计算机系统…一.引言
何为IO
涉及计算机核心(CPU和内存)与其他设备间数据迁移的过程就是I/O。数据输入到计算机内存的过程即输入反之输出到外部存储比如数据库文件远程主机的过程即输出。 I/O 描述了计算机系统与外部设备之间通信的过程。
磁盘I/O 输入就是从磁盘读取数据到内存输出将内存中的数据写入磁盘网络I/O 输入从网络中的另一台计算机或服务器获取数据并将其加载到本地内存中输出将本地内存中的数据发送到网络中的其他计算机或服务器
IO的过程
根据大学里学到的操作系统相关的知识为了保证操作系统的稳定性和安全性一个进程的地址空间划分为 用户空间User space 和 内核空间Kernel space 。
像我们平常运行的应用程序都是运行在用户空间只有内核空间才能进行系统态级别的资源有关的操作比如文件管理、进程通信、内存管理等等因为这些都是比较危险的操作不可以由应用程序乱来只能交给底层操作系统来。也就是说我们想要进行 IO 操作只能发起系统调用请求操作系统来间接访问内核空间。
我们在平常开发过程中接触最多的就是 磁盘 IO读写文件 和 网络 IO网络请求和响应。
从应用程序的视角来看的话我们的应用程序对操作系统的内核发起 IO 调用系统调用操作系统负责的内核执行具体的 IO 操作。也就是说我们的应用程序实际上只是发起了 IO 操作的调用而已具体 IO 的执行是由操作系统的内核来完成的。
当应用程序发起 I/O 调用后会经历两个步骤IO执行
数据准备内核等待 I/O 设备准备好数据即操作系统将外部数据加载到内核缓冲区数据拷贝内核将数据从内核缓冲区拷贝到用户进程缓冲区。 Java的3种网络IO模型
Java中提供的IO有关的API也是依赖操作系统层面的IO操作实现的。在Java中主要有三种IO模型分别是阻塞IOBIO、非阻塞IONIO和 异步IOAIO。
可以把Java中的BIO、NIO和AIO理解为是Java语言对操作系统的5种IO模型的封装(在Linux(UNIX)操作系统中共有五种IO模型分别是阻塞IO模型、非阻塞IO模型、IO复用模型、信号驱动IO模型以及异步IO模型)。程序员在使用这些API的时候不需要关心操作系统层面的知识也不需要根据不同操作系统编写不同的代码。只需要使用Java的API就可以了。
阻塞和非阻塞IO
上面已经说过应用程序的IO实际是分为两个步骤IO调用和IO执行。IO调用是由进程发起IO执行是操作系统的工作。操作系统的IO情况决定了进程IO调用是否能够得到立即响应。
阻塞IO如果操作系统尚未准备好数据当前进程或线程一直等待直到其就绪非阻塞IO如果操作系统尚未准备好数据进程或线程并不一直等待其就绪而是可以做其他事情。进程/线程会周期性地轮询或查询IO操作的状态以确定数据是否就绪。
非阻塞IO需要进程/线程自己负责查询IO状态而阻塞IO则是操作系统负责在数据就绪时唤醒进程/线程。
异步和同步IO
同步IO同步IO是指程序发起IO操作后程序会一直等待直到IO操作完成然后再继续执行后续的代码。异步IO异步IO是指程序发起IO操作后它可以继续执行其他任务而不必等待IO操作完成。当IO操作完成后程序会得到通知可以处理已完成的IO操作。异步IO可以提高程序的并发性和响应性因为它允许程序在等待IO的同时执行其他任务。
自己的理解我感觉阻塞和非阻塞IO针对的是操作系统未准备好数据时进程的处理方式是等待还是不等待。异步和同步IO针对的是IO操作未完成时IO操作包括数据准备和数据拷贝两步骤进程的处理方式是等待还是不等待。
二.BIO
Java BIO 就是传统的 java io 编程其相关的类和接口在 java.ioBIO(blocking I/O) 同步阻塞 IO 模型 即在读写数据过程中会发生阻塞现象直至 有可供读取的数据或者数据能够写入。 服务器实现模式为 一个连接一个线程即客户端有连接请求时服务器端就需 要启动一个线程进行处理如果这个连接不做任何事情会造成不必要的线程开销可以通过线程池机制改善(实现多个客户连接服务器) 映射到Linux操作系统中这就是一种最简单的IO模型即阻塞IO。 阻塞 I/O 是最简单的 I/O 模型一般表现为进程或线程等待某个条件如果条件不满足则一直等下去。条件满足则进行下一步操作。
BIO客户端、服务端通信实现
Server 服务端
/**目标实现服务端可以同时接收多个客户端的Socket通信需求。思路是服务端每接收到一个客户端socket请求对象之后都交给一个独立的线程来处理客户端的数据交互需求。*/
public class Server {public static void main(String[] args) {try {// 1、注册端口ServerSocket ss new ServerSocket(9999);// 2、定义一个死循环负责不断的接收客户端的Socket链接请求while(true){Socket socket ss.accept();// 3、创建一个独立的线程来处理与这个客户端的socket通信需求。new ServerThreadReader(socket).start();}} catch (IOException e) {e.printStackTrace();}}
}
ServerThreadReader 服务端与客户端保持通信的线程
public class ServerThreadReader extends Thread {private Socket socket;public ServerThreadReader(Socket socket){this.socket socket;}Overridepublic void run() {try {// 从socket对象中得到一个字节输入流InputStream is socket.getInputStream();// 使用缓冲字符输入流包装字节输入流BufferedReader br new BufferedReader(new InputStreamReader(is));String msg;while((msg br.readLine())!null){System.out.println(msg);}} catch (Exception e) {e.printStackTrace();}}
}Client 客户端
/**客户端*/
public class Client {public static void main(String[] args) {try {// 1、请求与服务端的Socket对象链接Socket socket new Socket(127.0.0.1 , 9999);// 2、得到一个打印流PrintStream ps new PrintStream(socket.getOutputStream());// 3、使用循环不断的发送消息给服务端接收Scanner sc new Scanner(System.in);while(true){System.out.print(请说);String msg sc.nextLine();ps.println(msg);ps.flush();}} catch (IOException e) {e.printStackTrace();}}
}三.NIO Java NIOnon-blocking是从Java 1.4版本开始引入的一个新的IO APINIO 相关类都被放在 java.nio 包及子包下并且对原 java.io 包中的很多类进行改写可以替代标准的Java IO API。NIO与原来的IO有同样的作用和目的但是使用的方式完全不同NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方式进行读写操作。 Java NIOnon-blocking 映射的不是操作系统五大IO模型中的NIO模型采用轮询的方式检查IO状态而是另外的一种模型叫做IO多路复用模型 IO multiplexing 。
IO复用模型核心思路: 系统给我们提供一类函数(如我们耳濡目染的select、 poll、epoll函数)它们可以同时监控多个 fd 的操作任何一个返回内核数据就绪应用进程再发起 recvfrom 系统调用。
文件描述符fd(File Descriptor)它是计算机科学中的一个术语形式上是一个非负整数。当程序打开一个现有文件或者创建一个新文件时内核向进程返回一个文件描述符. 目前支持 IO 多路复用的系统调用有 selectepoll 等等。select 系统调用目前几乎在所有的操作系统上都有支持。 select 调用内核提供的系统调用它支持一次查询多个系统调用的可用状态。几乎所有的操作系统都支持。epoll 调用属于 select 调用的增强版本优化了 IO 的执行效率 Java 中的 NIO 有一个非常重要的选择器 ( Selector ) 的概念也可以被称为 多路复用器。通过它只需要一个线程便可以管理多个客户端连接。当客户端数据到了之后才会为其服务。 NIO 有三大核心部分Channel( 通道) Buffer( 缓冲区), Selector( 选择器)
1. 三大组件
Channel Buffer
channel 有一点类似于 流它就是读写数据的双向通道可以从 channel 将数据读入 buffer也可以将 buffer 的数据写入 channel而之前的 流 要么是输入要么是输出channel 比 流 更为底层 常见的 Channel 有
FileChannel 文件从文件中读写数据。DatagramChannel UDP能通过 UDP 读写网络中的数据。SocketChannelTCP Client能通过 TCP 读写网络中的数据。ServerSocketChannelTCP Server可以监听新进来的 TCP 连接像 Web 服务器那样。对每一个新进来的连接都会创建一个 SocketChannel
buffer 则用来缓冲读写数据常见的 buffer 有
ByteBuffer用的最多 MappedByteBufferDirectByteBufferHeapByteBufferShortBufferIntBufferLongBufferFloatBufferDoubleBufferCharBuffer
Selector
selector 的作用就是配合一个线程来管理多个 channel获取这些 channel 上发生的事件这些 channel 工作在非阻塞模式下不会让线程吊死在一个 channel 上。适合连接数特别多但流量低的场景low traffic 调用 selector 的 select() 会阻塞直到 channel 发生了读写就绪事件这些事件发生select 方法就会返回这些事件交给 thread 来处理
2.ByteBuffer
2.1ByteBuffer的使用
向 buffer 写入数据例如调用 channel.read(buffer)调用 flip() 切换至读模式从 buffer 读取数据例如调用 buffer.get()调用 compact() 或 clear() 切换至写模式compact()会自动压缩未读的clear()则会直接清空一次可能读不完重复 1~4 步骤读
Slf4j
public class TestByteBuffer {public static void main(String[] args) {// FileChannel 获得方式// 1. 输入输出流 2. RandomAccessFiletry (FileChannel channel new RandomAccessFile(D:\\data.txt, rw).getChannel()) {// 准备缓冲区指定容量后不可更改ByteBuffer buffer ByteBuffer.allocate(10);while(true) {// 从 channel 读取数据向 buffer 写入int len channel.read(buffer);log.debug(读取到的字节数 {}, len);if(len -1) { // 没有内容了break;}// 打印 buffer 的内容buffer.flip(); // 切换至读模式while(buffer.hasRemaining()) { // 是否还有剩余未读数据byte b buffer.get();//get()会改变读指针但get(i)不会直接根据索引查找位置log.debug(实际字节 {}, (char) b);}buffer.clear(); // 切换为写模式}} catch (IOException e) {e.printStackTrace();}}
}2.2ByteBuffer 结构
ByteBuffer的结构可以看成一个连续的数组有以下重要属性
capacity容量position起始位置limit写入/读取限制位置
一开始 写模式下position 是写入位置limit 等于容量下图表示写入了 4 个字节后的状态
flip 动作发生后position 切换为读取位置limit 切换为读取限制
读取 4 个字节后状态 clear 动作发生后状态 compact 方法是把未读完的部分向前压缩然后切换至写模式
2.3ByteBuffer的常用方法
分配空间
分配容量后就不可修改了
ByteBuffer byteBuffer1 ByteBuffer.allocate(容量);//class java.nio.HeapByteBuffer
ByteBuffer byteBuffer2 ByteBuffer.allocateDirect(容量);//class java.nio.DirectByteBuffer
两种方法返回的实现类不同
HeapByteBuffer分配在 java 堆内存读写效率较低受到 GC垃圾回收 的影响DirectByteBuffer通过调用本地操作系统的内存管理机制来分配堆外内存读写效率高不需要通过额外的复制操作将数据从堆内存复制到物理内存不会受 GC 影响但分配的效率低并且如果释放不完全会造成内存泄漏
向 buffer 写入数据
有两种办法
调用 channel 的 read 方法调用 buffer 自己的 put 方法
从 buffer 读取数据
同样有两种办法
调用 channel 的 write 方法调用 buffer 自己的 get 方法
get 方法会让 position 读指针向后走如果想重复读取数据
可以调用 rewind 方法将 position 重新置为 0或者调用 get(int i) 方法获取索引 i 的内容它不会移动读指针
字符串与 ByteBuffer 互转
两种方法
ByteBuffer buffer1 StandardCharsets.UTF_8.encode(你好);
ByteBuffer buffer2 Charset.forName(utf-8).encode(你好);debug(buffer1);
debug(buffer2);CharBuffer buffer3 StandardCharsets.UTF_8.decode(buffer1);
System.out.println(buffer3.getClass());
System.out.println(buffer3.toString());
Buffer 是非线程安全的
分散读取、集中写入
2.4调试工具类
netty依赖 dependencygroupIdio.netty/groupIdartifactIdnetty-all/artifactIdversion4.1.51.Final/version/dependency
import io.netty.util.internal.StringUtil;import java.nio.ByteBuffer;import static io.netty.util.internal.MathUtil.isOutOfBounds;
import static io.netty.util.internal.StringUtil.NEWLINE;public class ByteBufferUtil {private static final char[] BYTE2CHAR new char[256];private static final char[] HEXDUMP_TABLE new char[256 * 4];private static final String[] HEXPADDING new String[16];private static final String[] HEXDUMP_ROWPREFIXES new String[65536 4];private static final String[] BYTE2HEX new String[256];private static final String[] BYTEPADDING new String[16];static {final char[] DIGITS 0123456789abcdef.toCharArray();for (int i 0; i 256; i) {HEXDUMP_TABLE[i 1] DIGITS[i 4 0x0F];HEXDUMP_TABLE[(i 1) 1] DIGITS[i 0x0F];}int i;// Generate the lookup table for hex dump paddingsfor (i 0; i HEXPADDING.length; i) {int padding HEXPADDING.length - i;StringBuilder buf new StringBuilder(padding * 3);for (int j 0; j padding; j) {buf.append( );}HEXPADDING[i] buf.toString();}// Generate the lookup table for the start-offset header in each row (up to 64KiB).for (i 0; i HEXDUMP_ROWPREFIXES.length; i) {StringBuilder buf new StringBuilder(12);buf.append(NEWLINE);buf.append(Long.toHexString(i 4 0xFFFFFFFFL | 0x100000000L));buf.setCharAt(buf.length() - 9, |);buf.append(|);HEXDUMP_ROWPREFIXES[i] buf.toString();}// Generate the lookup table for byte-to-hex-dump conversionfor (i 0; i BYTE2HEX.length; i) {BYTE2HEX[i] StringUtil.byteToHexStringPadded(i);}// Generate the lookup table for byte dump paddingsfor (i 0; i BYTEPADDING.length; i) {int padding BYTEPADDING.length - i;StringBuilder buf new StringBuilder(padding);for (int j 0; j padding; j) {buf.append( );}BYTEPADDING[i] buf.toString();}// Generate the lookup table for byte-to-char conversionfor (i 0; i BYTE2CHAR.length; i) {if (i 0x1f || i 0x7f) {BYTE2CHAR[i] .;} else {BYTE2CHAR[i] (char) i;}}}/*** 打印所有内容* param buffer*/public static void debugAll(ByteBuffer buffer) {int oldlimit buffer.limit();buffer.limit(buffer.capacity());StringBuilder origin new StringBuilder(256);appendPrettyHexDump(origin, buffer, 0, buffer.capacity());System.out.println(---------------------------- all ----------------------------------------);System.out.printf(position: [%d], limit: [%d]\n, buffer.position(), oldlimit);System.out.println(origin);buffer.limit(oldlimit);}/*** 打印可读取内容* param buffer*/public static void debugRead(ByteBuffer buffer) {StringBuilder builder new StringBuilder(256);appendPrettyHexDump(builder, buffer, buffer.position(), buffer.limit() - buffer.position());System.out.println(---------------------------- read ---------------------------------------);System.out.printf(position: [%d], limit: [%d]\n, buffer.position(), buffer.limit());System.out.println(builder);}public static void main(String[] args) {ByteBuffer buffer ByteBuffer.allocate(10);buffer.put(new byte[]{97, 98, 99, 100});debugAll(buffer);}private static void appendPrettyHexDump(StringBuilder dump, ByteBuffer buf, int offset, int length) {if (isOutOfBounds(offset, length, buf.capacity())) {throw new IndexOutOfBoundsException(expected: 0 offset( offset ) offset length( length ) buf.capacity( buf.capacity() ));}if (length 0) {return;}dump.append( ------------------------------------------------- NEWLINE | 0 1 2 3 4 5 6 7 8 9 a b c d e f | NEWLINE -------------------------------------------------------------------------);final int startIndex offset;final int fullRows length 4;final int remainder length 0xF;// Dump the rows which have 16 bytes.for (int row 0; row fullRows; row) {int rowStartIndex (row 4) startIndex;// Per-row prefix.appendHexDumpRowPrefix(dump, row, rowStartIndex);// Hex dumpint rowEndIndex rowStartIndex 16;for (int j rowStartIndex; j rowEndIndex; j) {dump.append(BYTE2HEX[getUnsignedByte(buf, j)]);}dump.append( |);// ASCII dumpfor (int j rowStartIndex; j rowEndIndex; j) {dump.append(BYTE2CHAR[getUnsignedByte(buf, j)]);}dump.append(|);}// Dump the last row which has less than 16 bytes.if (remainder ! 0) {int rowStartIndex (fullRows 4) startIndex;appendHexDumpRowPrefix(dump, fullRows, rowStartIndex);// Hex dumpint rowEndIndex rowStartIndex remainder;for (int j rowStartIndex; j rowEndIndex; j) {dump.append(BYTE2HEX[getUnsignedByte(buf, j)]);}dump.append(HEXPADDING[remainder]);dump.append( |);// Ascii dumpfor (int j rowStartIndex; j rowEndIndex; j) {dump.append(BYTE2CHAR[getUnsignedByte(buf, j)]);}dump.append(BYTEPADDING[remainder]);dump.append(|);}dump.append(NEWLINE -------------------------------------------------------------------------);}private static void appendHexDumpRowPrefix(StringBuilder dump, int row, int rowStartIndex) {if (row HEXDUMP_ROWPREFIXES.length) {dump.append(HEXDUMP_ROWPREFIXES[row]);} else {dump.append(NEWLINE);dump.append(Long.toHexString(rowStartIndex 0xFFFFFFFFL | 0x100000000L));dump.setCharAt(dump.length() - 9, |);dump.append(|);}}public static short getUnsignedByte(ByteBuffer buffer, int index) {return (short) (buffer.get(index) 0xFF);}2.5黏包、半包问题
黏包Packet Concatenation和半包Incomplete Packet问题是在网络通信中常见的两个问题。它们涉及到数据的传输和接收不完整或混淆的情况。
黏包问题Packet Pasting黏包问题指的是在网络通信中由于数据传输速度快于数据处理速度多个数据包可能会在接收端被一次性接收到导致它们被黏在一起无法准确分辨每个数据包的界限。这可能会导致数据解析错误或混乱。
例如发送端发送了两个数据包但接收端可能会一次性接收到这两个数据包从而形成一个黏包。解决这个问题的方法通常涉及在数据包中添加长度信息或特殊分隔符以便接收端能够正确地切分数据包。
半包问题Partial Packet半包问题是指在数据传输中数据包没有完整地传输完成就被接收端接收到造成接收到的数据包不完整即半包。这可能会导致数据不完整或无法正确解析。
例如发送端发送一个较大的数据包但在传输过程中被切分成多个片段接收端可能只接收到其中的一部分导致数据不完整。解决这个问题的方法通常是在数据包中添加长度信息确保接收端能够正确地等待和组装完整的数据包。
3.文件编程FileChannel
FileChannel 只能工作在阻塞模式下其他与网络有关的Channel则有阻塞模式与非阻塞模式两种
3.1常用方法
获取
不能直接打开 FileChannel必须通过 FileInputStream、FileOutputStream 或者RandomAccessFile 来获取 FileChannel它们都有 getChannel 方法
通过 FileInputStream 获取的 channel 只能读通过 FileOutputStream 获取的 channel 只能写通过 RandomAccessFile 是否能读写根据构造 RandomAccessFile 时的读写模式(rw)决定
读取
会从 channel 读取数据填充 ByteBuffer返回值表示读到了多少字节-1 表示到达了文件的末尾
int readBytes channel.read(buffer);
写入
ByteBuffer buffer ...;
buffer.put(...); // 存入数据
buffer.flip(); // 切换读模式while(buffer.hasRemaining()) {channel.write(buffer);
}
在 while 中调用 channel.write 是因为 write 方法并不能保证一次将 buffer 中的内容全部写入 channel
关闭
channel 必须关闭不过调用了 FileInputStream、FileOutputStream 或者 RandomAccessFile 的 close 方法会间接地调用 channel 的 close 方法
3.2两个Channel之间传输数据
超过 2g 大小的文件传输
transferTo起始位置传输数传输目标地
public class TestFileChannelTransferTo {public static void main(String[] args) {try (FileChannel from new FileInputStream(data.txt).getChannel();FileChannel to new FileOutputStream(to.txt).getChannel();) {// 效率高底层会利用操作系统的零拷贝进行优化long size from.size();// left 变量代表还剩余多少字节for (long left size; left 0; ) {System.out.println(position: (size - left) left: left);left - from.transferTo((size - left), left, to);}} catch (IOException e) {e.printStackTrace();}}
}