The NIO library was introduced with JDK 1.4. NIO was created to allow Java programmers to implement high-speed I/O without having to write custom native code. NIO moves the most time-consuming I/O activites ( namely, filling and draining buffers) back into the operating system, thus allowing for a great increase in speed. The most important distinction between the original I/O library and NIO is how data is packaged and transmitted. Original I/O deals with data in stream, whereas NIO deals with data in blocks
Part1 Buffer 和 Channel
Channel 和 Buffer 的基本使用
Buffer
和 Channel
是NIO中的重要對(duì)象儒旬,幾乎所有的I/O操作都要用到這兩個(gè)對(duì)象。Channel
意為通道聪姿,他的作用類似于流對(duì)象,所有發(fā)送和接受的數(shù)據(jù)都要通過(guò)Channel
沐悦。Buffer
實(shí)質(zhì)上是一個(gè)容器對(duì)象威恼。
所有從Channel
中讀取的數(shù)據(jù)都讀到了Buffer
里,所有寫(xiě)入Channel
的數(shù)據(jù)必須首先存放在Buffer
里距境。
例如:
/*從文件中讀取
*1)從 FileInputStream得到 channel
*2)生成 Buffer 的對(duì)象
*3)從Channel中讀入Buffer
*/
FileInputStream fin = new FileInputStream("read.txt");
FileChannel fc = fin.getChannel();
ByteBuffer buffer = ByteBuffer.allocate( 1024 );
fc.read(buffer) //所有從Channel中讀取的(比如:文件中的)數(shù)據(jù)都讀到了Buffer里
/*向文件中讀入
*1)從 FileOutputStream得到 channel
*2)生成 Buffer 的對(duì)象央拖,填充Buffer對(duì)象
*3)從Buffer中讀入Channel
*/
FileOutputStream fout = new FileOutputStream("read.txt");
FileChannel fc = fout.getChannel();
ByteBuffer buffer = ByteBuffer.allocate( 1024 );
for(int i = 0; i < message.length; i++){
buffer.put(message[i]);
}
buffer.flip(); //flip() 方法祭阀,用于在將數(shù)據(jù)填入buffer和數(shù)據(jù)取出buffer之間的切換
fc.write(buffer) //所有從Channel中寫(xiě)入(比如:文件中)的數(shù)據(jù)都寫(xiě)到了Buffer里
Buffer中的狀態(tài)量
在上一段代碼中,我們看到在Buffer
切換狀態(tài)時(shí)用到了filp()
方法鲜戒,事實(shí)上Buffer
還有其他用于切換狀態(tài)的函數(shù)专控,其中最常用的是filp()
和clear()
。
這兩個(gè)方法通常的用法如下:
buffer.clear(); //在buffer填入數(shù)據(jù)之前調(diào)用
int r = fcin.read(buffer);
if(r == -1){
break;
}
buffer.flip();//在buffer填入數(shù)據(jù)之后遏餐,取出數(shù)據(jù)之前調(diào)用
fcout.write(buffer);
為什么會(huì)出現(xiàn)吧這樣的情況呢伦腐?
這是因?yàn)?code>Buffer中有幾個(gè)狀態(tài)量,position
失都、limit
和 capacity
柏蘑。這在對(duì)Buffer
進(jìn)行操作時(shí),這三個(gè)狀態(tài)量不停變化粹庞,從而決定Buffer
的可操作范圍咳焚。
//TODO 關(guān)于buffer的三個(gè)狀態(tài)量的解釋
//TODO advanced JNIO
Part2 網(wǎng)絡(luò)編程中用到的Channel
譯自:Java NIO指南
DatagramChannel
操作1:打開(kāi)DatagramChannel
用open()
/*
*在這個(gè)例子中,我們打開(kāi)了一個(gè) DatagramChannel庞溜,并且可
*以從 9999 端口接收UDP數(shù)據(jù)報(bào)革半。
*/
DatagramChannel channel = DatagramChannel.open();
channel.socket().bind( new InetSocketAddress(9999));
操作2:接收數(shù)據(jù) receive()
/*
*receive()方法會(huì)將收到的packet中的數(shù)據(jù)拷貝到Buffer中
*如果Buffer不足以容納接收到的數(shù)據(jù),超出容納空間的數(shù)據(jù)
*將被靜默地丟棄
*/
ByteBuffer buf = ByteBuffer.allocate( 1024 );
buf.clear();
channel.recrive(buf);
操作3:發(fā)送數(shù)據(jù)send()
/*
*這個(gè)例子中我們向“jenkov.com”的80端口發(fā)送了一個(gè)字符串
*但是强缘,我們得不到發(fā)送的數(shù)據(jù)被接受或者沒(méi)有被接收的反饋
*因?yàn)閁DP不對(duì)數(shù)據(jù)送達(dá)作出任何保證督惰。
*/
String newData = "New String to write to file..."
+System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate( 1024 );
buf.clear();
buf.put(newData.getBytes());
buf.flip();
int bytesSent = channel.send(buf,
new InetSocketAddress("jenkov.com", 80));
操作4:鏈接到特定地址connect()
/*
*這個(gè)例子中的connect()不同于TCP中的鏈接不傅,這個(gè)例子中的
*connect()并不建立真的鏈接旅掂,而是將channel有特定的地址
*綁定,但仍然不保證數(shù)據(jù)一定被送達(dá)访娶。
*/
channel.connect(new InetSocketAddress("jenkov.com", 80));
//鏈接到特定地址后可以直接使用channel的read()和write()方法
int bytesRead = channel.read(buf);
int bytesWrriten = channel.write(buf);
SocketChannel
操作1:打開(kāi)SocketChnnel
SocketChannel socketChannel = socketChannel.open();
socketChannel.connect(new InetSocketAddress("http://jenvok.com", 80));
操作2:關(guān)閉SocketChannel
socketChannel.close();
操作3:從SocketChannel
中讀取數(shù)據(jù)
ByteBuffer buf = ByteBuffer.allocate( 1024 );
//返回值表示buf接受了多少字節(jié)的數(shù)據(jù)商虐,-1表示鏈接斷開(kāi)
int bytesRead = socketChannel.read(buf);
操作4:向SocketChannel
中寫(xiě)入數(shù)據(jù)
String newData = "New String to write to file..."
+System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate( 1024 );
buf.clear();
buf.put(newData.getBytes());
buf.flip();
//注意:SocketChannel.write()在while循環(huán)內(nèi)調(diào)用,因?yàn)椴荒?//保證write()方法會(huì)向channel中寫(xiě)入多少比特,因此我們反復(fù)
//調(diào)用write()方法秘车,知道buf的內(nèi)容全部被寫(xiě)入
while(buf.hasRemaining()){
channel.write(buf);
}
操作5:非阻塞模式下的connect()
典勇、write()
和 read()
方法
connect()
方法:
如果 SocketChannel
是非阻塞的,當(dāng)我們調(diào)用connect()
并返回時(shí)叮趴,鏈接可能還沒(méi)有建立割笙。因此,我們需要調(diào)用finishConnect()
方法進(jìn)行檢測(cè)眯亦。
socketChannel.configureBlocking(false);
socketChannel.connect(
new InetSocketAddress("http://jenlov.com",80));
while(! socketChannel.finishConnectt()){
//wait, or do something else...
}
write()
方法:
不需要特殊變化伤溉,因?yàn)?code>write()方法已經(jīng)在循環(huán)中調(diào)用了。
read()
方法:
需要注意這個(gè)方法的返回值妻率,因?yàn)榉祷刂当硎緦?shí)際實(shí)際讀到多少數(shù)據(jù)乱顾。
ServerSocketChannel
操作1:打開(kāi)SeverSocketChannel
ServerScoketChannel serverSocketChannel = ServerSocketChannel.open();
操作2:關(guān)閉ServerSocketChannel
serverSocketChannel.close();
操作3:監(jiān)聽(tīng)到來(lái)的連接
/*
*由于要監(jiān)聽(tīng)多個(gè)鏈接,所以將accept()放在while循環(huán)之中當(dāng)然在實(shí)際
*編程中會(huì)用其他條件替換while循環(huán)中的true
*/
while(true){
SocketChannel socketChannel = serverSocketChannel.accept();
//do something with socketChannel
}
操作4:非阻塞模式
/*
*當(dāng)沒(méi)有連接到來(lái)時(shí)宫静,accept()返回null
*/
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket.bind(new InetSocketAddress(9999));
serverSocketChannel.configureBlocking(false);
while(true){
SocektChannel socketChannel = serverSocketChannel.accept();
if(socketChannel != null){
//do something with socketChannel...
}
}
Part3 對(duì)象的序列化
為了硬盤存儲(chǔ)和網(wǎng)絡(luò)傳輸?shù)男枰呔唬覀冃枰獙?duì)Java對(duì)象序列化(Object->bytes);相反孤里,在需要使用Java對(duì)象時(shí)伏伯,我們需要進(jìn)行反序列化(bytes->Object)。
serialVersionUID的概念
serialVersionUID是在對(duì)象反序列化是用來(lái)確保版本一致性的捌袜。在進(jìn)行反序列化時(shí)舵鳞,JVM會(huì)把傳來(lái)的字節(jié)流中的serialVersionUID與本地相應(yīng)實(shí)體(類)的serialVersionUID進(jìn)行比較,如果相同就認(rèn)為是一致的琢蛤,可以進(jìn)行反序列化蜓堕,否則就會(huì)出現(xiàn)異常。
serialVersionUID的類型必須是如下形式:
ANY-ACCESS-MODIFIER static final long
序列化過(guò)程
需要進(jìn)行序列化的對(duì)象必須實(shí)現(xiàn)Serializable接口博其。這是一個(gè) maker interface套才,也就是一個(gè)空的接口。它的源碼如下:
public interface Serializable{
}
反序列化機(jī)制
其他情況
- 如果需要序列化的對(duì)象中有其他對(duì)象的引用慕淡,其他對(duì)象也必須實(shí)現(xiàn)Serializable接口背伴。
- TODO
- TODO
- TODO
- TODO
- TODO
- TODO
Part4 網(wǎng)絡(luò)和異步I/O
異步I/O是一種非阻塞的I/O。在調(diào)用異步I/O之后峰髓,通過(guò)對(duì)關(guān)心的事件進(jìn)行注冊(cè)傻寂,當(dāng)這些事件(如 :有可以讀取的數(shù)據(jù)到達(dá)、一個(gè)新的socket連接携兵,等)發(fā)生時(shí)會(huì)得到系統(tǒng)通知疾掰。異步I/O/的優(yōu)勢(shì)在于,可以監(jiān)聽(tīng)任意數(shù)量的Channels的I/O事件徐紧,而不用使用輪詢或者創(chuàng)建額外的線程静檬。
Selectors
Sector
是異步I/O的核心組件炭懊。我們?cè)?code>Selector上注冊(cè)感興趣的I/O事件,在這些事件發(fā)生的時(shí)候 Selector
就會(huì)通知我們拂檩。
操作1:打開(kāi)Selector
Selector selector = Selector.open();
操作2:用非阻塞方式打開(kāi)ServerSocketChannel
為了接收連接侮腹,我們需要ServerSockeChanne
。為了保證異步I/O操作稻励,ServerSocketChannel
要以非阻塞方式打開(kāi)父阻。
SeverSocketChannel ssc = SeverSocketChannel.open();
ssc.configureBlocking( false );
ServerSocket ss = ssc.socket();
InetSocketAddress address = new InetSocketAddress( port[i] );
ss.bind( address );
Selection Keys
操作3:注冊(cè)ServerSocketChannel
到Selector
使用ServerSocketChannel.register()
方法。這個(gè)方法的第一個(gè)參數(shù)是Selector
望抽,第二個(gè)參數(shù)是要監(jiān)測(cè)的事件(如:accept)至非。當(dāng)有監(jiān)測(cè)的事件發(fā)生時(shí),Selector
將它添加到SelectionKey
中糠聪。
SelectionKey key = ssc.register( selector, SelectionKey.PO_ACCEPT);
操作4:監(jiān)聽(tīng)事件
//阻塞直到至少有一個(gè)事件發(fā)生
int num = selector.select();
Set selectedKeys = selector.seectedKeys();
for(SelectionKye key: selectedKeys){
//deal with I/O event
}
操作5:檢查發(fā)生的事件的類型
//確認(rèn)事件是accept
if( ( key.readOps() ) & SelectionKey.OP_ACCEPT == SelectionKey.OP_ACCEPT){
//Accept the new connection
//...
}
操作6:接受一個(gè)新連接
上一步荒椭,我們已經(jīng)確認(rèn)youyige連接在等待,ServerSocket
接受舰蟆,所以我們可以安全的接受這個(gè)連接而不用擔(dān)心accept()
方法阻塞趣惠。
ServerSocketChannel ssc = key.channel();
SocketChannel sc = ssc.accept();
操作7:注冊(cè)新接收的ScoketChannel
sc.configureBloking( false );
SelectionKey newkey = sc.register( selector, Selection.OP_READ);
操作8:刪除處理過(guò)的key
selectedKeys.remove(key);
操作9:接受到來(lái)的數(shù)據(jù)
if( (key.readyOps() & Selectionkey.OP_READ) == SelectionKey.OP_READ){
SocketChannel sc = (SocketChannel)key.channel();
}
Part5 并發(fā)
在并發(fā)編程中有兩個(gè)基本的執(zhí)行單元:進(jìn)程和線程。在Java中身害,并發(fā)編程主要指多線程并發(fā)味悄。
Thread對(duì)象
**兩種定義方法
- 實(shí)現(xiàn)Runnable接口
- 繼承Thread類
public class HelloRunnable implements Runnable{
public void run(){
System.out.println("Hello from a thread!");
}
public static void main(String args[]){
(new Thread(new HelloRunnable())).start();
}
}
public class HelloThread exends Thread{
public void run(){
System.out.println("Hello from a thread!");
}
public static void main(String args[]){
(new HelloThread()).start;
}
}
//待續(xù)