今天來復習一下基礎IO,也就是最普通的IO。
輸入流與輸出流
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多路復用,可以回顧前面一篇分享。敬請期待吧...