日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java NIO_I/O基本概念_Java中的缓冲区(Buffer)_通道(Channel)_网络I/O

發布時間:2024/7/5 java 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java NIO_I/O基本概念_Java中的缓冲区(Buffer)_通道(Channel)_网络I/O 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

I/O基本概念

緩沖區基礎

緩沖區是I/O的基礎, 進程使用read(), write()將數據讀出/寫入從緩沖區中; 當緩沖區寫滿, 內核向磁盤發出指令, 將緩沖區中數據寫入磁盤中(這一步不需要CPU), 當磁盤控制器將緩沖區裝滿, 內核將緩沖區數據拷貝到進程中指定的緩沖區; 操作如下圖:

當中忽略了很多細節, 只涉及簡單的步驟

上面的進程通常是用戶進程, 需要指出的一點就是, 當內核接受到read指令的時候, 首先會去內核內部的緩沖區尋找所需數據, 如果所需的數據不在緩沖區中, 那么用戶進程將被掛起, 直到緩沖區中存在數據, 最后內核再將緩沖區中數據拷貝到進程內部的緩沖區中.
注:
1.使用內核的意義:

1. 用戶進程不能直接訪問磁盤, 需要使用中間層進行訪問; 2. 磁盤中的數據總是塊狀, 而用戶需要的數據可能是任意大小的; 3. 內核充當的中間人的角色;

2.讀取過程存在的優化方案:

1.進程將所有緩沖區的內存地址交給內核. 進程read操作: 內核根據緩沖區地址, 將數據發散到進程中每個緩沖區的; 進程write操作: 將進程中每個緩沖區的數據集聚, 然后一起存入內核緩沖區; 這樣做避免了每一次讀寫操作都要進行磁盤操作, 減少性能損耗 2.使用虛擬內存. 將內核緩沖區地址與進程緩沖區地址映射到同一片虛擬內存上面, 這樣當**磁盤控制器**操控內核緩沖區的時候就等價于操控進程緩沖區, 整個過程避免了拷貝操作這樣做的前提是內核和用戶的緩沖區必須使用相同的頁對齊(固定的大小字節組,一般為512字節大小)具體見下圖: 3.使用分頁技術進行操作系統I/O:a.確定請求數據在文件系統的哪些磁盤區域, 這些數據可能橫跨多個文件系統, 且位置不連續b.內核空間分配足夠多的內存頁(緩沖區), 用于容納確定的文件系統頁'c.建立內存頁與磁盤文件之間的聯系, 二者建立映射d.為每一個內存頁進行檢查e.根據d操作中的檢查結果, 決定每個內存頁是否執行讀寫操作f.從磁盤中讀取文件內容, 將數據導入, 文件系統對導入數據進行解析

進程與內核的緩沖區共享同一片內存區域

Java中的緩沖區(Buffer)

緩沖區基礎

  • 屬性:
容量(Capacity): 緩沖區能容納數據元素的最大量 上界(Limit): 代表緩沖區中最后一個元素的位置, 也代表著緩沖區中元素個數 位置(Position): 下一個被讀寫位置的索引 標記(Mark): 記錄位置 屬性范圍大小: 0 <= mark <= position <= limit <=capacity
  • 存取
    Buffer內部使用get(),put()函數進行數據存儲, get/put當index位置超出范圍,拋BufferOverflowException
  • 轉置: 使緩沖區的內容逆置, 只需要修改position與limit指向的位置, 讓position執行末尾
  • 清空緩沖區: 使用clear(), clear并沒有改變緩沖區中元素, 他所改變的就是設定了limit值, 并讓其指向0號位置
  • 復制緩沖區內容:
    當緩沖區屬性為只讀, 那么復制緩沖區內容就是淺復制, 對一個緩沖區的改變會反映到另外一個緩沖區上面, 新的緩沖區將會繼承舊的緩沖區所有的屬性. 當對只讀緩沖區進行put操作, 將拋ReadOnlyBufferException異常
    注:
    如果只讀緩沖區與可寫緩沖區共享一片內存區域, 可寫緩沖區進行改變(如put操作), 這種改變將會體現在只讀緩沖區上面; 就所謂的淺復制

字節緩沖區

  • 使用字節緩沖區作為通道執行I/O操作的源和目標, 而向通道中傳遞一個非ByteBuffer對象的時候將會出現如下的問題:
1.創建一個臨時的ByteBuffer對象 2.將目標對象內容復制到臨時的ByteBuffer中 3.使用臨時ByteBuffer進行I/O操作 4.結束操作, 回收無用數據

上面的過程導致的問題就是嚴重性能損耗, 當非ByteBuffer對象很多的時候

通道(Channel)

Channel基本概述

通道使用ByteBuffer作為端點, 使文件系統, 進程, 網絡等等進行交互, 這種交互總是最小的開銷(通道只支持字節操作);
如下圖: FileSystem與NetWork的I/O操作是通過Channel執行

  • 打開通道:
    通道分為2種類型(File, Socket), File: FileChannel, Socket: SocketChannel, ServerSocketChannel, DatagramChannel;
    獲得方式:
    1.FileChanne fc = new FileInputStream(new File(PATH)).getChannel();
    //文件有FileInputStream, FileOutputStream, RandomAccessFile
    2.SocketChannel sc = SocketChannel.open();
    其余Socket的Channel同2
    注: 在java.net中的socket使用getChannel獲得Channel, 但這樣的Channel并不是新通道(它永運不會創建新通道), 只有存在一個與Socket關聯的通道, 這樣獲得的才是新通道, 否則就是一個假通道

  • 使用通道:
    通道間的數據可以是單向的也可以是雙向的, 默認的ByteChannel接口實現的是雙向數據傳輸, 但是遇到這樣的問題的時候雙向傳輸將會拋異常: FileInputStream的getChannel獲得的FileChannel對象是只讀的, 但是由于FileInputStream實現了ByteChannel接口, 因此可以調用read, write操作, 當這個管道調用write將會拋未經檢查異常NonWritableChannelException;
    通道可以是阻塞, 或非阻塞; 非阻塞: 通道永遠不會讓調用線程休眠, 請求的操作立即完成, 要么返回獲得的數據, 要么返回未獲得數據;
    注: 只有sockets, pipes才能使用非阻塞模式

  • 關閉通道:
    使用close()關閉, 關閉通道將會導致底層I/O服務線程暫時阻塞, 即使該通道處于非阻塞模式, close多次調用沒有影響(close也會阻塞, 對已經關閉的通道使用close不會產生任何操作, 只會立即返回); 關閉的時候可以使用isOpen()判斷通道開放狀態. 對于已經關閉的Channel使用讀寫都將拋CloseChannelException
    注:
    1.當通道實現了InterruptibleChannel接口, 那么當某一線程在該通道上阻塞并且被中斷, 那么該通道將被關閉, 被阻塞的線程拋ClosedByInterruptException(在Selectors上阻塞的中斷線程不會導致通道關閉); InterruptibleChannel的檢查手段是通過isInterrupted()判斷線程的interrupt status
    看似上面這種操作過于苛刻, 線程阻塞且中斷就關閉對應的Channel, 但這完全是考慮因為操作系統而導致的I/O問題, 增強程序健壯性.
    2.中斷的線程可以使用異步關閉, 實現了InterruptibleChannel的線程接口的通道可以在任何時候被關閉, 一個通道關閉的時候在這個通道上的所有阻塞的線程都將被喚醒并接受到一個AsynchronousCloseException
    3.不實現InterruptibleChannel接口的通道通常不進行底層特殊操作, 這些通道永遠不會出現阻塞的問題

  • Scatter/Gather
    此處的Scatter/Gather就與緩沖區基礎中讀取優化中的第2點一樣, 在多個緩沖區上實現一個I/O操作.
    Scatter: 對于進程read, 從通道讀取的數據會按順序散布到多個緩沖區, 直到緩沖區或通道中數據用完.
    Gather: 對于進程write, 數據從多個緩沖區按順序抽取, 然后將數據放入通道中.
    下面使用Gather演示從緩沖區中獲取數據寫入管道中(Scatter與Gather相反, 就是將管道中數據存入緩沖區):
    緩沖區中元素根據Position, Limit定位每個緩沖區中取到的字符串, 使用標號對應記錄緩沖區內容在Channel中順序

    優點: 避免數據的來回拷貝, 可以按照不同的方式組合緩沖區數據的引用
    下面給出演示代碼:

package com.demo1;import java.io.FileOutputStream; import java.nio.ByteBuffer; import java.nio.channels.GatheringByteChannel; import java.util.LinkedList; import java.util.List; import java.util.Random; public class JavaNIOGatherTest{private static final String DEMOGRAPHIC = "c:\\Users\\regotto\\Desktop\\blahblah.txt";public static void main (String [] argv) throws Exception{//默認Buffer10個大小int reps = 10;if (argv.length > 0) {reps = Integer.parseInt (argv [0]);}FileOutputStream fos = new FileOutputStream (DEMOGRAPHIC);GatheringByteChannel gatherChannel = fos.getChannel( );ByteBuffer [] bs = utterBS (reps);//讀取到文件末尾, 自動停止writewhile (gatherChannel.write (bs) > 0) {}System.out.println ("Mindshare paradigms synergized to "+ DEMOGRAPHIC);fos.close( );}//模擬緩沖區內容private static String [] col1 = {"Aggregate", "Enable", "Leverage","Facilitate", "Synergize", "Repurpose","Strategize", "Reinvent", "Harness"};private static String [] col2 = {"cross-platform", "best-of-breed", "frictionless","ubiquitous", "extensible", "compelling","mission-critical", "collaborative", "integrated"};private static String [] col3 = {"methodologies", "infomediaries", "platforms","schemas", "mindshare", "paradigms","functionalities", "web services", "infrastructures"};//System.getProperty("line.separator");獲取換行private static String newline = System.getProperty ("line.separator");//獲取緩沖區內容private static ByteBuffer [] utterBS (int howMany)throws Exception{List list = new LinkedList( );for (int i = 0; i < howMany; i++) {//緩沖區中每一個字符串都隨機給一個Position, Limitlist.add (pickRandom (col1, " "));list.add (pickRandom (col2, " "));list.add (pickRandom (col3, newline));}ByteBuffer [] bufs = new ByteBuffer [list.size( )];list.toArray (bufs);return (bufs);}private static Random rand = new Random( );private static ByteBuffer pickRandom (String [] strings, String suffix)throws Exception{String string = strings [rand.nextInt (strings.length)];int total = string.length() + suffix.length( );//為每一個字符串分配對應total容量的ByteBufferByteBuffer buf = ByteBuffer.allocate (total);//編碼轉換buf.put (string.getBytes ("US-ASCII"));buf.put (suffix.getBytes ("US-ASCII"));buf.flip( );return (buf);} }運行結果如下(txt文件中存儲的內容): 字符串順序是隨機的, 前面代碼中使用隨機數取Position, Limit Enable compelling methodologies Enable frictionless platforms Facilitate cross-platform paradigms Reinvent collaborative platforms Repurpose extensible infomediaries Strategize integrated paradigms Strategize ubiquitous platforms Leverage collaborative infomediaries Harness collaborative schemas Harness extensible paradigms

阻塞, 非阻塞; 同步, 異步簡述

  • 同步, 異步, 阻塞, 非阻塞簡述:
    同步和異步是相對于操作結果來說,會不會等待結果返回。
    阻塞和非阻塞是相對于線程是否被阻塞。

  • 這兩者存在本質的區別:
    它們的修飾對象是不同的。阻塞和非阻塞是指進程訪問的數據如果尚未就緒,進程是否需要等待,簡單說這相當于函數內部的實現區別,也就是未就緒時是直接返回還是等待就緒。
    而同步和異步是指訪問數據的機制,同步一般指主動請求并等待I/O操作完畢的方式,當數據就緒后在讀寫的時候必須阻塞,異步則指主動請求數據后便可以繼續處理其它任務,隨后等待I/O,操作完畢的通知,這可以使進程在數據讀寫時也不阻塞。

網絡I/O

  • I/O模型:
    輸入操作包含:等待數據; 從內核向進程復制數據; 對于Socket, 先是等待數據從網絡到達, 隨后將到達的數據復制到內核的緩沖區, 最終將內核緩沖區數據復制到應用進程緩沖區

  • Unix的5種I/O模型

阻塞式IO: 阻塞當前應用, 直到數據從內核緩沖區復制到應用進程緩沖區才返回 非阻塞式IO:內核返回數據錯誤, 應用程序依舊執行, 但每隔一段時間就要執行系統調用IO是否完成,輪詢(polling).當內核有數據時就通知應用等待進行復制(內核復制過程仍具有阻塞)調用更多的底層, CPU利用率低 IO復用(select, poll): 使用select/poll等待數據, 采用多個socket進行等待, 其中一個出現變為可讀,就將內核數據復制到進程中(此過程將會出現阻塞狀態), 這種處理方式使得當個線程具有多個IO處理能力, 體現了IO的復用,又稱為事件驅動IO 信號驅動式IO(SIGIO):應用程序使用sigaction系統調用, 在等待數據階段應用進程是非阻塞的, 當內核獲得數據就向應用進程發送SIGIO信號, 通知程序處理數據的復制過程(此過程將會出現阻塞狀態).相比于非阻塞式IO, 信號驅動IO的CPU利用率更高 異步IO:應用程序使用aio_read系統調用, 應用繼續執行, 處理其他數據(不存在阻塞狀態), 當內核完成所有需要的IO操作, 再通知應用程序可直接獲取自身進程緩沖區數據, 也就不存在復制過程的阻塞情況###相對于信號驅動IO, 異步IO的信號處理是在IO已經完成的時候, 而信號驅動是在IO開始的時候

模型圖例如下:

  • 5種IO模型比較
同步IO: 將數據從內核緩沖區復制到應用緩沖區階段, 應用進程會阻塞阻塞式IO, 非阻塞式IO, IO復用, 信號驅動式IO, 主要區別在于第一階段: 非阻塞式IO, 信號驅動式IO, 異步IO第一階段不會阻塞 異步IO: 不會阻塞
  • IO復用:
    包含select/poll/epoll, select出現最早, 然后是poll, 再是epoll
包含select/poll/epoll, select出現最早, 然后是poll, 再是epollselect:int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);readfds, writefds, exceptfds代表讀,寫,異常條件描述符集合(使用fd_set類型的數組實現,大小為FD_SETSIZE)timeout表示select調用會一直阻塞直到超出timeout, 調用成功返回0, 異常-1, 超時0 poll:int poll(struct pollfd *fds, unsigned int nfds, int timeout);使用鏈表實現
  • select與poll二者的區別:
    select會修改描述符, poll不會, select描述符使用數組fd_set實現, 默認大小1024, 只能監聽1024個描述符, 需要修改FD_SETSIZE, poll沒有描述符數量限制

    poll提供更多的事件類型, 描述符重用率比select高一個線程對某個描述符調用select或poll, 另一個線程關閉該描述符, 會導致調用結果不確定二者的速度都慢, 每次調用都需要將全部描述符從應用進程緩沖區復制到內核緩沖區select和poll的返回結果中沒有聲明哪些描述符已經準備好了, 當返回值大于0, 采用輪詢的方式找到IO完成描述符所有系統都支持select, 只有較新的系統支持poll
  • epoll:
    int epoll_create(int size);
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
    用于向內核注冊新的描述符或改變某個文件描述符的狀態, 已注冊的描述符使用紅黑樹維護, 通過回調函數內核會將IO準備好的描述符加入一個鏈表中管理
    int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);獲得事件完成的描述符
    只需要將描述符從進程緩沖區向內核緩沖區拷貝一次, 進程不需要通過輪詢的方式來獲得事件完成的描述符

創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

總結

以上是生活随笔為你收集整理的Java NIO_I/O基本概念_Java中的缓冲区(Buffer)_通道(Channel)_网络I/O的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。