漫談Java IO之普通IO流與BIO服務器
阿新 • • 發佈:2018-03-28
com 釋放 膨脹 AR oca mar ace 暴露 pos
今天來復習一下基礎IO,也就是最普通的IO。
- 網絡IO的基本知識與概念
- 普通IO以及BIO服務器
- NIO的使用與服務器Hello world
- Netty入門與服務器Hello world
- Netty深入淺出
輸入流與輸出流
Java的輸入流和輸出流,按照輸入輸出的單元不同,又可以分為字節流和字符流的。
JDK提供了很多輸入流和輸出流,比如:
字節流可以按照不同的變量類型進行讀寫,而字符流則是基於字符編碼的。不同的字符編碼包含的字節數是不一樣的,因此在使用字符流時,一定要註意編碼的問題。
讀寫
字節的輸入輸出流操作
// 字節輸入流操作 InputStream input = new ByteArrayInputStream("abcd".getBytes()); int data = input.read(); while(data != -1){ System.out.println((char)data); data = input.read(); } // 字節輸出流 ByteArrayOutputStream output = new ByteArrayOutputStream(); output.write("12345".getBytes()); byte[] ob = output.toByteArray();
字符的輸入輸出流操作
// 字符輸入流操作 Reader reader = new CharArrayReader("abcd".toCharArray()); data = reader.read(); while(data != -1){ System.out.println((char)data); data = reader.read(); } // 字符輸出流 CharArrayWriter writer = new CharArrayWriter(); writer.write("12345".toCharArray()); char[] wc = writer.toCharArray();
關閉流
流打開後,相當於占用了一個文件的資源,需要及時的釋放。傳統的標準關閉的方式為:
// 字節輸入流操作 InputStream input = null; try { input = new ByteArrayInputStream("abcd".getBytes()); int data = input.read(); while (data != -1) { System.out.println((char) data); // todo data = input.read(); } }catch(Exception e){ // todo }finally { if(input != null) { try { input.close(); } catch (IOException e) { e.printStackTrace(); } } }
在JDK1.7後引入了try-with-resources的語法,可以在跳出try{}的時候直接自動釋放:
try(InputStream input1 = new ByteArrayInputStream("abcd".getBytes())){
//
}catch (Exception e){
//
}
IOUtils
直接使用IO的API還是很麻煩的,網上的大多數教程都是各種while循環,操作很麻煩。其實apache common已經提供了一個工具類——IOUtils,可以方便的進行IO操作。
比如IOUtils.readLines(is, Charset.forName("UTF-8"));
可以方便的按照一行一行讀取.
BIO阻塞服務器
基於原始的IO和Socket就可以編寫一個最基本的BIO服務器。
概要: 這個模型很簡單,就是主線程(Acceptor)負責接收連接,然後開啟新的線程專門負責連接處理客戶端的請求。
import io.netty.util.CharsetUtil;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class PlainOioServer {
public void serve(int port) throws IOException {
// 開啟Socket服務器,並監聽端口
final ServerSocket socket = new ServerSocket(port);
try{
for(;;){
// 輪訓接收監聽
final Socket clientSocket = socket.accept();
try {
Thread.sleep(500000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("accepted connection from "+clientSocket);
// 創建新線程處理請求
new Thread(()->{
OutputStream out;
try{
out = clientSocket.getOutputStream();
out.write("Hi\r\n".getBytes(CharsetUtil.UTF_8));
out.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try{
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
} catch (IOException e){
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
PlainOioServer server = new PlainOioServer();
server.serve(5555);
}
}
然後執行telnet localhost 5555
,就能看到返回結果了。
這種阻塞模式的服務器,原理上很簡單,問題也容易就暴露出來:
- 服務端與客戶端的連接相當於1:1,因此如果連接數上升,服務器的壓力會很大
- 如果主線程Acceptor阻塞,那麽整個服務器將會阻塞,單點問題嚴重
- 線程數膨脹後,整個服務器性能都會下降
改進的方式可以基於線程池或者消息隊列,不過也存在一些問題:
- 線程池的數量、消息隊列後端服務器並發處理數,都是並發數的限制
- 仍然存在Acceptor的單點阻塞問題
接下來,將會介紹基於Nio的非阻塞服務器模式,如果忘記什麽是IO多路復用,可以回顧前面一篇分享。敬請期待吧...
漫談Java IO之普通IO流與BIO服務器