標籤:
Java從1.4開始引進了對於輸入輸出的改進,相關類位於java.nio包中。新IO主要有以下幾個特性:
(1)字元集編碼器和解碼器
(2)非阻塞的IO
(3)記憶體對應檔
1. 字元集編碼器和解碼器
Charset類表示不同的字元集,可以使用Charset.forName方法獲得指定名稱的字元集對象,與Charset相關的類在java.nio.charset包中。
(1)編碼
將Unicode編碼的字串編碼成指定編碼的位元組序列
Charset cset = Charset.forName("UTF-8");
String str = "Charset類";
ByteBuffer buffer = cset.encode(str);
byte[] bytes = buffer.array();
(2)解碼
將位元組序列解碼成Unicode編碼的字串
Charset cset = Charset.forName("UTF-8");
ByteBuffer buffer = ByteBuffer.wrap(bytes,0,bytes.length);
String str = cset.decode(buffer).toString();
2. Buffer類
用於儲存基礎資料型別 (Elementary Data Type)資料的緩衝區,其常用的子類有ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer。
緩衝區有三個基本屬性:
位置,下一個要讀寫的元素在緩衝區中的位置;
界限,緩衝區中不允許讀寫的第一個元素的位置;
容量,緩衝區能包含的元素的數量,緩衝區的容量是固定的,在建立緩衝區的時候指定。
(1)讀緩衝區
讀緩衝區,即從緩衝區中讀取資料,通常對應著其他類的write方法,即將緩衝區中的內容讀出然後寫到某個地方。例:
ByteBuffer buffer = ByteBuffer.allocate(256);buffer.flip(); //將界限設定到當前位置,將位置複位到0,使緩衝區變成可讀狀態FileChannel fileChannel = new FileOutputStream("filename");fileChannel.write(buffer); //讀緩衝區的內容(從位置到界限)寫到檔案通道
從位置到界限之間的內容即緩衝區中的剩餘內容,這段內容是可讀或者可寫的。
(2)寫緩衝區
寫緩衝區,即向緩衝區中寫入資料,通常對應著其他類的read方法,即將讀到內容寫到緩衝區中儲存。例:
ByteBuffer buffer = ByteBuffer.allocate(256);buffer.clear(); //將位置複位到0,將界限設定成容量,使緩衝區變成可寫狀態FileChannel fileChannel = new FileInputStream("filename");fileChannel.read(buffer); //從檔案通道讀取內容寫到緩衝區中
3. 非阻塞IO
在java.nio.channels包中包含Channel相關的類,Channel表示一個到硬體裝置、檔案、網路通訊端等的一個I/O通道,Channel是可讀也可寫的雙向通道,這是Channel與java.io包中的流式I/O的一個很大的區別,另外Channel的讀寫操作可以設定為阻塞/非阻塞的。
Channel類常見的子類有:FileChannel、DatagramChannel、SocketChannel、ServerSocketChannel等,分別表示到檔案、資料報、TCP通訊端的通道。
java.nio.channels包中還包含Selector類和SelectionKey類,Selector類表示多工器,SelectionKey是多工器中用來標記註冊上來的通道操作屬性。通過Selector和SelectionKey可以用來實現多工I/O,例如:可以實現select模式的網路I/O。
下面給出簡單實現的java實現select模式的IO:
//伺服器端public class ServerTask implements Runnable{ private Selector selector; private ServerSocketChannel serverChannel; private Iterator<SelectionKey> keyItera; private ByteBuffer buffer; public ServerTask() throws IOException { serverChannel = ServerSocketChannel.open(); // 建立伺服器端通訊端通道 serverChannel.socket().bind(new InetSocketAddress("127.0.0.1",9898)); // 綁定到原生9898連接埠 selector = Selector.open(); // 建立Selector多路I/O複用器
//設定通道非阻塞並註冊該通道到ACCEPT事件監聽集合中 serverChannel.configureBlocking(false).register(selector,SelectionKey.OP_ACCEPT); buffer = ByteBuffer.allocate(128); } @Override public void run() { SelectionKey key = null; while(true) { try { if(selector.select() > 0) // 選擇一組鍵,其對應的通道已經為某個I/O操作準備就緒,返回選擇的鍵的數目 { keyItera = selector.selectedKeys().iterator(); while(keyItera.hasNext()) { key = keyItera.next(); if(key.isAcceptable()) // ACCPET操作準備就緒 {
// 接受用戶端的串連 SocketChannel clientChannel = ((ServerSocketChannel)key.channel()).accept();
// 設定用戶端通訊端通道非阻塞,並註冊到READ事件監聽集合中 if(clientChannel != null) clientChannel.configureBlocking(false).register(selector,SelectionKey.OP_READ); } else if(key.isReadable()) // READ操作準備就緒 {
// 從用戶端通訊端通道讀取資料儲存到緩衝區中 SocketChannel clientChannel = ((SocketChannel)key.channel()); buffer.clear(); clientChannel.read(buffer); buffer.flip(); System.out.print(buffer.getChar()); System.out.println(buffer.getInt()); } keyItera.remove(); // 移除該鍵,表示已經處理 } } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }}
//用戶端
public class ClientTask implements Runnable{ private SocketChannel channel; private char name; private ByteBuffer buffer; private int count = 0; public ClientTask(char name) throws IOException { this.name = name;
//建立用戶端通訊端通道,串連到伺服器端
channel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9898)); buffer = ByteBuffer.allocate(128); } @Override public void run() { while(true) { count++; try {
//將緩衝區中的內容寫入到用戶端通訊端通道,發送給伺服器端 buffer.putChar(name); buffer.putInt(count); buffer.flip(); channel.write(buffer); buffer.clear(); Thread.sleep(1000); // 休眠1s } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } }}
//主程式
public class Test{ public static void main(String[] args) { try { new Thread(new ServerTask()).start(); //啟動伺服器端線程 for(int i = 0; i < 3; i++) //啟動多個用戶端線程 { new Thread(new ClientTask((char)(‘A‘ + i))).start(); } } catch(Exception e) { e.printStackTrace(); } }
伺服器端接收到來自多個用戶端的資訊並列印出來,輸出結果:
A1B1C1A2B2C2
4. 記憶體對應檔
作業系統可以利用虛擬記憶體機制實現將一個檔案或者檔案的部分映射到記憶體中,然後對這個檔案的操作就像訪問記憶體一樣,比傳統的檔案流式I/O要快得多。
首先,建立一個FileChannel;
然後調用FileChannel類得map方法從這個通道中獲得一個MappedByteBuffer,即將此檔案對應到虛擬記憶體中。可以指定對應檔的部分以及映射模式,包括以下三種模式:
FileChannel.MapMode.READ_ONLY,唯讀模式;
FileChannel.MapMode.READ_WRITE,可讀寫入模式,對緩衝區的修改會寫回到檔案中;
FileChannel.MapMode.PRIVATE,可讀寫入模式,但是對緩衝區的修改不會寫回到檔案中;
然後就可以通過獲得的MappedByteBuffer對象對檔案進行讀寫操作了。
Java新IO