更新時間:2022-10-14 來源:黑馬程序員 瀏覽量:
IT就到黑馬程序員.gif)
NIO是New I/O的簡稱,與舊式基于流的I/O相對,從名字上來看,它表示新的一套I/O標準。它是從JDK1.4中被納入到JDK中的。
與舊式的IO流相比,NIO是基于Block的,它以塊為單位來處理數(shù)據(jù),最為重要的兩個組件是緩沖區(qū)Buffer和通道Channel。緩沖區(qū)是一塊連續(xù)的內(nèi)存塊,是NIO讀寫數(shù)據(jù)的載體;通道表示緩沖數(shù)據(jù)的源頭和目的地,它用于向緩沖區(qū)讀取或者寫入數(shù)據(jù),是訪問緩沖區(qū)的接口。

Buffer的基本原理
Buffer中最重要的3個參數(shù):位置(position)、容量(capacity)、上限(limit)。他們3者的含義如下
位置(position): 表示當前緩沖區(qū)的位置,從position位置之后開始讀寫數(shù)據(jù)。 容量(capacity): 表示緩沖區(qū)的最大容量 上限(limit): 表示緩沖區(qū)的實際上限,它總是小于或等于容量

緩沖區(qū)的容量(capacity)是不變的,而位置(position)和上限(limit)和以根據(jù)實際需要而變化。也就是說,可以通過改變當前位置和上限來操作緩沖區(qū)內(nèi)任意位置的數(shù)據(jù)。
Buffer的常用方法
NIO提供一系列方法來操作Buffer的位置(position)和上限(limit),以及向緩沖區(qū)讀寫數(shù)據(jù)。
put() //向緩沖區(qū)position位置添加數(shù)據(jù)。并且position往后移動,不能超過limit上限。 get() //讀取當前position位置的數(shù)據(jù)。并且position往后移動,不能超過limit上限。 flip() //將limit置位為當前position位置,再講position設(shè)置為0 rewind() //僅將當前position位置設(shè)置為0 remaining //獲取緩沖區(qū)中當前position位置和limit上限之間的元素數(shù)(有效的元素數(shù)) hasRemaining() //判斷當前緩沖區(qū)是否存在有效的元素數(shù) mark() //在當前position位置打一個標記 reset() //將當前position位置恢復(fù)到mark標記的位置。 duplicate() //復(fù)制緩沖區(qū)
創(chuàng)建緩沖區(qū)
//創(chuàng)建一個容量為10的緩沖區(qū)
ByteBuffer byteBuffer1=ByteBuffer.allocate(10);
//創(chuàng)建一個包裹數(shù)據(jù)的緩沖區(qū)
ByteBuffer byteBuffer2 = ByteBuffer.wrap("abcde".getBytes());獲取/設(shè)置緩沖區(qū)參數(shù)
//創(chuàng)建一個容量為10的緩沖區(qū)
ByteBuffer byteBuffer=ByteBuffer.allocate(10);
System.out.println("位置:"+byteBuffer.position()); //0
System.out.println("上限:"+byteBuffer.limit()); //10
System.out.println("容量:"+byteBuffer.capacity());//10

添加數(shù)據(jù)到緩沖區(qū)
//創(chuàng)建一個容量為10的緩沖區(qū)
ByteBuffer byteBuffer=ByteBuffer.allocate(10);
//添加數(shù)據(jù)到緩沖區(qū)
byteBuffer.put("abcde".getBytes());
System.out.println("position位置:"+byteBuffer.position()); //5
System.out.println("limit上限:"+byteBuffer.limit()); //10
System.out.println("capacity容量:"+byteBuffer.capacity()); //10

rewind重置緩沖區(qū)
rewind函數(shù)將position置為0位置,并清除標記。
//創(chuàng)建一個容量為10的緩沖區(qū)
ByteBuffer byteBuffer=ByteBuffer.allocate(10);
//添加數(shù)據(jù)到緩沖區(qū)
byteBuffer.put("abcde".getBytes());
System.out.println("position位置:"+byteBuffer.position()); //5
System.out.println("limit上限:"+byteBuffer.limit()); //10
System.out.println("capacity容量:"+byteBuffer.capacity()); //10
System.out.println("------------");
//重置緩沖區(qū)
byteBuffer.rewind();
System.out.println("position位置:"+byteBuffer.position()); //0
System.out.println("limit上限:"+byteBuffer.limit()); //10
System.out.println("capacity容量:"+byteBuffer.capacity());//10

flip重置緩沖區(qū)
flip函數(shù)現(xiàn)將limit設(shè)置為position位置,再將position置為0位置,并清除mar標記。
//創(chuàng)建一個容量為10的緩沖區(qū)
ByteBuffer byteBuffer=ByteBuffer.allocate(10);
//添加數(shù)據(jù)到緩沖區(qū)
byteBuffer.put("abcde".getBytes());
System.out.println("position位置:"+byteBuffer.position()); //5
System.out.println("limit上限:"+byteBuffer.limit()); //10
System.out.println("capacity容量:"+byteBuffer.capacity()); //10
System.out.println("------------");
//重置緩沖區(qū)
byteBuffer.flip();
System.out.println("position位置:"+byteBuffer.position()); //0
System.out.println("limit上限:"+byteBuffer.limit()); //5
System.out.println("capacity容量:"+byteBuffer.capacity());//10

clear清空緩沖區(qū)
clear方法也將position置為0,同時將limit置為capacity的大小,并清除mark標記。
//創(chuàng)建一個容量為10的緩沖區(qū)
ByteBuffer byteBuffer=ByteBuffer.allocate(10);
//設(shè)置上限為5
byteBuffer.limit(5);
//添加數(shù)據(jù)到緩沖區(qū)
byteBuffer.put("abcde".getBytes());
System.out.println("position位置:"+byteBuffer.position()); //5
System.out.println("limit上限:"+byteBuffer.limit()); //5
System.out.println("capacity容量:"+byteBuffer.capacity()); //10
System.out.println("------------");
//重置緩沖區(qū)
byteBuffer.clear();
System.out.println("position位置:"+byteBuffer.position()); //0
System.out.println("limit上限:"+byteBuffer.limit()); //10
System.out.println("capacity容量:"+byteBuffer.capacity());//10

標記和恢復(fù)
ByteBuffer buffer = ByteBuffer.allocate(10);
//添加數(shù)據(jù)到緩沖區(qū)
buffer.put("abcde".getBytes());
//打一個標記
buffer.mark();
System.out.println("標記位置:"+buffer.position()); //5
//再添加5個字節(jié)數(shù)據(jù)到緩沖區(qū)
buffer.put("fijkl".getBytes());
System.out.println("當前位置:"+buffer.position()); //10
//將position恢復(fù)到mark標記位置
buffer.reset();
System.out.println("恢復(fù)標記位置:"+buffer.position());//5 FileChannel通道
FileChannel是用于操作文件的通道,可以用于讀取文件、也可以寫入文件
//創(chuàng)建讀取文件通道
FileChannel fisChannel = new FileInputStream("day05/src/a.txt").getChannel();
//創(chuàng)建寫入文件的通道
FileChannel fosChannel = new FileOutputStream("day05/src/b.txt").getChannel();
//創(chuàng)建緩沖區(qū)
ByteBuffer buffer = ByteBuffer.allocate(2);
while (fisChannel.read(buffer)!=-1){
System.out.println("position:"+buffer.position()); //0
System.out.println("limit:"+buffer.limit());//2
//重置緩沖區(qū)(為輸出buffer數(shù)據(jù)做準備)
buffer.flip();
fosChannel.write(buffer);
//重置緩沖區(qū)(為輸入buffer數(shù)據(jù)做準備)
buffer.clear();
}
//關(guān)閉通道
fisChannel.close();
fosChannel.close();
SocketChannel通道
下面代碼使用SocketChannel通道上傳文件到服務(wù)器
public class Client {
public static void main(String[] args) throws IOException {
//創(chuàng)建通道
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 10000));
//創(chuàng)建緩沖區(qū)
ByteBuffer buffer = ByteBuffer.allocate(1024);
//讀取本地文件數(shù)據(jù)到緩沖區(qū)
FileChannel fisChannel = new FileInputStream("day05/src/a.txt").getChannel();
while (fisChannel.read(buffer)!=-1){
buffer.flip(); //為寫入數(shù)據(jù)做準備
socketChannel.write(buffer);
buffer.clear(); //為讀取數(shù)據(jù)做準備
}
//關(guān)閉本地通道
fisChannel.close();
//socketChannel.shutdownOutput();
//讀取服務(wù)端回寫的數(shù)據(jù)
buffer.clear();
int len = socketChannel.read(buffer);
System.out.println(new String(buffer.array(), 0, len));
//關(guān)閉socket通道
socketChannel.close();
}
} ServerSocketChannel通道
下面代碼使用ServerSocketChannel通道接收文件并保存到服務(wù)器
public class Server {
public static void main(String[] args) throws IOException {
//1.創(chuàng)建ServerSocketChannel通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2.綁定端口號
serverSocketChannel.bind(new InetSocketAddress(10000));
//3.設(shè)置非阻塞
serverSocketChannel.configureBlocking(false);
System.out.println("服務(wù)器已開啟");
while (true) {
//4.獲取客戶端通道,如果有客戶端連接返回客戶端通道,否則返回null
SocketChannel socketChannel = serverSocketChannel.accept();
if(socketChannel!=null){
socketChannel.configureBlocking(false);
//創(chuàng)建本地通道,用于往文件中寫數(shù)據(jù)
UUID uuid = UUID.randomUUID();
FileChannel fosChannel=new FileOutputStream("day05/src/"+uuid+".txt").getChannel();
ByteBuffer buffer=ByteBuffer.allocate(1024);
while (socketChannel.read(buffer)>0){
buffer.flip(); //準備把緩沖區(qū)數(shù)據(jù)輸出
fosChannel.write(buffer);
buffer.clear();//準備讀取數(shù)據(jù)到緩沖區(qū)
}
fosChannel.close();
//回寫數(shù)據(jù)到客戶端
ByteBuffer resultBuffer=ByteBuffer.wrap("上傳文件成功".getBytes());
socketChannel.write(resultBuffer);
//關(guān)閉客戶端通道
socketChannel.close();
}
}
}
} NIO Selector選擇器
Selector一般稱為選擇器,當然你也可以翻譯為 **多路復(fù)用器** 。它是Java NIO核心組件中的一個,用于檢查一個或多個NIO Channel(通道)的狀態(tài)是否處于可讀、可寫。如此可以實現(xiàn)單線程管理多個channels,也就是可以管理多個網(wǎng)絡(luò)鏈接。

使用Selector的服務(wù)器模板代碼
有了模板代碼我們在編寫程序時,大多數(shù)時間都是在模板代碼中添加相應(yīng)的業(yè)務(wù)代碼
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress("localhost", 8080));
ssc.configureBlocking(false);
Selector selector = Selector.open();
ssc.register(selector, SelectionKey.OP_ACCEPT);
while(true) {
int readyNum = selector.select();
if (readyNum == 0) {
continue;
}
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
while(it.hasNext()) {
SelectionKey key = it.next();
if(key.isAcceptable()) {
// 接受連接
} else if (key.isReadable()) {
// 通道可讀
} else if (key.isWritable()) {
// 通道可寫
}
it.remove();
}
} NIO Selector服務(wù)端
按照上面的模板代碼,改寫接收文件的服務(wù)端。
public class Server {
public static void main(String[] args) {
try {
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress("localhost", 10000));
ssc.configureBlocking(false);
Selector selector = Selector.open();
ssc.register(selector, SelectionKey.OP_ACCEPT);
FileChannel fosChannel=null;
while (true) {
int readyNum = selector.select();
System.out.println(readyNum);
if (readyNum == 0) {
continue;
}
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
SocketChannel socketChannel1=null;
SocketChannel socketChannel2=null;
if (key.isAcceptable()) {
System.out.println("isAcceptable");
// 創(chuàng)建新的連接,并且把連接注冊到selector上,而且,
// 聲明這個channel只對讀操作感興趣。
socketChannel1 = ssc.accept();
socketChannel1.configureBlocking(false);
socketChannel1.register(selector, SelectionKey.OP_READ);
UUID uuid = UUID.randomUUID();
fosChannel=new FileOutputStream("day05/src/"+uuid+".txt").getChannel();
} else if (key.isReadable()) {
System.out.println("isReadable");
// 通道可讀
socketChannel2 = (SocketChannel) key.channel();
//創(chuàng)建本地通道,用于往文件中寫數(shù)據(jù)
ByteBuffer readBuff=ByteBuffer.allocate(1024);
while (socketChannel2.read(readBuff)>0){
readBuff.flip(); //準備把緩沖區(qū)數(shù)據(jù)輸出
fosChannel.write(readBuff);
readBuff.clear();//準備讀取數(shù)據(jù)到緩沖區(qū)
}
fosChannel.close();
key.interestOps(SelectionKey.OP_WRITE);
} else if (key.isWritable()) {
System.out.println("isWritable");
// 通道可寫
ByteBuffer writeBuff=ByteBuffer.allocate(1024);
writeBuff.put("上傳成功".getBytes());
writeBuff.flip();
SocketChannel socketChannel = (SocketChannel) key.channel();
socketChannel.write(writeBuff);
key.interestOps(SelectionKey.OP_READ);
}
it.remove();
}
}
} catch (Exception e) {
//e.printStackTrace();
} finally {
}
}
}1024首播|39歲程序員逆襲記:不被年齡定義,AI浪潮里再迎春天
2025-10-241024程序員節(jié)丨10年同行,致敬用代碼改變世界的你
2025-10-24【AI設(shè)計】北京143期畢業(yè)僅36天,全員拿下高薪offer!黑馬AI設(shè)計連續(xù)6期100%高薪就業(yè)
2025-09-19【跨境電商運營】深圳跨境電商運營畢業(yè)22個工作日,就業(yè)率91%+,最高薪資達13500元
2025-09-19【AI運維】鄭州運維1期就業(yè)班,畢業(yè)14個工作日,班級93%同學(xué)已拿到Offer, 一線均薪資 1W+
2025-09-19【AI鴻蒙開發(fā)】上海校區(qū)AI鴻蒙開發(fā)4期5期,距離畢業(yè)21天,就業(yè)率91%,平均薪資14046元
2025-09-19