與傳統IO的區別
- Java NIO( New IO) 是從Java 1.4版本開始引入的一個新的IO API,可以替代標準的Java IO API。NIO與原來的IO有同樣的作用和目的,但是使用的方式完全不同, NIO支持面向緩衝區的、基於通道的IO操作,雙向的。 NIO將以更加高效的方式進行文件的讀寫操作。
IO | NIO |
---|---|
面向流(Stream Oriented) | 面向緩衝區(Buffer Oriented) |
阻塞IO(Blocking IO) | 非阻塞IO(Non Blocking IO) |
(無) | 選擇器(Selectors) |
- 通道和緩衝區
Java NIO系統的核心在於:通道(Channel)和緩衝區(Buffer)。通道表示打開到 IO 設備(例如:文件、套接字)的連接。若需要使用 NIO 系統,需要獲取用於連接 IO 設備的通道以及用於容納數據的緩衝區。然後操作緩衝區,對數據進行處理。
簡而言之, hannel 負責傳輸, Buffer 負責存儲
- 總體來說NIO有兩大點需要掌握
(1)通道
(2)緩衝區
緩衝區
-
緩衝區(Buffer):在 Java NIO 中負責數據的存取。緩衝區就是數組。用於存儲不同數據類型的數據根據數據類型不同(boolean 除外),提供了相應類型的緩衝區:
ByteBuffer
CharBuffer
ShortBuffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBuffer
上述緩衝區的管理方式幾乎一致,通過 allocate() 獲取緩衝區 -
緩衝區存取數據的兩個核心方法:
put() : 存入數據到緩衝區中
get() : 獲取緩衝區中的數據 -
緩衝區中的四個核心屬性:
capacity : 容量,表示緩衝區中最大存儲數據的容量。一旦聲明不能改變。
limit : 界限,表示緩衝區中可以操作數據的大小。(limit 後數據不能進行讀寫)
position : 位置,表示緩衝區中正在操作數據的位置。
mark : 標記,表示記錄當前 position 的位置。可以通過 reset() 恢復到 mark 的位置
- 0 <= mark <= position <= limit <= capacity
- 直接緩衝區與非直接緩衝區:
非直接緩衝區:通過 allocate() 方法分配緩衝區,將緩衝區建立在 JVM 的內存中
直接緩衝區:通過 allocateDirect() 方法分配直接緩衝區,將緩衝區建立在物理內存中。可以提高效率
import java.nio.ByteBuffer;
import org.junit.Test;
public class TestBuffer {
@Test
public void test3(){
//分配直接緩衝區
ByteBuffer buf = ByteBuffer.allocateDirect(1024);
System.out.println(buf.isDirect());
}
@Test
public void test2(){
String str = "abcde";
ByteBuffer buf = ByteBuffer.allocate(1024);
buf.put(str.getBytes());
buf.flip();
byte[] dst = new byte[buf.limit()];
buf.get(dst, 0, 2);
System.out.println(new String(dst, 0, 2));
System.out.println(buf.position());
//mark() : 標記
buf.mark();
buf.get(dst, 2, 2);
System.out.println(new String(dst, 2, 2));
System.out.println(buf.position());
//reset() : 恢復到 mark 的位置
buf.reset();
System.out.println(buf.position());
//判斷緩衝區中是否還有剩餘數據
if(buf.hasRemaining()){
//獲取緩衝區中可以操作的數量
System.out.println(buf.remaining());
}
}
@Test
public void test1(){
String str = "abcde";
//1. 分配一個指定大小的緩衝區
ByteBuffer buf = ByteBuffer.allocate(1024);
System.out.println("-----------------allocate()----------------");
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//2. 利用 put() 存入數據到緩衝區中
buf.put(str.getBytes());
System.out.println("-----------------put()----------------");
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//3. 切換讀取數據模式
buf.flip();
System.out.println("-----------------flip()----------------");
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//4. 利用 get() 讀取緩衝區中的數據
byte[] dst = new byte[buf.limit()];
buf.get(dst);
System.out.println(new String(dst, 0, dst.length));
System.out.println("-----------------get()----------------");
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//5. rewind() : 可重複讀
buf.rewind();
System.out.println("-----------------rewind()----------------");
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//6. clear() : 清空緩衝區. 但是緩衝區中的數據依然存在,但是處於“被遺忘”狀態
buf.clear();
System.out.println("-----------------clear()----------------");
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
System.out.println((char)buf.get());
}
}
通道Channel
- 通道( Channel):由 java.nio.channels 包定義的。 Channel 表示 IO 源與目標打開的連接。Channel 類似於傳統的“流”。只不過 Channel本身不能直接訪問數據, Channel 只能與Buffer 進行交互。
import org.junit.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Objects;
public class ChannelTest {
//字符集的編碼與解碼
@Test
public void test5(){
//獲取字符集對象
Charset charset = Charset.forName("UTF-8");
//根據字符集對象獲取編碼器對象和解碼器對象
CharsetEncoder charsetEncoder = charset.newEncoder();
CharsetDecoder charsetDecoder = charset.newDecoder();
CharBuffer cb = CharBuffer.allocate(1024);
String str = "nio字符編解碼測試";
cb.put(str);
cb.flip();
try {
ByteBuffer ebuf = charsetEncoder.encode(cb);
//此時注意:get方法會使position指針後移,
for (int i = 0; i < ebuf.limit(); i++) {
System.out.println(ebuf.get());
}
/**而flip不是轉換讀取模式,源碼中會進行如下操作:
* limit = position;
* position = 0;
* mark = -1;
* 如果position沒有位移,直接使用position會導致limit爲0
*/
ebuf.flip();
CharBuffer dbuf = charsetDecoder.decode(ebuf);
System.out.println(dbuf.toString());
} catch (CharacterCodingException e) {
e.printStackTrace();
}
}
//分散於聚集
@Test
public void test4(){
FileChannel inCha = null;
FileChannel outCha = null;
try {
inCha = FileChannel.open(Paths.get("1.jpg"),
StandardOpenOption.READ);
outCha = FileChannel.open(Paths.get("2.jpg"),
StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
ByteBuffer buf1 = ByteBuffer.allocate(100);
ByteBuffer buf2 = ByteBuffer.allocate(1024);
ByteBuffer[] bufs = new ByteBuffer[]{buf1, buf2};
while (inCha.read(bufs) != -1) {
//多個緩衝區同時轉換
for (ByteBuffer buf : bufs) {
buf.flip();
}
outCha.write(bufs);
//寫入完成後pos和limit歸位
for (ByteBuffer buf : bufs) {
buf.clear();
}
}
} catch (Exception e) {
} finally {
try {
inCha.close();
outCha.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//通道間傳輸
@Test
public void test3() {
FileChannel inCha = null;
FileChannel outCha = null;
try {
inCha = FileChannel.open(Paths.get("1.jpg"),
StandardOpenOption.READ);
outCha = FileChannel.open(Paths.get("2.jpg"),
StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
inCha.transferTo(0, inCha.size(), outCha);
} catch (Exception e) {
} finally {
try {
inCha.close();
outCha.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public void test2() {
FileChannel inCha = null;
FileChannel outCha = null;
try {
inCha = FileChannel.open(Paths.get("1.jpg"),
StandardOpenOption.READ);
outCha = FileChannel.open(Paths.get("3.jpg"),
StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
//物理內存映射
MappedByteBuffer inMap = inCha.map(FileChannel.MapMode.READ_ONLY,
0, inCha.size());
MappedByteBuffer outMap = outCha.map(FileChannel.MapMode.READ_WRITE,
0, inCha.size());
byte[] buf = new byte[inMap.limit()];
inMap.get(buf);
outMap.put(buf);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
Objects.requireNonNull(inCha).close();
} catch (IOException e) {
}
try {
Objects.requireNonNull(outCha).close();
} catch (IOException e) {
}
}
}
//利用通道進行文件複製
//非直接緩衝區方式
@Test
public void test1() {
File file1 = new File("1.jpg");
File file2 = new File("2.jpg");
FileOutputStream fos = null;
FileInputStream fis = null;
FileChannel chi = null;
FileChannel cho = null;
try {
//創建流對象
fis = new FileInputStream(file1);
fos = new FileOutputStream(file2);
//獲取通道
chi = fis.getChannel();
cho = fos.getChannel();
//建立緩衝區
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (chi.read(buffer) != -1) {
//切換讀取模式
buffer.flip();
cho.write(buffer);
buffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fos.close();
fis.close();
chi.close();
cho.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
網絡部分
使用 NIO 完成網絡通信的三個核心:
-
通道(Channel):負責連接
java.nio.channels.Channel 接口: |--SelectableChannel |--SocketChannel |--ServerSocketChannel |--DatagramChannel |--Pipe.SinkChannel |--Pipe.SourceChannel
-
緩衝區(Buffer):負責數據的存取
-
選擇器(Selector):是 SelectableChannel 的多路複用器。用於監控 SelectableChannel 的 IO 狀況
基於通道的普通阻塞式網絡通信演示
import org.junit.Test;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class NetSocketTest {
@Test
public void client(){
SocketChannel sc = null;
FileChannel fc = null;
try {
sc = SocketChannel.open(new InetSocketAddress("192.168.0.102",18888));
fc = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
ByteBuffer buf = ByteBuffer.allocate(1024*1024);
while (fc.read(buf)!=-1){
buf.flip();
sc.write(buf);
buf.clear();
}
sc.shutdownOutput();
} catch (IOException e) {
}finally {
try {
fc.close();
sc.close();
} catch (IOException e) {
}
}
}
@Test
public void Server(){
ServerSocketChannel ssc = null;
FileChannel fc = null;
ServerSocketChannel sscPort = null;
try {
ssc = ServerSocketChannel.open();
sscPort = ssc.bind(new InetSocketAddress(18888));
fc = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE,StandardOpenOption.CREATE);
ByteBuffer buf =ByteBuffer.allocate(1024*1024);
//阻塞式方法
SocketChannel accept = sscPort.accept();
while (accept.read(buf) != -1) {
buf.flip();
fc.write(buf);
buf.clear();
}
accept.shutdownInput();
} catch (IOException e) {
}finally {
try {
fc.close();
sscPort.close();
ssc.close();
} catch (IOException e) {
}
}
}
}
基於選擇器的無阻塞式網絡通信
public class NetSocketTest2 {
@Test
public void Client() {
SocketChannel sc = null;
FileChannel file = null;
try {
sc = SocketChannel.open(new InetSocketAddress("192.168.0.103", 18888));
file = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
//切換爲非阻塞式
sc.configureBlocking(false);
ByteBuffer buf = ByteBuffer.allocate(1024 * 1024);
while (file.read(buf) != -1) {
buf.flip();
sc.write(buf);
buf.clear();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
file.close();
sc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public void Server() {
ServerSocketChannel ssc = null;
FileChannel file = null;
try {
//將客戶端的數據存儲到本地
file = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
//獲取服務端通道
ssc = ServerSocketChannel.open();
//設置爲非阻塞式
ssc.configureBlocking(false);
//綁定服務監聽端口號
ssc.bind(new InetSocketAddress(18888));
//獲取選擇器
Selector selector = Selector.open();
//選擇器監控通道,因此
//將通道註冊選擇器上,並設置選擇鍵,選擇鍵是明確選擇器監控的是那種狀態
ssc.register(selector, SelectionKey.OP_ACCEPT);
//迭代選擇器上已經準備好了的事件(OP_ACCEPT)
while (selector.select() > 0) {//如果選擇器上準備就緒的事件大於0
//如果有就緒的事件,就把它們拿出來迭代,
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectionKeys.iterator();
while (it.hasNext()){
SelectionKey selectionKey = it.next();
//判斷具體什麼事件準備就緒,
if (selectionKey.isAcceptable()) {//如果是接收事件準備就緒
//則獲取客戶端的套接字通道
SocketChannel accept = ssc.accept();
//並把套接字通道設置爲非阻塞式
accept.configureBlocking(false);
//得到了客戶端的通道後,也註冊到選擇器上,並監聽他的讀事件是否就緒
//同個下面的代碼,就明確了無論是客戶端還是服務端獲取的通道,
// 都可以放在選擇器中進行監聽狀態
accept.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
//如果當前選擇鍵位讀就緒,就獲取他的套接字通道,
SocketChannel sc = (SocketChannel) selectionKey.channel();
ByteBuffer buf = ByteBuffer.allocate(1024 * 1024);
//開始讀寫數據
while (sc.read(buf) > 0) {
buf.flip();
file.write(buf);
buf.clear();
}
}
//如果不是上面if else 語句中的事件,或已經執行完成了的,就可以清空掉。
it.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
file.close();
ssc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
UDP版無阻塞式
import org.junit.Test;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Iterator;
import java.util.Scanner;
public class UDPNetSocketTest {
public static void main(String[] args) {
new UDPNetSocketTest().Cline();
}
@Test
public void Cline() {
DatagramChannel dc = null;
Scanner sc = null;
try {
dc = DatagramChannel.open();
dc.configureBlocking(false);
ByteBuffer buf = ByteBuffer.allocate(1024);
sc = new Scanner(System.in);
String str = null;
while ((str = sc.next()) != null) {
buf.put(str.getBytes());
buf.flip();
dc.send(buf, new InetSocketAddress("127.0.0.1", 18888));
buf.clear();
}
} catch (IOException e) {
} finally {
if (sc != null) {
sc.close();
}
try {
if (dc != null) {
dc.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public void Server() {
DatagramChannel dc = null;
try {
dc = DatagramChannel.open();
dc.bind(new InetSocketAddress(18888));
dc.configureBlocking(false);
Selector selector = Selector.open();
dc.register(selector, SelectionKey.OP_READ);
while (selector.select() > 0) {
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
if (selectionKey.isReadable()) {
ByteBuffer buf = ByteBuffer.allocate(1024);
dc.receive(buf);
buf.flip();
System.out.println(new String(buf.array(),0,buf.limit()));
buf.clear();
}
//一定要注意清空
iterator.remove();
}
}
} catch (IOException e) {
} finally {
try {
if (dc != null) {
dc.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}