共计 4595 个字符,预计需要花费 12 分钟才能阅读完成。
NIO
同步非阻塞
阻塞与非阻塞的区别:
阻塞时,在调用后果返回时,以后线程会被挂起,并在失去后果之后返回
非阻塞时,如不能立刻失去后果,该调用不会阻塞以后线程,调用者须要定时轮询查看解决状态
Channel(通道)和 Buffer(缓冲区)
与一般 IO 的不同和关系
NIO 以块的形式解决数据,然而 IO 是以最根底的字节流的模式去写入和读出的
NIO 不再是和 IO 一样用 OutputStream 和 InputStream 输出流的模式来进行解决数据的,然而又是基于这种流的形式,采纳了通道和缓冲区的流量交易模式进行解决
NIO 的通道是能够双向的,IO 的流只能是单向的
NIO 的缓冲区 (字节数组) 还能够进行分片,能够建设只读缓冲区、间接缓冲区和间接缓冲区,只读缓冲区就是只能够读,间接缓冲区是为了放慢 I / O 速度,以一种非凡的形式调配其内存的缓冲区
NIO 采纳的是多路复用的 IO 模型,BIO 用的是阻塞的 IO 模型
通道的概念
通道是对原 I / O 包中的流的模仿。到任何目的地的所有数据都必须通过一个 Channel 对象(通道)。一个 Buffer 本质上就是一个容器对象。发送给一个通道的所有对象都必须首先放到缓冲区中;从通道中读取的任何数据都要读到缓冲区中
缓冲区的概念
Buffer 是一个对象,它蕴含一些要写入或者刚读出的数据。在 NIO 中退出 Buffer 对象,在流式 IO 中,将数据间接写入或者读到 Stream 对象中
在 NIO 库中,所有数据都是用缓冲区解决的。在读取数据时,它是间接读到缓冲区中的。在写入数据时,它是写入到缓冲区的。任何时候拜访 NIO 中的数据,都须要将它放到缓冲区中
缓冲区本质上是一个数组。通常它是一个字节数组,然而也能够应用其余品种的数组。然而一个缓冲区不仅仅是一个数组,缓冲区提供了对数据的结构化拜访,而且还能够跟踪零碎的读 / 写过程
ByteBuffer
CharBuffer
ShortBuffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBuffer
选择器
Selector 是多路复用器,用于同时检测多个通道的事件以实现异步 I /O。
通过一个选择器来同时对多个套接字通道进行监听,当套接字通道有可用的事件的时候,通道改为可用状态,选择器就能够实现可用的状态。
工作原理
客户端 —–》Channel—–》Selector——》keys– 状态扭转 —》server
Buffer 缓冲区 Channel 通道 Selector 选择器
Server 端创立 ServerSocketChannel 有一个 Selector 多路复用器 轮询所有注册的通道,依据通道状态,执行相干操作
Connect 连贯状态
Accept 阻塞状态
Read 可读状态
Write 可写状态
Client 端创立 SocketChannel 注册到 Server 端的 Selector
buffer
capacity 缓冲区数组的总长度
position 下一个要操作的数据元素的地位
limit 缓冲区数组中不可操作的下一个元素的地位,limit<=capacity
mark 用于记录以后 position 的前一个地位或者默认是 0
clear/flip/rewind 等都是操作 limit 和 position 的值来实现反复读写的
ByteBuffer
有且仅有 ByteBuffer(字节缓冲区)能够间接与通道交互。
public static void main(String[] args) {
// 生成 FileChannel 文件通道 FileChannel 的操作 –> 操作 ByteBuffer 用于读写,并独占式拜访和锁定文件区域
// 写入文件
try(FileChannel fileChannel = new FileOutputStream(FILE).getChannel()){
fileChannel.write(ByteBuffer.wrap(“test”.getBytes()));
} catch (IOException e){
throw new RuntimeException(“ 写入文件失败 ”,e);
}
// 在文件结尾写入
try(FileChannel fileChannel = new RandomAccessFile(FILE,”rw”).getChannel()){
fileChannel.position(fileChannel.size());// 移至文件结尾
fileChannel.write(ByteBuffer.wrap(“some”.getBytes()));
} catch (IOException e){
throw new RuntimeException(“ 写入文件结尾失败 ”,e);
}
try(FileChannel fileChannel = new FileInputStream(FILE).getChannel();
FileChannel out = new FileOutputStream(“C:UserssinosoftDesktopcopy.txt”).getChannel()
){
// 读取操作,须要调用 allocate 显示调配 ByteBuffer
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// read 之后将数据放入缓冲区
while (fileChannel.read(byteBuffer) != -1){
byteBuffer.flip(); // 筹备写入
out.write(byteBuffer);
byteBuffer.clear(); // 清空缓存区
}
} catch (IOException e){
throw new RuntimeException(“ 读取文件失败 ”,e);
}
}
办法阐明
rewind()办法是将 position 设置为缓冲区的开始地位
get()和 put()都会批改 position
get(int)和 put(int)都不会批改 position
mark()设置 mark 为以后 position
flip()将写模式切换为读模式
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
内存映射文件
内存映射文件能够创立和批改那些因为太大而无奈放入内存的文件。
RandomAccessFile tdat = new RandomAccessFile(“test.dat”, “rw”);
MappedByteBuffer out = tdat.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, length);
或者
FileChannel fc = new FileInputStream(new File(“temp.tmp”)).getChannel();
IntBuffer ib = fc.map(FileChannel.MapMode.READ_ONLY,0, fc.size()).asIntBuffer();
映射文件拜访比规范 IO 性能高很多
文件锁定
文件锁定可同步拜访,文件锁对其余操作系统过程可见,因为 java 文件锁间接映射到本机操作系统锁定工具。
public class FileLockTest {
private static final String FILE = “C:UserssinosoftDesktop 残余工作正本.txt”;
public static void main(String[] args) throws IOException, InterruptedException {
FileChannel fileChannel = new FileOutputStream(FILE).getChannel();
// 文件锁
FileLock fileLock = fileChannel.tryLock();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
FileChannel fileChannel = null;
try {
fileChannel = new FileOutputStream(FILE).getChannel();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put(“aqws”.getBytes());
try {
System.out.println(“ 线程筹备写 ”);
fileChannel.write(byteBuffer);
System.out.println(“ 线程写完 ”);
} catch (IOException e) {
e.printStackTrace();
}
}
});
thread.start();
if(fileLock != null){
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put(“aqwqdqdhwfwihfejfhi”.getBytes());
System.out.println(“ 主线程睡眠 ”);
Thread.sleep(10000);
// 会报错 java.nio.channels.NonWritableChannelException// fileChannel.read(byteBuffer);
System.out.println(“ 主线程筹备写 ”);
fileChannel.write(byteBuffer);
fileLock.release();
}
}
}
主线程睡眠
线程筹备写
java.io.IOException: 另一个程序已锁定文件的一部分,过程无法访问。
at sun.nio.ch.FileDispatcherImpl.write0(Native Method)
at sun.nio.ch.FileDispatcherImpl.write(FileDispatcherImpl.java:75)
at sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:93)
at sun.nio.ch.IOUtil.write(IOUtil.java:65)
at sun.nio.ch.FileChannelImpl.write(FileChannelImpl.java:211)
at com.zhanghe.study.io.nio.FileLockTest$1.run(FileLockTest.java:35)
at java.lang.Thread.run(Thread.java:745)
主线程筹备写
通过调用 FileChannel 上的 tryLock 或 lock,能够取得整个文件的 FileLock(SocketChannel、DatagramChannel 和 ServerSocketChannel 不须要锁定,因为实质上就是单线程实体)
tryLock()是非阻塞的,试图获取锁,若不能获取,只是从办法调用返回
lock()会阻塞,直到取得锁,或者调用 lock()的线程中断,或者调用 lock()办法的通道敞开。
应用 FileLock.release()开释锁