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中文網其他相關文章!