首頁  >  文章  >  Java  >  詳細介紹Java NIO

詳細介紹Java NIO

王林
王林轉載
2020-10-21 16:35:211745瀏覽

詳細介紹Java NIO

Java NIO主要需要理解緩衝區、通道、選擇器三個核心概念,作為Java I/O的補充, 以提升大量資料傳輸的效率。

(推薦教學:java課程

學習NIO之前最好能有基礎的網路程式設計知識

Java I/O串流

Java 網路程式設計

Java NIO:緩衝區

通道(Channel)作為NIO的三大核心概念之一(緩衝區、通道、選擇器),用於在位元組緩衝區與位於通道另一側的實體(檔案或套接字)之間有效的傳輸資料(核心是傳輸資料)

NIO程式設計的一般模式是:將資料填入傳送字節緩衝區--> 透過通道傳送到通道對端檔案或套接字

通道基礎

使用Channel的目的是進行資料傳輸,使用前需要打開通道、使用後需要關閉通道

打開通道

我們知道I/O有兩大類:File IO和Stream I/O,其對應到通道也就有檔案通道(FileChannel)和套接字通道(SocketChannel、ServerSocketChannel、DatagramChannel)兩種

對於套接字通道,使用靜態工廠方法開啟

SocketChannel sc = SocketChannel.open();
ServerSocketChannel sc = ServerSocketChannel.open();
DatagramChannel sc = DatagramChannel.open();

對於檔案通道只能透過對一個RandomAccessFile、FileInputStream、FileOutputStream物件呼叫getChannel()方法取得

FileInputStream in = new FileInputStream("/tmp/a.txt");
FileChannel fc = in.getChannel();

使用通道進行資料傳輸

下段程式碼首先將要寫入的資料放到ByteBuffer中, 然後開啟檔案通道,把緩衝區中的資料放到檔案通道。

//准备数据并放入字节缓冲区
ByteBuffer bf = ByteBuffer.allocate(1024);
bf.put("i am cool".getBytes());
bf.flip();
//打开文件通道
FileOutputStream out = new FileOutputStream("/tmp/a.txt");
FileChannel fc = out.getChannel();
//数据传输
fc.write(bf);
//关闭通道
fc.close();

關閉通道

如同Socket、FileInputStream等物件使用完畢之後需要關閉一樣, 通道使用之後也需要關閉。一個打開的通道代表與一個特定I/O服務的特定連接並封裝該連接的狀態,通道關閉時連接丟失,不再連接任何東西。

阻塞& 非阻塞模式

通道有阻塞和非阻塞兩種運作模式,非阻塞模式的通道永遠不會休眠,請求的操作要麼立即完成,要么返回一個結果表明未進行任何操作(具體看Socket通道處的描述)。只有面向流的通道可使用非阻塞模式

文件通道

文件通道用於對文件進行訪問, 透過對一個RandomAccessFile、FileInputStream、FileOutputStream物件呼叫getChannel ()方法取得。呼叫getChannel方法傳回一個連接到相同檔案的FileChannel對象,該FileChannel物件具有與file對象相同的存取權。

檔案存取

使用檔案通道的目的還是對檔案進行讀寫操作,通道的讀寫api如下:

public abstract int read(ByteBuffer dst) throws IOException;
public abstract int write(ByteBuffer src) throws IOException;

下面是一段讀取檔案的Demo

//打开文件channel
RandomAccessFile f = new RandomAccessFile("/tmp/a.txt", "r");
FileChannel fc = f.getChannel();
//从channel中读取数据,直到文件尾
ByteBuffer bb = ByteBuffer.allocate(1024);
while (fc.read(bb) != -1) {
;
}
//翻转(读之前需要先进行翻转)
bb.flip();
StringBuilder builder = new StringBuilder();
//把每一个字节转为字符(ascii编码)
while (bb.hasRemaining()) {
builder.append((char) bb.get());
}
System.out.println(builder.toString());

上面這個demo有個問題:我們只能讀取字節, 然後由應用程式去解碼,這個問題我們可以透過工具類Channels將通道包裝成Reader和Writer來解決;當然我們也可以直接使用Java I/O流模式的Reader和Writer操作字符

#文件通道位置與文件空洞

文件通道位置(position)就是普通檔案的位置, position的值決定了檔案中哪個位置的資料接下來將被讀取或寫入

#讀取超出檔案尾部位置的資料會回傳-1(檔案EOF)

往一個超出檔案尾部的位置寫入資料會造成檔案空洞:例如一個檔案現在有10個位元組, 但是此時往position=20 處寫入資料就會造成10~20之間的位置是沒有資料的,這就是檔案空洞

force操作

force操作強制通道將全部修改立即套用到磁碟檔案(防止系統宕機導致修改遺失)

public abstract void force(boolean metaData) throws IOException;

記憶體檔案對應

FileChannel提供了一個map()方法,可以在一個開啟的檔案和特殊類型的ByteBuffer(MappedByteBuffer)之間建立一個虛擬記憶體映射。

因為map方法傳回的MappedByteBuffer物件是直接緩衝區,所以透過MappedByteBuffer來操作檔案非常有效率(尤其是大量資料傳輸的情況)

MappedByteBuffer的使用

透過MappedByteBuffer讀取檔案

FileInputStream in = new FileInputStream("/tmp/a.txt");
FileChannel fc = in.getChannel();
MappedByteBuffer mbb = fc.map(MapMode.READ_ONLY, 0, fc.size());
StringBuilder builder = new StringBuilder();
while (mbb.hasRemaining()) {
  builder.append((char) mbb.get());
}
System.out.println(builder.toString());

MappedByteBuffer的三種模式

READ_ONLY

#READ_WRITE

PRIVATE

只讀和讀寫模式都好理解,PRIVATE模式下寫操作寫的是一個暫存緩衝區,不會真正去寫檔案。 (寫時拷貝思想)

Socket通道

Socket 通道可以運行在非阻塞模式且是可選擇的,這兩點使得對於網路程式設計我們不再需要為每個Socket連線建立一個線程,而是使用一個線程即可管理成百上千的Socket連線。

所有的Socket通道在實例化的時候都會創建一個對象的Socket對象, Socket通道並不負責協議相關的操作, 協議相關的操作都委派給對等socket對象(如SocketChannel對象委派給Socket物件)

非阻塞模式

相较于传统Java Socket的阻塞模式,SocketChannel提供了非阻塞模式,以构建高性能的网络应用程序

非阻塞模式下,几乎所有的操作都是立刻返回的。比如下面的SocketChannel运行在非阻塞模式下,connect操作会立即返回,如果success为true代表连接已经建立成功了, 如果success为false, 代表连接还在建立中(tcp连接需要一些时间)。

 //打开Socket通道
 SocketChannel ch = SocketChannel.open();
 //非阻塞模式
 ch.configureBlocking(false);
 //连接服务器 
 boolean success = ch.connect(InetSocketAddress.createUnresolved("127.0.0.1", 7001));
 //轮训连接状态, 如果连接还未建立就可以做一些别的工作
 while (!ch.finishConnect()){
    //dosomething else
 }
 //连接建立, 做正事
 //do something;

ServerSocketChannel

ServerSocketChannel与ServerSocket类似,只是可以运行在非阻塞模式下

下为一个通过ServerSocketChannel构建服务器的简单例子,主要体现了非阻塞模式,核心思想与ServerSocket类似

ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
ssc.bind(new InetSocketAddress(7001));
while (true){
  SocketChannel sc = ssc.accept();
  if(sc != null){
    handle(sc);
  }else {
    Thread.sleep(1000);
  }
}

SocketChannel 与 DatagramChannel

SocketChannel 对应 Socket, 模拟TCP协议;DatagramChannel对应DatagramSocket, 模拟UDP协议

二者的使用与SeverSocketChannel大同小异,看API即可

工具类

文体通道那里我们提到过, 通过只能操作字节缓冲区, 编解码需要应用程序自己实现。如果我们想在通道上直接操作字符,我们就需要使用工具类Channels,工具类Channels提供了通道与流互相转换、通道转换为阅读器书写器的能力,具体API入下

//通道 --> 输入输出流
public static OutputStream newOutputStream(final WritableByteChannel ch);
public static InputStream newInputStream(final AsynchronousByteChannel ch);
//输入输出流 --> 通道
public static ReadableByteChannel newChannel(final InputStream in);
public static WritableByteChannel newChannel(final OutputStream out);
//通道  --> 阅读器书写器
public static Reader newReader(ReadableByteChannel ch, String csName);
public static Writer newWriter(WritableByteChannel ch, String csName);

通过将通道转换为阅读器、书写器我们就可以直接在通道上操作字符。

    RandomAccessFile f = new RandomAccessFile("/tmp/a.txt", "r");
  FileChannel fc = f.getChannel();
  //通道转换为阅读器,UTF-8编码
  Reader reader = Channels.newReader(fc, "UTF-8");
  int i = 0, s = 0;
  char[] buff = new char[1024];
  while ((i = reader.read(buff, s, 1024 - s)) != -1) {
    s += i;
  }
  for (i = 0; i < s; i++) {
    System.out.print(buff[i]);
  }

总结

通道主要分为文件通道和套接字通道。

对于文件操作:如果是大文件使用通道的文件内存映射特性(MappedByteBuffer)来有利于提升传输性能, 否则我更倾向传统的I/O流模式(字符API);对于套接字操作, 使用通道可以运行在非阻塞模式并且是可选择的,利于构建高性能网络应用程序。

相关推荐:java入门

以上是詳細介紹Java NIO的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.im。如有侵權,請聯絡admin@php.cn刪除