前言:擼基礎篇系列,避免每次都要從頭開始看,寫個自己的知識體系樹
NIO 核心就是異步, 比如,復制文件,讓操作系統去處理,等通知
BIO核心類
一,BIO
NIO基本操作類
Bytebuffer
構建:
用JVM的內存構建:
ByteBuffer.allocate(bufferSize)
用JVM的直接內存構建:
ByteBuffer.allocateDirect(bufferSize)
內存結構:
flip()后-->
postion, limit,和captain的處理工具類
flip() 如上, postion=0, limit = wirtCont, captian不變
mark()與reset()方法連用,mark一個postion后,可以通過reset方法返回到原來的訂單
緩存的數據處理類
clear() 方法會清空整個緩沖區。
compact()方法只會清除已經讀過的數據(0到posstion)
CharSet
用于構建String 和ByteBuffer,以及編碼的的一個轉換類
構建:
Charset charSet = Charset.forName("gbk");
charSet.decode(butBf) , 用于byteBuffer to String
charSet.encode("測試下") 用于String to byteBuffer
public static void main(String[] args) throws Exception { FileInputStream finput = new FileInputStream("E://test.txt"); FileChannel fchannel = finput.getChannel(); ByteBuffer byteBf = ByteBuffer.allocate(1024); fchannel.read(byteBf); Charset charSet = Charset.forName("utf-8"); byteBf.flip(); System.out.println(charSet.decode(byteBf)); }
網絡NIO基本操作
selector
一個輪詢,實現了 SelectableChannel 接口的都可以在select上添加感興趣的事件
NBlocking IO,網絡服務
/** * selector 只是一個通知,具體各個部分自行處理 * * NIO , 不阻塞,當有東西來了就開始通知處理,不然能一直select到· * * @author hejb * */ public class Test { public static void main(String[] args) throws Exception { ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 阻塞為false serverSocketChannel.configureBlocking(false); // 綁定IP和端口 serverSocketChannel.socket().bind(new InetSocketAddress("0.0.0.0", 8080)); Selector selector = Selector.open(); // 注冊感興趣事件到selector serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (selector.select() != 0) { Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) { // 循選中事件 SelectionKey selecttionKey = iterator.next(); // 刪除已經處理 iterator.remove(); if (selecttionKey.isAcceptable()) { // 返回注冊該事件時的channel ,即SelectableChannel ServerSocketChannel channel = (ServerSocketChannel) selecttionKey.channel(); // 有連接事件來了, 可以處理接收請求了,注意如果不進行accept,select.select()一直能輪詢到東西 // 接收后返回了個socketchannel,開始配置 SocketChannel socketChannel = channel.accept(); // 也配置成非阻塞處理 socketChannel.configureBlocking(false); // 復用同一個selector上注冊感興趣的事件,并注冊感興趣的可讀事件 socketChannel.register(selector, selecttionKey.OP_READ); } // 如果來可以可讀事件 if (selecttionKey.isReadable()) { // 返回注冊該事件時的channel ,即實現了SelectableChannel的 SocketChannel socketChannel = (SocketChannel) selecttionKey.channel(); // 后面就都是通過byteBuffer和channel來讀操作了 ByteBuffer byteBf = ByteBuffer.allocate(1024); socketChannel.read(byteBf); Charset charset = Charset.forName("utf-8"); byteBf.flip(); System.out.println("clinet :" + charset.decode(byteBf)); // socket是雙通道,故也可以直接返回東西了 socketChannel.write(charset.encode("test only")); } } } } // 有個疑問,NIO的鏈接什么時候關閉呢,有文章說是在finalize方法里,待我去找找 }
那么什么時候用IO,什么時候用NIO呢?
大量鏈接涌入的時候,傳的數據比較少,然后處理時間比較長,的時候適合NIO(偏向IO密集型)
如果傳入的鏈接比較少,然后傳輸數據量大,比如文件上傳之類,適合BIO
NIO的網絡模型:
用一個thread(selector)做服務接收,和鏈接的維持
IO的網路模型
用一個thread做服務接收,其它每個鏈接都用一條線程保持
文件操作流部分
NIO主要是引入了緩沖區和映射來操作文件
JAVA操作文件的過程
1, JAVA讀取-> 2,調用native讀取--> 3, 調用系統讀取(切換到內核態)-> 4,磁盤讀取文件
每一層都有設置緩存, JAVA操作上的分類
先說緩沖區,以前的IO中也有緩沖區,如 BufferedInputStream
傳統BIO的緩存操作
BIO流的設計,先看連個讀取文件例子
沒有緩存的FileInputStream
public static String readFileWithoutBuffer() throws IOException { // BIO inputstream without buffer FileInputStream input = new FileInputStream("E://test.txt"); byte[] content = new byte[1024]; int length = input.read(content); input.close(); return new String(content, 0, length); }
讀取文件時,read的實現是直接通過調用系統的讀取數據(native),就是每次讀取時都要調用到系統讀取。
帶緩存的BufferedInputStream
public static String readFileWithBuffer() throws IOException { // BIO inputstream with buffer FileInputStream input = new FileInputStream("E://test.txt"); BufferedInputStream binpt = new BufferedInputStream(input); byte[] bufferContent = new byte[1024]; int length = binpt.read(bufferContent); return new String(bufferContent, 0, length); }
BufferedInputStream是通過內部緩沖區數組實現的緩沖,每次讀取時先返回內部數組的數據,默認大小是8192。
具體點用了Decorator模式,繼承自FileInputStream ,(覆蓋)裝飾read方法
代碼段:
public synchronized int read() throws IOException { if (pos >= count) { fill(); // 如果讀取大于緩存了,填充緩沖buff if (pos >= count) return -1; } return getBufIfOpen()[pos++] & 0xff; }
結論:
BufferedInputStream 的作用不是減少 磁盤IO操作次數(這個OS已經幫我們做了),而是通過減少系統調用次數來提高性能的。
NIO的文件映射操作
總結下JAVA文件操作
順序讀取
1, InputStream/OutputStream - 直接調用native方法
2, BufferedInputStream/BufferedOutputStream- 在類里維護了個byte[],先緩存讀取到byte中(裝飾器的模式)
隨機讀取
3,RandomAccessFile - 直接調用native方法
4,FileChannel -系統的虛擬內存映射文件,講文件映射到內存中當數組操作
各種文件讀取適用場景
--轉個實驗
在Core Java II中進行了這么一個實驗:在同一臺機器上,對JDK的jre/lib目錄中的37MB的rt.jar文件分別用以上四種操作來計算CRC32校驗和,記錄下了如下時間
方法 | 時間 |
普通輸入流 | 110s |
帶緩沖的輸入流 | 9.9s |
隨機訪問文件 | 162s |
內存映射文件 | 7.2s |
這個小實驗也驗證了內存映射文件這個方法的可行性,由于具有隨機訪問的功能(映射在內存數組),所以常用來替代RandomAccessFile。
當然,對于中等尺寸文件的順序讀入則沒有必要使用內存映射以避免占用本就有限的I/O資源,這時應當使用帶緩沖的輸入流。
歡迎關注我的公眾號, 一起來構建我們的知識體系
文章列表
留言列表