管道能夠理解爲水管,能夠直接運輸水流(字節數據)java
管道能夠理解鐵路,須要依賴火車(緩衝區)才能運輸數據。linux
Java NIO系統的核心在於:通道/管道(Channel)和緩衝區(Buffer)。通道表示打開到I0設備(例如:文件、套接字)的鏈接。若須要使用NIO系統,須要獲取用於鏈接I0設備的通道以及用於容納數據的緩衝區。而後操做緩衝區,對數據進行處理。簡而言之,Channel負責傳輸,Buffer負責存儲編程
緩衝區就是數組,用戶存儲不一樣數據類型的數據,根據數據類型不一樣(boolean除外),提供了相應類型的緩衝區
ByteBuffer
CharBuffer
ShortBuffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBuffer
上述緩衝區的管理方式幾乎一致,經過allocate()獲取緩衝區,緩衝區存儲數據的連個核心方法 put()、get()windows
capacity:容量,表示緩衝區中最大存儲數據的容量。一旦聲明不能改變。 limit: 界限,表示緩衝區中能夠操做數據的大小。(limit後數據不能進行讀寫) position:位置,表示緩衝區中正在操做數據的位置。 mark:標記,表示記錄當前position的位置。能夠經過reset()恢復到mark的位置 0<=mark<=position<=limit<=capacity
public class Main { public static void main(String[] args) { //test1(); test2(); } public static void test1(){ ByteBuffer byteBuffer= ByteBuffer.allocate(1024); System.out.println("-----allocate-----"); System.out.println(byteBuffer.position()); System.out.println(byteBuffer.limit()); System.out.println(byteBuffer.capacity()); byteBuffer.put("abcde".getBytes()); System.out.println("-----put()-----"); System.out.println(byteBuffer.position()); System.out.println(byteBuffer.limit()); System.out.println(byteBuffer.capacity()); //切換讀取模式 byteBuffer.flip(); System.out.println("-----flip()-----"); System.out.println(byteBuffer.position()); System.out.println(byteBuffer.limit()); System.out.println(byteBuffer.capacity()); System.out.println("-----get()-----"); byte[] bytes = new byte[byteBuffer.limit()]; byteBuffer.get(bytes);//若是bytes的空間大於byteBuffer.limit(),會報錯 System.out.println(new String(bytes,0,byteBuffer.limit())); System.out.println(byteBuffer.position()); System.out.println(byteBuffer.limit()); System.out.println(byteBuffer.capacity()); //可重複讀取數據 byteBuffer.rewind(); System.out.println("-----rewind()-----"); System.out.println(byteBuffer.position()); System.out.println(byteBuffer.limit()); System.out.println(byteBuffer.capacity()); //清空緩衝區(緩衝區的數據並無真正意義上的清空,但處於被遺忘的狀態) byteBuffer.clear(); System.out.println("-----clear()-----"); System.out.println(byteBuffer.position()); System.out.println(byteBuffer.limit()); System.out.println(byteBuffer.capacity()); } public static void test2(){ ByteBuffer byteBuffer = ByteBuffer.allocate(1024); byteBuffer.put("abcde".getBytes()); byteBuffer.flip(); byte[] bytes = new byte[5]; byteBuffer.get(bytes, 0, 2); byteBuffer.mark(); System.out.println(new String(bytes,0,2)); System.out.println(byteBuffer.position()); byteBuffer.get(bytes, 0, 2); //position又回到了mark標記的配置 byteBuffer.reset(); byteBuffer.get(bytes, 0, 2); System.out.println(new String(bytes,0,2)); System.out.println(byteBuffer.position()); } }
非直接緩衝區:經過allocate()方法分配緩衝區,將緩衝區創建在JVM的內存中數組
直接緩衝區:經過allocateDirect()方法分配直接緩衝區,將緩衝區創建在物理內存中。能夠提升效率緩存
內核空間與用戶空間
內核空間主要指操做系統用於程序調度、虛擬內存的使用或者鏈接硬件資源等的程序邏輯。爲了保證操做系統的穩定向,運行在操做系統中的用戶進程不能訪問操做系統所使用的內存空間。若是用戶程須要訪問硬件資源,如網絡鏈接等,能夠調用操做系統提供的接口來實現,這個接口的調用其實也是系統調用。每次系統調用都會存在兩個內存空間的切換,一般的網絡傳輸也是一次系統調用,經過網絡傳輸的數據先是從內核空間從遠程主機接受數據,而後再從內核空間複製到用戶空間,供程序使用。這種複製手段很費時,雖然包住了程序運行時的安全性與穩定性,可是也犧牲了部分效率。如今linux系統上提供了sendfile文件傳輸方式來減小這種複製方式的成本。
內核空間和用戶空間大小分配也是個須要權衡的問題,若是是一臺登陸服務器要分配更多的內核空間,由於沒有個登陸用戶操做系統都會初始化一個用戶進程,這個進程大部分在內核空間運行。當前windows內核:用戶爲1:1(也就是大約2G內核空間,2G用戶空間),linux爲1:3。安全
補充參考:https://blog.csdn.net/u012129558/article/details/82878994服務器
DMA技術的重要性在於,利用它進行數據傳送時不須要CPU的參與。每臺電腦主機板上都有DMA控制器,一般計算機對其編程,並用一個適配器上的ROM(如軟盤驅動控制器上的ROM)來儲存程序,這些程序控制DMA傳送數據。一旦控制器初始化完成,數據開始傳送,DMA就能夠脫離CPU,獨立完成數據傳送。網絡
參考:https://baike.baidu.com/item/DMA%E9%80%9A%E9%81%93/7492727?fr=aladdin多線程
通道(Channe1):用於源節點與目標節點的鏈接。在Java NIO中負責緩衝區中數據的傳輸。Channe1自己不存儲數據,所以須要配合緩衝區進行傳輸。
注意FileChannel不能切換非堵塞模式,經過上面的圖能夠看出SelectableChannel(監聽器),下面沒有FileChannel
1.Java針對支持通道的類提供了getChanne1()方法
本地IO:
FileInputStream/FileOutputStream
RandomAccessFile
網絡IO:
Socket
ServerSocket
DatagramSocket
2.在JDK1.7中的NIO.2針對各個通道提供了靜態方法open()
3.在JDK 1.7中的NIO.2的Files 工具類的newByteChannel()
public static void main(String[] args) { String from_file = "C:\\Users\\zhengyan\\Desktop\\test1\\x.txt"; String to_file = "C:\\Users\\zhengyan\\Desktop\\test1\\t.txt"; copyFile(from_file,to_file); } private static void copyFile(String from_file, String to_file) { try { //建立輸入文件通道 FileChannel fcIn = new FileInputStream(from_file).getChannel(); //建立輸出文件通道 FileChannel fcOut = new FileOutputStream(to_file).getChannel(); //建立緩衝區 ByteBuffer buf = ByteBuffer.allocate(1024); while(fcIn.read(buf)!=-1){ buf.flip(); fcOut.write(buf); buf.clear(); } fcIn.close(); fcOut.close(); System.out.println("copy successful"); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
public static void main(String[] args) throws IOException { String from_file = "C:\\Users\\zhengyan\\Desktop\\test1\\x.txt"; String to_file = "C:\\Users\\zhengyan\\Desktop\\test1\\t.txt"; copyFile(from_file,to_file); } private static void copyFile(String from_file, String to_file) throws IOException { FileChannel inchannel = FileChannel.open(Paths.get(from_file), StandardOpenOption.READ); FileChannel outchannel = FileChannel.open(Paths.get(to_file), StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE); //內存映射文件 MappedByteBuffer inByteBuffer = inchannel.map(FileChannel.MapMode.READ_ONLY, 0, inchannel.size()); MappedByteBuffer outByteBuffer = outchannel.map(FileChannel.MapMode.READ_WRITE, 0, inchannel.size()); //直接對緩衝區進行數據讀寫操做 byte bytes[] = new byte[1024]; for(int i=0;i<inchannel.size();i++){ outByteBuffer.put(inByteBuffer.get()); } inchannel.close(); outchannel.close(); }
public static void main(String[] args) throws IOException { String from_file = "C:\\Users\\zhengyan\\Desktop\\test1\\x.txt"; String to_file = "C:\\Users\\zhengyan\\Desktop\\test1\\t.txt"; copyFile(from_file,to_file); } private static void copyFile(String from_file, String to_file) throws IOException { FileChannel inchannel = FileChannel.open(Paths.get(from_file), StandardOpenOption.READ); FileChannel outchannel = FileChannel.open(Paths.get(to_file), StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE); inchannel.transferTo(0,inchannel.size(),outchannel); inchannel.close(); outchannel.close(); }
public static void main(String[] args) throws IOException, InterruptedException { String from_file = "C:\\Users\\zhengyan\\Desktop\\test1\\x.txt"; String to_file = "C:\\Users\\zhengyan\\Desktop\\test1\\t.txt"; copyFile(from_file,to_file); } private static void copyFile(String from_file, String to_file) throws IOException, InterruptedException { RandomAccessFile r = new RandomAccessFile(from_file, "rw"); RandomAccessFile rw = new RandomAccessFile(to_file, "rw"); FileChannel rChannel = r.getChannel(); FileChannel rwChannel = rw.getChannel(); ByteBuffer byteBuffer1 = ByteBuffer.allocate(1); ByteBuffer byteBuffer2 = ByteBuffer.allocate(1024); //分散讀取數據 ByteBuffer byteBuffers[] = {byteBuffer1,byteBuffer2}; rChannel.read(byteBuffers); while (rChannel.read(byteBuffers)!=-1){ for (ByteBuffer byteBuffer:byteBuffers){ byteBuffer.flip(); } //聚合寫入數據 rwChannel.write(byteBuffers); for (ByteBuffer byteBuffer:byteBuffers){ byteBuffer.clear(); } } r.close(); rw.close(); }
private static void charsettest() throws IOException { Charset charset = Charset.forName("UTF-8"); ByteBuffer estr = charset.encode("sdfsd的df"); //estr.flip(); CharBuffer decode = charset.decode(estr); System.out.println(decode.toString()); estr.rewind(); Charset gbk = Charset.forName("GBK"); CharBuffer decode1 = gbk.decode(estr); //會出現亂碼 System.out.println(decode1.toString()); }
注意堵塞和非堵塞以及同步和異步的區別
網絡過程當中IO堵塞
假如服務器只有一個線程來處理用戶請求,因爲某種緣由(數據還沒到達)形成線程堵塞(線程放棄了CPU執行權),此時若是有其餘的用戶請求,該線程就不能及時的處理該請求。傳統的解決方式就是開一個線程池,多線程來處理用戶請求,可是這樣可能依然會形成堵塞的狀況。
NIO解決非堵塞
利用的是select選擇器。將用戶的請求註冊到select上,select來監聽全部的請求數據(經過單獨的一個線程),若是請求的數據準備完畢,纔將該請求任務分配到服務器的一個或者多個線程上執行。
服務端
//服務端 public class BlockNIOServer { public static void main(String[] args) throws IOException, InterruptedException { //獲取通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); //綁定端口 serverSocketChannel.bind(new InetSocketAddress(8090)); while (true){ //獲取客戶端鏈接通道 SocketChannel socketChannel = serverSocketChannel.accept(); System.out.println("鏈接成功,等待用戶發送數據"); SocketAddress remoteAddress = socketChannel.getRemoteAddress(); String s = remoteAddress.toString(); String[] split = s.split(":"); //寫入本地 String path = "C:\\Users\\zhengyan\\Desktop\\test1\\"+split[1]+".txt"; FileChannel fileChannel = FileChannel.open(Paths.get(path), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE); //接受客戶端的數據而且寫入文件 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); while (socketChannel.read(byteBuffer)!=-1){ byteBuffer.flip(); fileChannel.write(byteBuffer); byteBuffer.clear(); } //能夠給客戶端提供反饋信息 byteBuffer.put("數據已經接受完畢...".getBytes()); byteBuffer.flip(); socketChannel.write(byteBuffer); fileChannel.close(); socketChannel.close(); System.out.println("寫入數據成功...."); } } }
客戶端
//客戶端 public class BlockNIOClient { public static void main(String[] args) throws IOException, InterruptedException { //獲取通道 SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8090)); FileChannel fileChannel = FileChannel.open(Paths.get("C:\\Users\\zhengyan\\Desktop\\test1\\x.txt"), StandardOpenOption.READ); //System.out.println("模擬10秒以後發送數據..."); //能夠開啓兩個客戶端,一個睡10秒發送數據(先請求),一個不用睡眠(後請求),發現,必須等第一個用戶處理完畢以後,第二個用戶才能夠被處理 //Thread.sleep(10000); //分配緩衝區大小 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); //讀取本地文件發送到服務器 while (fileChannel.read(byteBuffer)!=-1){ byteBuffer.flip(); socketChannel.write(byteBuffer); byteBuffer.clear(); } //告訴服務器,個人數據已經發送完畢 socketChannel.shutdownOutput(); //接受服務器返回來的消息 StringBuffer stringBuffer = new StringBuffer(); int len =-1; while ((len=socketChannel.read(byteBuffer))!=-1){ byteBuffer.flip(); stringBuffer.append(new String(byteBuffer.array(),0,len)); byteBuffer.clear(); } System.out.println(stringBuffer); socketChannel.close(); fileChannel.close(); } }
服務端
//服務端 public class BlockNIOServer { public static void main(String[] args) throws IOException, InterruptedException { //獲取通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); //切換非阻塞模式 serverSocketChannel.configureBlocking(false); //綁定端口 serverSocketChannel.bind(new InetSocketAddress(8090)); //獲取選擇器 Selector selector = Selector.open(); //將該通道註冊到select中,讓select監聽該通道的鏈接是否準備就緒 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); Iterator<SelectionKey> iterator = null; //經過選擇器輪詢獲取已經準備就緒的事件 while (selector.select()>0){ iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()){ SelectionKey selectionKey = iterator.next(); //若是獲取的是準備鏈接就緒的事件 if (selectionKey.isAcceptable()){ System.out.println("有客戶端已經準備好鏈接了...."); //開始接受鏈接客戶端 SocketChannel accept = serverSocketChannel.accept(); //切換非阻塞模式 accept.configureBlocking(false); //將通道註冊到selector中,讓select監聽該通道的數據是否準備就緒 accept.register(selector,SelectionKey.OP_READ); } else if (selectionKey.isReadable()){ SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); Random random = new Random(); int i = random.nextInt(100); String path = "C:\\Users\\zhengyan\\Desktop\\test1\\"+i+".txt"; FileChannel fileChannel = FileChannel.open(Paths.get(path), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); while (socketChannel.read(byteBuffer)!=-1){ byteBuffer.flip(); fileChannel.write(byteBuffer); byteBuffer.clear(); } byteBuffer.put("數據已經接受完畢...".getBytes()); byteBuffer.flip(); socketChannel.write(byteBuffer); fileChannel.close(); socketChannel.close(); System.out.println("寫入數據成功...."); } //取消選擇鍵 iterator.remove(); } } } }
客戶端
//客戶端 public class BlockNIOClient { public static void main(String[] args) throws IOException, InterruptedException { //獲取通道 SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8090)); FileChannel fileChannel = FileChannel.open(Paths.get("C:\\Users\\zhengyan\\Desktop\\test1\\x.txt"), StandardOpenOption.READ); //System.out.println("模擬10秒以後發送數據..."); //能夠開啓兩個客戶端,一個睡10秒發送數據(先請求),一個不用睡眠(後請求),發現,必須等第一個用戶處理完畢以後,第二個用戶才能夠被處理 //Thread.sleep(20000); //分配緩衝區大小 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); //讀取本地文件發送到服務器 while (fileChannel.read(byteBuffer)!=-1){ byteBuffer.flip(); socketChannel.write(byteBuffer); byteBuffer.clear(); } //告訴服務器,個人數據已經發送完畢 socketChannel.shutdownOutput(); //接受服務器返回來的消息 StringBuffer stringBuffer = new StringBuffer(); int len =-1; while ((len=socketChannel.read(byteBuffer))!=-1){ byteBuffer.flip(); stringBuffer.append(new String(byteBuffer.array(),0,len)); byteBuffer.clear(); } System.out.println(stringBuffer); socketChannel.close(); fileChannel.close(); } }
服務端
//服務端 public class BlockNIOServer { public static void main(String[] args) throws IOException, InterruptedException { //獲取通道 DatagramChannel datagramChannel = DatagramChannel.open(); //切換非阻塞模式 datagramChannel.configureBlocking(false); //綁定端口 datagramChannel.bind(new InetSocketAddress(8090)); //獲取選擇器 Selector selector = Selector.open(); //只須要監聽數據是否到來 datagramChannel.register(selector, SelectionKey.OP_READ); while (selector.select()>0){ Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()){ SelectionKey selectionKey = iterator.next(); if (selectionKey.isReadable()){ DatagramChannel channel = (DatagramChannel) selectionKey.channel(); ByteBuffer buf=ByteBuffer.allocate(1024); channel.receive(buf); buf.flip(); System.out.println(new String(buf.array(),0,buf.limit())); } //取消選擇鍵 iterator.remove(); } } } }
客戶端
//客戶端 public class BlockNIOClient { public static void main(String[] args) throws IOException{ DatagramChannel datagramChannel = DatagramChannel.open(); ByteBuffer buf=ByteBuffer.allocate(1024); buf.put(new Date().toString().getBytes()); buf.flip(); datagramChannel.send(buf,new InetSocketAddress("127.0.0.1",8090)); buf.clear(); datagramChannel.close(); } }
Java NIO 管道是兩個線程之間的單向數據鏈接。Pipe有一個source通道和一個sink通道。數據會被寫到sink通道,從source通道讀取。
public class PipeTest { public static void main(String[] args) throws IOException, InterruptedException { //獲取管道 Pipe pipe = Pipe.open(); new Thread(new MyThread1(pipe)).start(); Thread.sleep(3000); new Thread(new MyThread2(pipe)).start(); } } class MyThread1 implements Runnable{ private Pipe pipe; public MyThread1(Pipe pipe){ this.pipe = pipe; } @Override public void run() { Pipe.SinkChannel sink = pipe.sink(); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); byteBuffer.put("ssss".getBytes()); byteBuffer.flip(); try { sink.write(byteBuffer); } catch (IOException e) { e.printStackTrace(); } byteBuffer.clear(); } } class MyThread2 implements Runnable{ private Pipe pipe; public MyThread2(Pipe pipe){ this.pipe = pipe; } @Override public void run() { ByteBuffer byteBuffer = ByteBuffer.allocate(1024); Pipe.SourceChannel source = pipe.source(); try { source.read(byteBuffer); } catch (IOException e) { e.printStackTrace(); } System.out.println(new String(byteBuffer.array(),0,byteBuffer.limit())); } }