原文鏈接http://zhhll.icu/2020/java%E5%9F%BA%E7%A1%80/IO/NIO%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C/
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:\\Users\\sinosoft\\Desktop\\copy.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:\\Users\\sinosoft\\Desktop\\剩余工作副本.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()釋放鎖
// 鎖定文件的一部分,鎖住size-position區域。第三個參數指定是否共享此鎖
tryLock(long position, long size, boolean shared)
由于本身的博客百度沒有收錄,博客地址http://zhhll.icu