Before
IO 分為:
同步、異步
阻塞、非阻塞
同步和異步是目的,阻塞和非阻塞是實現(xiàn)方式。
一個IO操作其實分成了兩個步驟:發(fā)起IO請求和實際的IO操作。
同步IO和異步IO的區(qū)別就在于第二個步驟是否阻塞,如果實際的IO讀寫阻塞請求進程,那么就是同步IO。
阻塞IO和非阻塞IO的區(qū)別在于第一步,發(fā)起IO請求是否會被阻塞,如果阻塞直到完成那么就是傳統(tǒng)的阻塞IO,如果不阻塞,那么就是非阻塞IO。
IO操作可以分為3類:同步阻塞(即早期的IO操作)、同步非阻塞(NIO)、異步(AIO)
BIO、NIO、AIO適用場景分析:
- BIO方式適用于連接數(shù)目比較小且固定的架構,這種方式對服務器資源要求比較高,并發(fā)局限于應用中,JDK1.4以前的唯一選擇,但程序直觀簡單易理解。
- NIO方式適用于連接數(shù)目多且連接比較短(輕操作)的架構,比如聊天服務器,并發(fā)局限于應用中,編程比較復雜,JDK1.4開始支持。
- AIO方式使用于連接數(shù)目多且連接比較長(重操作)的架構,比如相冊服務器,充分調用OS參與并發(fā)操作,編程比較復雜,JDK7開始支持。
IO
https://www.cnblogs.com/ylspace/p/8128112.html
Java I/O主要包括如下幾個層次,包含三個部分:
1.流式部分,IO的主體部分;
2.非流式部分,主要包含一些輔助流式部分的類,如:File類、RandomAccessFile類和FileDescriptor等類;
3.其他類,文件讀取部分的與安全相關的類,如:SerializablePermission類,以及與本地操作系統(tǒng)相關的文件系統(tǒng)的類,如:FileSystem類和Win32FileSystem類和WinNTFileSystem類。
流:代表任何有能力產出數(shù)據的數(shù)據源對象或者是有能力接受數(shù)據的接收端對象
IO流的分類
- 根據處理數(shù)據類型的不同分為:字符流和字節(jié)流
- 根據數(shù)據流向不同分為:輸入流和輸出流
- 按數(shù)據來源(去向)分類:
1、File(文件): FileInputStream, FileOutputStream, FileReader, FileWriter
2、byte[]:ByteArrayInputStream, ByteArrayOutputStream
3、Char[]: CharArrayReader,CharArrayWriter
4、String:StringBufferInputStream, StringReader, StringWriter
5、網絡數(shù)據流:InputStream,OutputStream, Reader, Writer
流序列中的數(shù)據既可以是未經加工的原始二進制數(shù)據,也可以是經一定編碼處理后符合某種格式規(guī)定的特定數(shù)據。因此Java中的流分為兩種:
- 字節(jié)流:數(shù)據流中最小的數(shù)據單元是字節(jié)
- 字符流:數(shù)據流中最小的數(shù)據單元是字符, Java中的字符是Unicode編碼,一個字符占用兩個字節(jié)。
字符流的由來: Java中字符是采用Unicode標準,一個字符是16位,即一個字符使用兩個字節(jié)來表示。為此,JAVA中引入了處理字符的流。因為數(shù)據編碼的不同,而有了對字符進行高效操作的流對象。本質其實就是基于字節(jié)流讀取時,去查了指定的碼表。
NIO
http://www.iteye.com/magazines/132-Java-NIO
三個關鍵組成:
Buffer
Channel
Selector
深入淺出NIO之Channel、Buffer
http://www.itdecent.cn/p/052035037297
Buffer
一塊緩存區(qū),內部使用字節(jié)數(shù)組存儲數(shù)據,并維護幾個特殊變量,實現(xiàn)數(shù)據的反復利用。
1、mark:初始值為-1,用于備份當前的position;
2、position:初始值為0,position表示當前可以寫入或讀取數(shù)據的位置,當寫入或讀取一個數(shù)據后,position向前移動到下一個位置;
3、limit:寫模式下,limit表示最多能往Buffer里寫多少數(shù)據,等于capacity值;讀模式下,limit表示最多可以讀取多少數(shù)據。
4、capacity:緩存數(shù)組大小

ByteBuffer的實現(xiàn)類包括"HeapByteBuffer"和"DirectByteBuffer"兩種。
HeapByteBuffer通過初始化字節(jié)數(shù)組hd,在虛擬機堆上申請內存空間。
DirectByteBuffer通過unsafe.allocateMemory申請堆外內存,并在ByteBuffer的address變量中維護指向該內存的地址。
unsafe.setMemory(base, size, (byte) 0)方法把新申請的內存數(shù)據清零。
Channel
NIO把它支持的I/O對象抽象為Channel,Channel又稱“通道”,類似于原I/O中的流(Stream),但有所區(qū)別:
1、流是單向的,通道是雙向的,可讀可寫。
2、流讀寫是阻塞的,通道可以異步讀寫。
3、流中的數(shù)據可以選擇性的先讀到緩存中,通道的數(shù)據總是要先讀到一個緩存中,或從緩存中寫入

目前已知Channel的實現(xiàn)類有:
- FileChannel
- DatagramChannel
- SocketChannel
- ServerSocketChannel
FileChannel的read、write和map通過其實現(xiàn)類FileChannelImpl實現(xiàn)。
通過上述實現(xiàn)可以看出,基于channel的文件數(shù)據讀取步驟如下:
1、申請一塊和緩存同大小的DirectByteBuffer bb。
2、讀取數(shù)據到緩存bb,底層由NativeDispatcher的read實現(xiàn)。
3、把bb的數(shù)據讀取到dst(用戶定義的緩存,在jvm中分配內存)。
read方法導致數(shù)據復制了兩次。
通過上述實現(xiàn)可以看出,基于channel的文件數(shù)據寫入步驟如下:
1、申請一塊DirectByteBuffer,bb大小為byteBuffer中的limit - position。
2、復制byteBuffer中的數(shù)據到bb中。
3、把數(shù)據從bb中寫入到文件,底層由NativeDispatcher的write實現(xiàn)
write方法也導致了數(shù)據復制了兩次
File file = new RandomAccessFile("data.txt", "rw");
FileChannel channel = file.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(48);
int bytesRead = channel.read(buffer);
while (bytesRead != -1) {
System.out.println("Read " + bytesRead);
buffer.flip();
while(buffer.hasRemaining()){
System.out.print((char) buffer.get());
}
buffer.clear();
bytesRead = channel.read(buffer);
}
file.close();
注意buffer.flip() 的調用,首先將數(shù)據寫入到buffer,然后變成讀模式,再從buffer中讀取數(shù)據。
Selector
深入淺出NIO之Selector實現(xiàn)原理
http://www.itdecent.cn/p/0d497fe5484a

為了實現(xiàn) Selector 管理多個 SocketChannel,必須將具體的 SocketChannel 對象注冊到 Selector,并聲明需要監(jiān)聽的事件(這樣 Selector 才知道需要記錄什么數(shù)據),一共有4種事件:
1、connect:客戶端連接服務端事件,對應值為SelectionKey.OP_CONNECT(8)
2、accept:服務端接收客戶端連接事件,對應值為SelectionKey.OP_ACCEPT(16)
3、read:讀事件,對應值為SelectionKey.OP_READ(1)
4、write:寫事件,對應值為SelectionKey.OP_WRITE(4)
這個很好理解,每次請求到達服務器,都是從connect開始,connect成功后,服務端開始準備accept,準備就緒,開始讀數(shù)據,并處理,最后寫回數(shù)據返回。
所以,當SocketChannel有對應的事件發(fā)生時,Selector都可以觀察到,并進行相應的處理。
SocketChannel、ServerSocketChannel 和 Selector 的實例初始化都通過 SelectorProvider 類實現(xiàn),其中 Selector 是整個 NIO Socket 的核心實現(xiàn)。
實例
服務端
public class NIOServer {
/*接受數(shù)據緩沖區(qū)*/
private ByteBuffer sendbuffer = ByteBuffer.allocate(1024);
/*發(fā)送數(shù)據緩沖區(qū)*/
private ByteBuffer receivebuffer = ByteBuffer.allocate(1024);
private Selector selector;
public NIOServer(int port) throws IOException {
// 打開服務器套接字通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 服務器配置為非阻塞
serverSocketChannel.configureBlocking(false);
// 檢索與此通道關聯(lián)的服務器套接字
ServerSocket serverSocket = serverSocketChannel.socket();
// 進行服務的綁定
serverSocket.bind(new InetSocketAddress(port));
// 通過open()方法找到Selector
selector = Selector.open();
// 注冊到selector,等待連接
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server Start----:");
}
//
private void listen() throws IOException {
while (true) {
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
handleKey(selectionKey);
}
}
}
private void handleKey(SelectionKey selectionKey) throws IOException {
// 接受請求
ServerSocketChannel server = null;
SocketChannel client = null;
String receiveText;
String sendText;
int count=0;
// 測試此鍵的通道是否已準備好接受新的套接字連接。
if (selectionKey.isAcceptable()) {
// 返回為之創(chuàng)建此鍵的通道。
server = (ServerSocketChannel) selectionKey.channel();
// 接受到此通道套接字的連接。
// 此方法返回的套接字通道(如果有)將處于阻塞模式。
client = server.accept();
// 配置為非阻塞
client.configureBlocking(false);
// 注冊到selector,等待連接
client.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
// 返回為之創(chuàng)建此鍵的通道。
client = (SocketChannel) selectionKey.channel();
//將緩沖區(qū)清空以備下次讀取
receivebuffer.clear();
//讀取服務器發(fā)送來的數(shù)據到緩沖區(qū)中
count = client.read(receivebuffer);
if (count > 0) {
receiveText = new String( receivebuffer.array(),0,count);
System.out.println("服務器端接受客戶端數(shù)據--:"+receiveText);
client.register(selector, SelectionKey.OP_WRITE);
}
} else if (selectionKey.isWritable()) {
//將緩沖區(qū)清空以備下次寫入
sendbuffer.clear();
// 返回為之創(chuàng)建此鍵的通道。
client = (SocketChannel) selectionKey.channel();
sendText="message from server--";
//向緩沖區(qū)中輸入數(shù)據
sendbuffer.put(sendText.getBytes());
//將緩沖區(qū)各標志復位,因為向里面put了數(shù)據標志被改變要想從中讀取數(shù)據發(fā)向服務器,就要復位
sendbuffer.flip();
//輸出到通道
client.write(sendbuffer);
System.out.println("服務器端向客戶端發(fā)送數(shù)據--:"+sendText);
client.register(selector, SelectionKey.OP_READ);
}
}
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
int port = 8080;
NIOServer server = new NIOServer(port);
server.listen();
}
}
客戶端
public class NIOClient {
/*接受數(shù)據緩沖區(qū)*/
private static ByteBuffer sendbuffer = ByteBuffer.allocate(1024);
/*發(fā)送數(shù)據緩沖區(qū)*/
private static ByteBuffer receivebuffer = ByteBuffer.allocate(1024);
public static void main(String[] args) throws IOException {
// 打開socket通道
SocketChannel socketChannel = SocketChannel.open();
// 設置為非阻塞方式
socketChannel.configureBlocking(false);
// 打開選擇器
Selector selector = Selector.open();
// 注冊連接服務端socket動作
socketChannel.register(selector, SelectionKey.OP_CONNECT);
// 連接
socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));
Set<SelectionKey> selectionKeys;
Iterator<SelectionKey> iterator;
SelectionKey selectionKey;
SocketChannel client;
String receiveText;
String sendText;
int count=0;
while (true) {
//選擇一組鍵,其相應的通道已為 I/O 操作準備就緒。
//此方法執(zhí)行處于阻塞模式的選擇操作。
selector.select();
//返回此選擇器的已選擇鍵集。
selectionKeys = selector.selectedKeys();
//System.out.println(selectionKeys.size());
iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
selectionKey = iterator.next();
if (selectionKey.isConnectable()) {
System.out.println("client connect");
client = (SocketChannel) selectionKey.channel();
// 判斷此通道上是否正在進行連接操作。
// 完成套接字通道的連接過程。
if (client.isConnectionPending()) {
client.finishConnect();
System.out.println("完成連接!");
sendbuffer.clear();
sendbuffer.put("Hello,Server".getBytes());
sendbuffer.flip();
client.write(sendbuffer);
}
client.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
client = (SocketChannel) selectionKey.channel();
//將緩沖區(qū)清空以備下次讀取
receivebuffer.clear();
//讀取服務器發(fā)送來的數(shù)據到緩沖區(qū)中
count=client.read(receivebuffer);
if(count>0){
receiveText = new String( receivebuffer.array(),0,count);
System.out.println("客戶端接受服務器端數(shù)據--:"+receiveText);
client.register(selector, SelectionKey.OP_WRITE);
}
} else if (selectionKey.isWritable()) {
sendbuffer.clear();
client = (SocketChannel) selectionKey.channel();
sendText = "message from client--";
sendbuffer.put(sendText.getBytes());
//將緩沖區(qū)各標志復位,因為向里面put了數(shù)據標志被改變要想從中讀取數(shù)據發(fā)向服務器,就要復位
sendbuffer.flip();
client.write(sendbuffer);
System.out.println("客戶端向服務器端發(fā)送數(shù)據--:"+sendText);
client.register(selector, SelectionKey.OP_READ);
}
}
selectionKeys.clear();
}
}
}