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()开释锁