java-IO操作簡介

流:字節流和字符流
字節流:處理二進制數據,用於處理原始數據,不應該用來處理文本內容
字符流:處理字符數據,自動轉換本地字符集
行尾結束符: \r\n 或者\r 或者\n
對象流:處理對象二進制數據
需要對流進行關閉:
字符流操作:Reader,Writer
字節流操作:InputStream,OutputStream
Channel,GatheringByteChannel,InterruptibleChannel
ReadableByteChannel,ScatteringByteChannel,WritableByteChannel
Channels, DatagramChannel,FileChannel,FileLock,Pipe
SelectableChannel,SelectionKey,Selector,ServerSocketChannel,SocketChannel

中文兩個字節,英文1個字節,一個字節8bit,高位補零
字節流不會使用內存緩衝,文件本身直接操作
字符流操作使用內存緩衝,用緩衝操作文件,字符流在輸出前將內容保存在內容中

主要有四類操作:
1:字節操作:InputStream和OutputStream
2:字符操作:Reader和Writer
3:磁盤操作:File
4:網絡操作:Socket

無論是網絡還是磁盤,存儲單元都是字節,字符是在字節的基礎上進行了轉換:
InputStreamReader
拿到字節數據後,要指定編碼字符集,否則採用的是系統默認的字符集

磁盤IO機制:
系統在內核空間中加入了緩存機制,如果用戶訪問的是同一磁盤地址的空間數據,那麼會從緩存中返回
標準訪問方式:
調用read時,操作系統檢查內核緩存中有沒有數據,如果沒有則從磁盤讀取並緩存到內核緩存
調用write時:先複製到緩存中,對於用戶來說操作已經完成,什麼時候寫入磁盤由操作系統決定
直接IO方式:
應用直接訪問磁盤,不經過系統內核數據緩存區,通常使用在數據庫管理系統中
同步訪問方式:讀取和寫入都是同步操作,寫入經過高速緩存然後進入磁盤,讀取經過磁盤到高速緩存
異步訪問方式:發起讀寫請求後,線程去處理其他的,等待高速緩存從磁盤讀取回來後再操作
內存映射方式:把內存的一塊區域和文件關聯起來,文件的數據就是內存中的數據

FileInputStream會創建一個FileDescriptor對象,這個對象是真正代表一個存在的文件對象的描述
可以通過getFD犯法獲取真正操作與底層操作系統相關的文件的描述

傳入文件名時,創建一個File對象。讀取File對象內容時,創建一個FileDescriptor操作,使用StreamDecoder類將byte解碼成char格式。然後返回char數據

Java序列化技術:java序列化就是將一個對象轉換成一串二進制字節數組。這樣,對象就能保存和傳輸,進行持久化。對象需要實現Serializable接口
序列化的二進制數組主要包含5個部分內容:
1:序列化文件頭:使用的序列化協議,序列化協議,該數組的類型(比如說是一個對象)
2:序列化類的描述,比如是Serialize類。包含一個類聲明,class名字長度,完整類名,序列化ID,如果沒有指定則生成一個8字節的ID,還有該類的字段個數
3:對象中各個屬性項的描述
4:該對象的父類信息描述
5:該對象的屬性項的實際值。

序列化的複雜情況:
1:父類實現Serializable接口時,所有子類都可以被序列化
2:子類實現Serializable接口,而父類沒有實現,則父類屬性不能被序列化,不報錯,數據丟失,子類仍然能序列化。
3:如果序列化的對象的屬性也是對象,那麼子對象也需要實現序列化接口,否則報錯
4:反序列化時,對象的屬性有修改或刪減,則修改部分的屬性會丟失,但不會報錯
5:反序列化時,如果serialVersionUID被修改,則反序列化時會失敗

TCP連接:
TCP狀態:
CLOSED:
LISTEN:等待連接狀態
SYN-SENT:客戶端發起連接,發送SYN給服務器,如果接受不到則直接進入CLOSED
SYN-RCVD:服務器接收請求,返回ACK響應
ESTABLISHED:服務器端和客戶端在完成3次握手後進入該狀態,說明可以傳輸數據了
FIN-WAIT-1:發送FIN給對方
FIN-WAIT-2:接收FIN ACK數據
CLOSE-WAIT:
LAST-ACK:發起關閉請求
CLOSING
TIME-WAIT:

closed--->listen :被動打開
closed--->syn-send:主動打開,發送SYN
listen->syn-received:收到syn,發送syn-ack
syn-send->syn-received:同時打開,收到syn,發送syn-ack
syn-received-------->established:收到ack
syn-send---------->established:收到syn-ack,發送ack
established-------->FIN-WAIT1:關閉,發送FIN
FIN-WAIT1-------->FIN-WAIT2:收到對FIN的ACK
FIN-WAIT2------->TIME-WAIT:收到FIN,發送ACK
FIN-WAIT1------>CLOSING:收到FIN,發送ACK,同時關閉
CLOSING------->TIME-WAIT:收到對FIN的應答
ESTABLISHED-------->CLOSE-WAIT:收到FIN,發送ACK
CLOSE-WAIT--------->LAST-ACK:等待應用程序關閉,發送FIN
LAST-ACK------------>CLOSED:收到對FIN的ACK

影響網絡傳輸因素:
1:帶寬:1秒傳輸最大比特數
2:傳輸距離:光纖的折射率導致有傳輸延遲。
3:TCP擁堵:停-等-停-等

NIO工作方式:非阻塞IO
一旦發生阻塞,線程就會失去CPU的使用權,在大規模訪問下是不能允許的。

工作過程:
0:創建一個ByteBuffer來獲取數據
ByteBuffer buffer = ByteBuffer.allocate(1024);
1:Selector靜態工廠方法創建一個選擇器selector
Selector selecotr = Selector.open();
2:創建一個服務器端的Channel,然後綁定到一個Socket對象,並註冊到selector上
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);//非阻塞模式
ssc.socket.bind(new InetSocketAddress(8080));
ssc.register(selector,SelectionKey.OP_ACCEPT);//註冊監聽事件
3:調用selector的selectedKeys方法來檢查是否有事件發生,如果有事件發生則返回SelectionKey,通過這個對象的Channel方法獲得通信信道對象:
Set selectedKeys = selector.selectedKeys();
SelectionKey key = 遍歷selectedKeys
if(key.readOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT){
ServerSocketChannel ssChannel = (ServerSocketChannel) key.channel();
SocketChannel sc = ssChannel.accept();//
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ)
} else if((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
SocketChannel sc = (SocketChannel) key.channel();
sc.read(buffer);
buffer.flip();
}

RAID策略:
RAID0:數據被平均寫入多個磁盤陣列,寫數據和讀數據都是並行的,所以磁盤的IOPS可以提高一倍
RAID1:將一份數據複製到多個磁盤陣列中,不能提高IOPS,但能提高安全性
RAID5:前兩種方式的折中。將數據平均寫到所有磁盤陣列總數減1的磁盤中,往另外一個磁盤中寫入這份數據的奇偶校驗信息。如果其中一個磁盤數據損壞,可以通過其他磁盤的數據和這個數據的奇偶校驗信息來恢復這份數據。
RAID0+1:根據數據的備份情況進行分組,一份數據同時寫入多個備份磁盤分組中,同時多個分組也會進行並行讀寫。

java中操作字符的Reader和Writer中有StreamDecoder,StreamEncoder,負責字符和字節的轉換。
內存中的編碼:在內存中進行從字符到字節的轉換。String類提供了getBytes(字符集)來轉換
和new String(bytes, 字符集);
已經廢棄的ByteToCharConverter和CharToByteConverter,使用Charset取代,Charset提供encode和decode方法
還有一個ByteBuffer類,提供char和byte的軟交換,他們之間的轉換不需要編碼,僅僅只是把一個16bit的char拆分成2個8bit的byte表示。

java內存編碼使用的是utf-16編碼,效率高,但不利於網絡之前傳輸。
因爲網絡傳輸容易損壞字節流,字節流損壞將很難恢復,所以相比較utf-8更適合網絡傳輸。
utf-8對ascii字符使用單字節存儲,另外單個字符損壞也不會影響後面的其他字符。是理想的編碼方式。

大多數IO都是沒有緩衝的,這意味着每個讀寫請求都是底層OS直接處理的。這讓程序比較低效,因爲每次請求都會觸發磁盤訪問,網絡活動或一些相對昂貴的操作
爲了減少這種類型的開銷,java平臺實現緩衝IO stream。緩衝的輸入流從內存區域讀取數據作爲buffer,僅當buffer爲空的時候,纔會調用原生輸入api。類似的,緩衝的輸出流寫入數據到一個buffer,當buffer滿了的時候原生輸出api纔會被調用。
flush緩衝流:在關鍵點寫出緩衝區而不需要等它填充是有意義的。這個被稱爲flush
一些緩衝輸出類支持自動flush,通過構造參數指定一個選項。當自動刷新開啓,特定關鍵事件會導致緩衝會被flush

NIO:
標準IO是面向字節流和字符流的,而NIO是面向通道和緩衝區的,數據總是從通道中讀到buffer緩衝區內,或者從buffer寫入通道
核心組件:
Channels:Buffer的數據從Channel中讀取或寫入
FileChannel,DatagramChannel,SocketChannel,ServerSocketChannel
Buffers:實際就是一個塊內存,有三個主要屬性:capacity容量,position位置,limit限制
ByteBuffer,CharBuffer,DoubleBuffer,FloatBuffer,IntBuffer,LongBuffer,ShortBuffer

Selectors:選擇器允許單線程操作多個通道,Channel需要註冊到Selector上

RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
buf.flip(); //make buffer ready for read
while(buf.hasRemaining()){
System.out.print((char) buf.get()); // read 1 byte at a time
}
buf.clear(); //make buffer ready for writing
bytesRead = inChannel.read(buf);

FileChannel類型可以把數據直接傳輸到另一個channel上:
兩個都是FileChannel:
aChannel.transferFrom(bChannel);
aChannel.transferTo(bChannel);

註冊selector:
Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

Unrecognized Windows Sockets error: 0: recv failed:
socket連接中有數據未處理

=================
編寫Socket服務器:
只需要返回http協議中的內容即可
BufferedOutputStream outputStream = new BufferedOutputStream(socket.getOutputStream());
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream));
writer.write("HTTP/1.1 200 OK");
writer.newLine();
writer.write("Server: Apache-Coyote/1.1");
writer.newLine();
writer.write("Content-Type: text/html;charset=UTF-8");
writer.newLine();
String body = "<html><head></head><body>1234</body></html>";
writer.write("Content-Length: " + body.length());
writer.newLine();
writer.write("Date: Mon, 03 Nov 2014 06:37:28 GMT");
writer.newLine();
writer.write("\n" + body);
writer.flush();
writer.close();
在body處需要\n,否則瀏覽器不能正確識別響應實體
在讀取請求的數據時,只能判斷最後的內容爲空字符串,不能判斷爲null
ServerSocket serverSocket = new ServerSocket(1099);
Socket socket = serverSocket.accept();
BufferedInputStream inputStream = new BufferedInputStream(socket.getInputStream());
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String lineStr = null;
while (!(lineStr=reader.readLine()).equals("")){
System.out.println(lineStr);
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章