文章目录
![](https://img-blog.csdnimg.cn/20210712112028624.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80ODQ1MDM4NQ==,size_16,color_FFFFFF,t_70#pic_center)
1. 同步和异步
同步和异步站在任务调度者角度:描述消息通信的机制
- 同步(Synchronous):调用一个方法后,必须要等待一个返回值才能执行后续操作
- 异步(Asynchronous):调用一个方法后,不需要等待返回值就可以执行后续操作,当被调用的方法执行完成后,一般会通过状态、通知和回调来通知调用者
2. 阻塞和非阻塞
阻塞和非阻塞站在CPU角度:描述等待调用结果时的状态
- 阻塞:调用结果返回之前,当前线程会被挂起,只有在得到结果之后才会返回
- 非阻塞:若该调用不能立刻得到结果,则该调用不会阻塞当前线程,会立刻返回,相应操作在后台继续处理,调用者需要定时轮询查看处理状态
阻塞和非阻塞是同步和异步的一种属性
3. IO类型
- BIO(Block IO):同步阻塞式IO;JDK 1.4之前是面向流的I/O,任何数据都是以流的形式进行传输,速度较慢
- NIO(non-blocking IO):同步非阻塞式IO;在JDK 1.4中出现了NIO,是一个**面向块(缓冲区)**的I/O,任何数据都是以块的形式进行传输;在java.nio包中
- NIO2/AIO(Asynchronous IO):异步非阻塞式IO;在JDK 1.7中NIO有了进一步改进,即NIO2/AIO
NIO之所以是同步,是因为它的accept/read/write方法的内核I/O操作都会阻塞当前线程
NIO在并发量特别大时可以使用,一般情况下IO就够用了
4. NIO主要组成
NIO的主要组成部分:Buffer(缓冲区)、Channel(通道)、Selector(选择器)
4.1 Buffer(缓冲区)
-
NIO中通过Buffer来读写数据,所有数据都用Buffer来处理,Buffer是NIO读写数据的中转池
-
Buffer对基本类型的数组进行了封装(但排除了boolean),已知直接子类:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer
-
缓冲区内部存在指针,初始时在0索引处,对缓冲区进行读写操作时,操作的是指针所指索引处的数据,操作完后指针会后移一次;因此在读之前写的数据时,应让指针重新指向缓冲区开头
-
ByteBuffer常用方法:
创建ByteBuffer对象: 间接缓冲区的创建和销毁效率高于直接缓冲区,但工作效率低于直接缓冲区 // 方式1:在堆中创建间接缓冲区* static ByteBuffer allocate(int capacity) // 方式2:在系统内存中创建直接缓冲区 static ByteBuffer allocateDirect(int capacity) // 方式3:通过数组创建间接缓冲区 static ByteBuffer wrap(byte[] array) 添加数据:不能让缓冲区内部的指针超出容量 // 向缓冲区当前可用位置添加数据 abstract ByteBuffer put(byte b) // 向缓冲区当前可用位置添加一个byte[]数组 ByteBuffer put(byte[] src) // 向缓冲区当前可用位置添加一个byte[]数组的一部分 ByteBuffer put(byte[] src, int offset, int length)
-
ByteBuffer直接抽象子类MappedByteBuffer,可以将文件直接映射至内存,把硬盘中的读写变成内存中的读写,因此可以提高大文件的读写效率
FileChannel的map方法获取MappedByteBuffer: // 将节点中从position开始的size个字节映射到返回的MappedByteBuffer中 // 由源码得知:参数size可以传递的最大值为2147483647L(int的最大值2^31-1),即size<2G abstract MappedByteBuffer map(FileChannel.MapMode mode, long position, long size)
-
Buffer常用方法:
// 返回此缓冲区的底层实现数组 // 若是直接缓冲区,则会抛出不支持操作异常 abstract Object array() // 返回此缓冲区的容量,Buffer创建后容量不可变 int capacity() 限制只能在[position, limit)区间内读写 // 获取此缓冲区的限制 // 初始时limit() = capacity() int limit() // 设置此缓冲区的限制 Buffer limit(int newLimit) position值应在[0, limit]区间内 // 获取当前可写入位置索引 int position() // 更改当前可写入位置索引 Buffer position(int newPosition) // 设置此缓冲区的标记为当前的position Buffer mark() // 将此缓冲区的位置重置为mark标记的position // 若没有标记,则会抛出异常 Buffer reset() 给Buffer存入数据后可以flip()一下,便于下次取出 // limit() = position() // position() = 0 // 丢弃mark() Buffer flip() 从Buffer取出数据后可以clear()一下,便于下次存入 // limit() = capacity() // position() = 0 // 丢弃mark() Buffer clear() // 重绕此缓冲区: // limit()不变 // position() = 0 // 丢弃mark() Buffer rewind() // 获取position与limit之间的元素数 int remaining() // 获取当前缓冲区是否只读 abstract boolean isReadOnly() // 获取当前缓冲区是否为直接缓冲区 abstract boolean isDirect()
4.2 Channel(通道)
-
Channel是一个双向通道,用于连接I/O操作,可以读、写、同时读写,而IO流只能读或写
-
Channel常用抽象实现类:
- FileChannel:读写文件数据;可以使用FileInputStream或FileOutputStream的getChannel()方法获取
- SelectableChannel:可通过Selector实现多路复用的通道;是下面3个的爷爷类
- DatagramChannel:读写UDP数据
- SocketChannel:读写TCP数据
- ServerSocketChannel:监听TCP连接
-
FileChannel的读写方法:
// 读取多少字节,position就后移多少,最多和limit一样 // read方法给ByteBuffer中成功放入数据后,FileChannel中指着文件的指针才会后移 abstract int read(ByteBuffer dst) // 将[position, limit)区间内的内容写出,写多少就将position后移多少 // write方法从ByteBuffer中成功取出数据放入文件后,FileChannel中指着文件的指针才会后移 abstract int write(ByteBuffer src)
-
示例1:FileChannel结合MappedByteBuffer复制大于2GB的文件
import java.io.IOException; import java.io.RandomAccessFile; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; /** * FileChannel结合MappedByteBuffer复制大于2GB的文件: * 可以将文件分成几块,每块小于2GB,每次映射一块 * <p> * InputStream获取的Channel只能映射为READ_ONLY模式 * OutputStream获取的Channel不可以映射,并且OutputStream的Channel只能写不能读 * RandomAccessFile获取的Channel可以映射为READ_ONLY、READ_WRITE、PRIVATE三种模式任意一种 * java.io.RandomAccessFile是一个可以设置读r写w模式的IO流类 */ public class CopyGE2G { public static void main(String[] args) throws IOException { String src = "D:\\src.txt"; String dest = "D:\\dest.txt"; // 1. 获取流对象 // 输入流,只需读文件就行了 RandomAccessFile r = new RandomAccessFile(src, "r"); // 输出流,需要读和写 RandomAccessFile rw = new RandomAccessFile(dest, "rw"); // 2. 根据流对象获取Channel对象 FileChannel rChannel = r.getChannel(); FileChannel rwChannel = rw.getChannel(); // 获取文件大小 long fileSize = rChannel.size(); // 设置每次映射的的大小:此处为512MB long mapSize = 512 * 1024 * 1024; // 一共需要映射多少次,需要向上取整 long count = fileSize % mapSize == 0 ? fileSize / mapSize : fileSize / mapSize +