Java NIO
PS: 之前一直想了解這個NIO到底是什麼東西,奈何目前用不到,聽說現在許多框架都在用,而且面試的時候也有被問道,感覺還是去多瞭解瞭解底層怎麼實現的~~這是我的第100篇博客!!!
一. NIO與IO區別
NIO主要有三大核心部分:Channel(通道),Buffer(緩衝區), Selector
二. 緩衝區
緩衝區(Buffer) :一個用於特定基本數據類型的容器。由 java.nio 包定義的,所有緩衝區都是 Buffer 抽象類的子類。
Java NIO 中的 Buffer 主要用於與 NIO 通道進行交互,數據是從通道讀入緩衝區,從緩衝區寫入通道中的。
2.1 直接緩衝區與非直接緩衝區
- 非直接緩衝區:通過allocate()方法分配緩衝區,將緩衝區建立在JVM的內存中
- 直接緩衝區:通過allocateDirect()方法分配直接緩衝區,將緩衝區建立在物理內存中。可以提高效率
這裏探究驗證了一下底層Buffer的原理
//分配指定大小的緩衝區
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
String s=new String("abcde");
//緩衝區存數據
System.out.println("PUT操作");
byteBuffer.put(s.getBytes());
System.out.println(byteBuffer.position()); //5
System.out.println(byteBuffer.limit()); //1024
System.out.println(byteBuffer.capacity()); //1024
System.out.println("***************************");
System.out.println("filp操作");
byteBuffer.flip();
System.out.println(byteBuffer.position()); //0
System.out.println(byteBuffer.limit()); //5
System.out.println(byteBuffer.capacity()); //1024
System.out.println("***************************");
System.out.println("get操作");
byte[] b=new byte[byteBuffer.limit()];
byteBuffer.get(b,0,4);
if(byteBuffer.hasRemaining()){ //判斷緩衝區是否有剩餘
System.out.println("緩衝區還有幾個剩餘的: "+byteBuffer.remaining());
}
System.out.println(byteBuffer.position()); //5
System.out.println(byteBuffer.limit()); //5
System.out.println(byteBuffer.capacity()); //1024
System.out.println("***************************");
System.out.println("rwind操作可重複讀"); //又把position置於開頭
byteBuffer.rewind();
System.out.println(byteBuffer.position()); //0
System.out.println(byteBuffer.limit()); //5
System.out.println(byteBuffer.capacity()); //1024
System.out.println("***************************");
System.out.println("mark操作記錄當前的position位置"); //又把position置於開頭
byteBuffer.mark();
byte[] bytes=new byte[1024];
byteBuffer.get(bytes,0,2); //取走前兩個,position爲2
System.out.println("取走前兩個,position爲"+byteBuffer.position()); //2
byteBuffer.reset(); //恢復到mark位置的position 爲0
System.out.println("恢復到mark位置的position 爲"+byteBuffer.position()); //0
System.out.println("***************************");
System.out.println("clear操作清空(非真清空,相當於把position和limit恢復開始狀態)數組中的數據依然存在"); //又把position置於開頭
byteBuffer.clear();
System.out.println(byteBuffer.position()); //0
System.out.println(byteBuffer.limit()); //1024
System.out.println(byteBuffer.capacity()); //1024
System.out.println("***************************");
//獲取第一個bytez轉化爲char還是能輸出
System.out.println("獲取第一個bytez轉化爲char還是能輸出"+(char)byteBuffer.get());
if(byteBuffer.hasRemaining()){ //判斷緩衝區是否有剩餘
System.out.println("緩衝區還有幾個剩餘的: "+byteBuffer.remaining());
}
ByteBuffer byteBuffer=ByteBuffer.allocateDirect(1024);//創建一個直接緩衝區
System.out.println(byteBuffer.isDirect());//判斷是否爲直接緩衝區
三. 通道
通道:由java.nio.channels包定義。
Channel表示IO源與目標打開的連接。
Channel類似於傳統的“流”。但其自身不能直接訪問數據,Channel只能與Buffer進行交互。
java.nio.channels.Channel 接口
-
FileChannel:用於讀取、寫入、映射和操作文件的通道。
-
SocketChannel:通過 TCP 讀寫網絡中的數據。
-
ServerSocketChannel:可以監聽新進來的 TCP 連接,對每一個新進來的連接都會創建一個 SocketChannel。
-
DatagramChannel:通過 UDP 讀寫網絡中的數據通道。
3.1 java針對支持通道的類提供了getChannel()方法
- 本地IO:
FileInputStream/FileOutputStream
long start=System.currentTimeMillis();
FileInputStream fis=null;
FileOutputStream fos=null;
//獲取通道
FileChannel inchannel = null;
FileChannel outchannel = null;
try {
fis = new FileInputStream("E:\\image/1.jpg");
fos = new FileOutputStream("E:\\image/3.jpg");
inchannel = fis.getChannel();
outchannel = fos.getChannel();
//設置非直接緩衝區大小
ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
while(inchannel.read(byteBuffer)!=-1){ //將通道里的數據存入緩衝區
byteBuffer.flip(); //切換讀取模式;
outchannel.write(byteBuffer); // //將緩衝區中的數據寫入通道中
byteBuffer.clear(); ////清空緩衝區
}
}catch (Exception e){
e.printStackTrace();
}finally{
if(outchannel!=null){
try {
outchannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(inchannel!=null){
try {
inchannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
long end=System.currentTimeMillis();
System.out.println("耗費時間:"+(end-start));//耗費時間:1094
RandomAccessFile
RandomAccessFile raf=new RandomAccessFile("E:\\image/1234.txt","rw");
//建立通道
FileChannel inChannel = raf.getChannel();
//創建緩存區數組 ->分散讀取
ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
ByteBuffer byteBuffer1=ByteBuffer.allocate(100);
ByteBuffer[] buffers={byteBuffer,byteBuffer1};
//讀取到緩存區
inChannel.read(buffers);
for (ByteBuffer buffer : buffers) {
buffer.flip(); //切換爲讀模式
}
//查看各自通道有多少
System.out.println(new String(buffers[0].array(),0,buffers[0].limit()));
System.out.println("****************************************************");
System.out.println(new String(buffers[1].array(),0,buffers[1].limit()));
//聚集寫入
RandomAccessFile raf2=new RandomAccessFile("E:\\image/12345.txt","rw");
FileChannel outChannel = raf2.getChannel();
outChannel.write(buffers);
inChannel.close();
outChannel.close();
- 網絡IO:
Socket
ServerSocket
DatagramSocket
3.2 在JDK 1.7 中的NIO.2 針對各個通道提供了靜態方法 open()
long start=System.currentTimeMillis();
//通過靜態方法 open()創建通道
FileChannel inChannel=FileChannel.open(Paths.get("E:\\image/1.jpg"), StandardOpenOption.READ); //讀模式
FileChannel outChannel=FileChannel.open(Paths.get("E:\\image/4.jpg"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE); //讀寫模式;存在就報錯,不存在就創建
//內存映射文件
MappedByteBuffer inMappedBuf=inChannel.map(FileChannel.MapMode.READ_ONLY,0,inChannel.size());
MappedByteBuffer outMappedBuf=outChannel.map(FileChannel.MapMode.READ_WRITE,0,inChannel.size());
//進行寫操作
byte[] bytes=new byte[inMappedBuf.limit()];
inMappedBuf.get(bytes);
outMappedBuf.put(bytes);
//關閉通道
inChannel.close();
outChannel.close();
long end=System.currentTimeMillis();
System.out.println("耗時: "+(end-start));
直接通過通道複製
long start=System.currentTimeMillis();
FileChannel inChannel=FileChannel.open(Paths.get("E:\\image/1.jpg"), StandardOpenOption.READ); //讀模式
FileChannel outChannel=FileChannel.open(Paths.get("E:\\image/4.jpg"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE); //讀寫模式;存在就覆蓋,不存在就創建
inChannel.transferTo(0,inChannel.size(),outChannel);
inChannel.close();
outChannel.close();
long end=System.currentTimeMillis();
System.out.println("耗時: "+(end-start));