【J2SE】JAVAIO 流BIO,NIO,AIO
javaIO程式設計.
隨著java版本的不斷升級與迭代,java的IO模型開始得到改變。從原始的BIO,到1.4以後釋出的NIO,再到對NIO進行的改進AIO分別對IO模型做了優化,BIO是同步,阻塞的IO.
NIO是同步,非阻塞的IO,AIO是非同步非阻塞的IO,那麼效能就自然不用多說了,肯定是依次得到了提升的。
一 檔案
基本的IO流與IO 模型,可以說它是通過序列化後的位元組流來進行基本的通訊的。
-
- BIO
1.1.1無緩衝區IO
public class BIONOBuffer { @SuppressWarnings("resource") public static void main(String[] args) { long timeA=System.currentTimeMillis(); FileInputStream bur=null; FileOutputStream bor=null; try{ bur = new FileInputStream(new File("C:/Users/pc/Desktop/[www.java1234.com]Activiti實戰 PDF電子書-含書籤目錄.pdf")); bor = new FileOutputStream(new File("C:/Users/pc/Desktop/拷貝/cs.pdf")); byte[] buf=new byte[1024]; while((bur.read(buf))!=-1){ bor.write(buf); bor.flush(); } }catch(Exception exception){ exception.printStackTrace(); }finally{ try { if(bur!=null){ bur.close(); } if(bor!=null){ bor.close(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } long timeB=System.currentTimeMillis(); System.out.println((timeB-timeA)+" ms"); } }
無緩衝區的用時(514+460+467)/3=480.333ms
1.1.2有緩衝區IO
public class IOTransport { public static void main(String[] args) { long timeA=System.currentTimeMillis(); BufferedInputStream bur=null; BufferedOutputStream bor=null; try{ bur=new BufferedInputStream(new FileInputStream(new File("C:/Users/pc/Desktop/[www.java1234.com]Activiti實戰 PDF電子書-含書籤目錄.pdf"))); bor=new BufferedOutputStream(new FileOutputStream(new File("C:/Users/pc/Desktop/拷貝/cs.pdf"))); byte[] buf=new byte[1024]; while(bur.read(buf)!=-1){ bor.write(buf); bor.flush(); } }catch(Exception exception){ exception.printStackTrace(); }finally{ try { if(bur!=null){ bur.close(); } if(bor!=null){ bor.close(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } long timeB=System.currentTimeMillis(); System.out.println((timeB-timeA)+" ms"); } }
讀取時間計算(401+368+372)/3=380.33ms
-
- NIO
- jkd1.7之前NIO
- NIO
關於直接緩衝區與非直接緩衝區的區別
非直接緩衝區:通過 allocate() 方法分配緩衝區,將緩衝區建立在 JVM 的記憶體中
直接緩衝區:通過 allocateDirect() 方法分配直接緩衝區,將緩衝區建立在實體記憶體中。可以提高效率
位元組緩衝區要麼是直接的,要麼是非直接的。如果為直接位元組緩衝區,則 Java 虛擬機器會盡最大努力直接在此緩衝區上執行本機 I/O 操作。也就是說,在每次呼叫基礎作業系統的一個本機 I/O 操作之前(或之後),虛擬機器都會盡量避免將緩衝區的內容複製到中間緩衝區中(或從中間緩衝區中複製內容)。
直接位元組緩衝區可以通過呼叫此類的 allocateDirect() 工廠方法來建立。此方法返回的緩衝區進行分配和取消分配所需成本通常高於非直接緩衝區。直接緩衝區的內容可以駐留在常規的垃圾回收堆之外,因此,它們對應用程式的記憶體需求量造成的影響可能並不明顯。所以,建議將直接緩衝區主要分配給那些易受基礎系統的本機 I/O 操作影響的大型、持久的緩衝區。一般情況下,最好僅在直接緩衝區能在程式效能方面帶來明顯好處時分配它們。
直接位元組緩衝區還可以通過 FileChannel 的 map() 方法 將檔案區域直接對映到記憶體中來建立。該方法返回MappedByteBuffer 。 Java 平臺的實現有助於通過 JNI 從本機程式碼建立直接位元組緩衝區。如果以上這些緩衝區中的某個緩衝區例項指的是不可訪問的記憶體區域,則試圖訪問該區域不會更改該緩衝區的內容,並且將會在訪問期間或稍後的某個時間導致丟擲不確定的異常。
位元組緩衝區是直接緩衝區還是非直接緩衝區可通過呼叫其 isDirect() 方法來確定。提供此方法是為了能夠在效能關鍵型程式碼中執行顯式緩衝區管理。
- 非直接緩衝區
public class BIOTransport {
public static void main(String[] args) {
long timeA=System.currentTimeMillis();
FileInputStream fileInputStream=null;
FileOutputStream fileOutputStream=null;
FileChannel inchan=null;
FileChannel outchan=null;
try{
fileInputStream = new FileInputStream(new File("C:/Users/pc/Desktop/[www.java1234.com]Activiti實戰 PDF電子書-含書籤目錄.pdf"));
fileOutputStream = new FileOutputStream(new File("C:/Users/pc/Desktop/拷貝/cs.pdf"));
inchan = fileInputStream.getChannel();
outchan = fileOutputStream.getChannel();
//ByteBuffer byteBuffer=ByteBuffer.allocateDirect(1024);
ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
while(inchan.read(byteBuffer)!=-1){
//開啟讀模式
byteBuffer.flip();
outchan.write(byteBuffer);
byteBuffer.clear();
}
}catch(Exception exception){
exception.printStackTrace();
}finally{
try {
if(fileInputStream!=null){
fileInputStream.close();
}
if(fileOutputStream!=null){
fileOutputStream.close();
}
if(inchan!=null){
inchan.close();
}
if(outchan!=null){
outchan.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
long timeB=System.currentTimeMillis();
System.out.println((timeB-timeA)+" ms");
}
}
讀取時間計算(523+562+518)/3=534.33ms
- 直接緩衝區
public class BIOTransport {
public static void main(String[] args) {
long timeA=System.currentTimeMillis();
FileInputStream fileInputStream=null;
FileOutputStream fileOutputStream=null;
FileChannel inchan=null;
FileChannel outchan=null;
try{
fileInputStream = new FileInputStream(new File("C:/Users/pc/Desktop/[www.java1234.com]Activiti實戰 PDF電子書-含書籤目錄.pdf"));
fileOutputStream = new FileOutputStream(new File("C:/Users/pc/Desktop/拷貝/cs.pdf"));
inchan = fileInputStream.getChannel();
outchan = fileOutputStream.getChannel();
ByteBuffer byteBuffer=ByteBuffer.allocateDirect(1024);
//ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
while(inchan.read(byteBuffer)!=-1){
//開啟讀模式
byteBuffer.flip();
outchan.write(byteBuffer);
byteBuffer.clear();
}
}catch(Exception exception){
exception.printStackTrace();
}finally{
try {
if(fileInputStream!=null){
fileInputStream.close();
}
if(fileOutputStream!=null){
fileOutputStream.close();
}
if(inchan!=null){
inchan.close();
}
if(outchan!=null){
outchan.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
long timeB=System.currentTimeMillis();
System.out.println((timeB-timeA)+" ms");
}
}
時間計算(457+452+461)/3=456.333
1.2.2 jkd1.7之後NIO
public class FileIOTransport {
public static void main(String[] args) {
FileChannel inChannel = null;
FileChannel outChannel = null;
try{
long start = System.currentTimeMillis();
inChannel = FileChannel.open(Paths.get("C:/Users/pc/Desktop/[www.java1234.com]Activiti實戰 PDF電子書-含書籤目錄.pdf"), StandardOpenOption.READ);
outChannel = FileChannel.open(Paths.get("C:/Users/pc/Desktop/拷貝/cs.pdf"), StandardOpenOption.WRITE,
StandardOpenOption.READ, StandardOpenOption.CREATE);
// 記憶體對映檔案
MappedByteBuffer inMappedByteBuf = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outMappedByteBuffer = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
// 直接對緩衝區進行資料的讀寫操作
byte[] dsf = new byte[inMappedByteBuf.limit()];
inMappedByteBuf.get(dsf);
outMappedByteBuffer.put(dsf);
inChannel.close();
outChannel.close();
long end = System.currentTimeMillis();
System.out.println((end - start)+ " ms");
}catch(Exception exception){
exception.printStackTrace();
}finally{
try {
if(inChannel!=null){
inChannel.close();
}
if(outChannel!=null){
outChannel.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
讀取時間計算(144+143+145)/3=144ms
由上面的IO計算方式可以看出,選擇IO流的時候應該選什麼了吧?很明顯JDK1.7以後檔案的IO讀取方式做了很大的改變。NIO的讀取效率明顯遠遠的超過了BIO,而且超出了至少3倍之多。以上是對檔案的IO的總結,當然還有網路的IO的區別,以上還沒有體現出NIO 非阻塞的優勢。
-
- AIO
二 網路
面向緩衝區來進行資料傳輸的。
2.1 BIO
這個時候我們假設用BIO進行通訊
2.1.1 server
public class ServerSockets {
static ExecutorService executorService=Executors.newCachedThreadPool();
private static ServerSocket serverSocket;
public static void main(String[] args) throws IOException {
serverSocket = new ServerSocket(8080);
while(true){
Socket accept = serverSocket.accept();
executorService.execute(new MutiThreadServer(accept));
}
}
}
public class MutiThreadServer implements Runnable{
private Socket socket=null;
public MutiThreadServer(Socket socket){
this.socket=socket;
}
@Override
public void run() {
BufferedReader reader=null;
PrintWriter writer=null;
try {
reader=new BufferedReader(new InputStreamReader(socket.getInputStream()));
writer=new PrintWriter(socket.getOutputStream(), true);
String inline=null;
long a=System.currentTimeMillis();
while((inline=reader.readLine())!=null){
writer.println(inline);
writer.flush();
}
long b=System.currentTimeMillis();
System.out.println(" "+(b-a)+"ms");
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
if(null!=reader){
reader.close();
}
if(null!=writer){
writer.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
}
2.1.2 client
public class IOSocketClient {
private static long TIME=1000*1000*1000;
public static void main(String[] args) {
ExecutorService executorService=Executors.newCachedThreadPool();
for(int i=0;i<10;i++){
executorService.execute(new Runnable() {
@Override
public void run() {
Socket client=null;
BufferedReader reader=null;
PrintWriter writer=null;
try {
client=new Socket();
client.connect(new InetSocketAddress("localhost", 8080));
OutputStream outputStream = client.getOutputStream();
writer=new PrintWriter(new OutputStreamWriter(outputStream),true);
LockSupport.parkNanos(TIME);
writer.print("h");
LockSupport.parkNanos(TIME);
writer.print("e");
LockSupport.parkNanos(TIME);
writer.print("l");
LockSupport.parkNanos(TIME);
writer.print("l");
LockSupport.parkNanos(TIME);
writer.print("o");
LockSupport.parkNanos(TIME);
writer.println("!");
writer.flush();
reader=new BufferedReader(new InputStreamReader(client.getInputStream()));
System.out.println(reader.readLine());
} catch (Exception e) {
} finally{
try {
client.close();
reader.close();
writer.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
});
}
}
}
執行結果
如上圖所示,伺服器端十個參與通訊的執行緒的時間,對於服務氣短來說由於客戶端漫長準備資料的等待造成了IO阻塞,以至於客戶端要為單個客戶端執行緒等待了近6秒的時間,這個就是BIO容易造成服務端與客戶端進行執行緒之間的阻塞。那麼有什麼辦法可以解決這個阻塞問題呢。這個時候NIO就是我們最好的良藥了。
2.2 NIO
(1)緩衝區Buffer
Buffer是一個物件,它包含一些要寫入或者要讀出的資料,在NIO庫中,所有資料都是用緩衝區處理的。在讀取資料時,它是直接讀到緩衝區中的;在寫入資料時,寫入到緩衝區中,任何時候訪問NIO中的資料,都是通過緩衝區進行操作。
緩衝區實質上是一個數組。通常它是一個位元組陣列(ByteBuffer),也可以使用其他種類的陣列,但是一個緩衝區不僅僅是一個數組,緩衝區提供了對資料的結構化訪問以及維護讀寫位置(limit)等資訊。常用的有ByteBuffer,其它還有CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer
(2)通道Channel
Channel是一個通道,可以通過它讀取和寫入資料,它就像自來水管一樣,網路資料通過Channel讀取和寫入。通道與流的不同之處在於通道是雙向的,流只是一個方向上移動(一個流必須是InputStream或者OutputStream的子類),而且通道可以用於讀、寫或者用於讀寫。同時Channel是全雙工的,因此它可以比流更好的對映底層作業系統的API。特別是在Unix網路程式設計中,底層作業系統的通道都是全雙工的,同時支援讀寫操作。我們常用到的ServerSocketChannnel和SocketChannel都是SelectableChannel的子類。
(3)多路複用器Selector
多路複用器Selector是Java NIO程式設計的基礎,多路複用器提供選擇已經就緒的任務的能力,簡單的說,Selector會不斷的輪詢註冊在其上的Channel,如果某個Channel上面有新的TCP連線接入、讀和寫事件,這個Channel就處於就緒狀態,會被Selector輪詢出來,然後通過SelectionKey可以獲取就緒Channel的集合,進行後續的I/O操作。
一個多用複用器Selector可以同時輪詢多個Channel,由於JDK使用了epoll()代替傳統的select實現,所以它並沒有最大連線控制代碼1024/2048的限制,這也意味著只需要一個執行緒負責Selector的輪詢,就可以接入成千上萬的客戶端。
2.2.1 server
public class NIOServersx {
public static void main(String[] args) throws IOException {
System.out.println("伺服器端已經啟動....");
// 1.建立通道
ServerSocketChannel sChannel = ServerSocketChannel.open();
// 2.切換讀取模式
sChannel.configureBlocking(false);
// 3.繫結連線
sChannel.bind(new InetSocketAddress(8080));
// 4.獲取選擇器
Selector selector = Selector.open();
// 5.將通道註冊到選擇器 "並且指定監聽接受事件"
sChannel.register(selector, SelectionKey.OP_ACCEPT);
// 6. 輪訓式 獲取選擇 "已經準備就緒"的事件
while (selector.select() > 0) {
// 7.獲取當前選擇器所有註冊的"選擇鍵(已經就緒的監聽事件)"
java.util.Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
// 8.獲取準備就緒的事件
SelectionKey sk = it.next();
// 9.判斷具體是什麼事件準備就緒
if (sk.isAcceptable()) {
// 10.若"接受就緒",獲取客戶端連線
SocketChannel socketChannel = sChannel.accept();
// 11.設定阻塞模式
socketChannel.configureBlocking(false);
// 12.將該通道註冊到伺服器上
socketChannel.register(selector, SelectionKey.OP_READ);
} else {
// 13.獲取當前選擇器"就緒" 狀態的通道
SocketChannel socketChannel = (SocketChannel) sk.channel();
// 14.讀取資料
ByteBuffer buf = ByteBuffer.allocate(1024);
int len = 0;
long timeA = System.currentTimeMillis();
while ((len = socketChannel.read(buf)) > 0) {
buf.flip();
System.out.print(new String(buf.array(), 0, len));
buf.clear();
}
long timeB = System.currentTimeMillis();
System.out.println((timeB-timeA));
}
it.remove();
}
}
}
}
此時我們啟動NIO的伺服器端。
2.2.2 client
public class IOSocketClient {
private static long TIME=1000*1000*1000;
public static void main(String[] args) {
ExecutorService executorService=Executors.newCachedThreadPool();
for(int i=0;i<10;i++){
executorService.execute(new Runnable() {
@Override
public void run() {
Socket client=null;
BufferedReader reader=null;
PrintWriter writer=null;
try {
client=new Socket();
client.connect(new InetSocketAddress("localhost", 8080));
OutputStream outputStream = client.getOutputStream();
writer=new PrintWriter(new OutputStreamWriter(outputStream),true);
LockSupport.parkNanos(TIME);
writer.print("h");
LockSupport.parkNanos(TIME);
writer.print("e");
LockSupport.parkNanos(TIME);
writer.print("l");
LockSupport.parkNanos(TIME);
writer.print("l");
LockSupport.parkNanos(TIME);
writer.print("o");
LockSupport.parkNanos(TIME);
writer.println("!");
writer.flush();
reader=new BufferedReader(new InputStreamReader(client.getInputStream()));
System.out.println(reader.readLine());
} catch (Exception e) {
} finally{
try {
client.close();
reader.close();
writer.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
});
}
}
}
執行結果
如上圖所示是nio的伺服器端阻塞時間,如上圖所示我們的伺服器端IO並沒有阻塞,也不會佔用伺服器端的資源,總是在客戶端單個執行緒準備好資料以後才進行完整的一次傳輸,如上就是阻塞與非阻塞的區別。當然以後伺服器端程式碼比較複雜,而且也可以進行復用。我們就此產生了nerry框架對nio進行了網路通訊的封裝,以方便我們的編碼人員。但是此時還有一個不好的地方就是,所有的資料等待開啟執行緒還是一個同步的過程,所以怎麼實現非同步非阻塞就是我們的AIO處理的了。
2.3 AIO