IO 與 NIO 的比較
IO | NIO |
---|---|
面向流(Stream Oriented) | 面向緩衝區(Buffer Oriented) |
阻塞IO(Blocking IO) | 非阻塞IO(Non Blocking IO) |
無 | 選擇器(Selectors) |
通道和緩衝區
- 通道(Channel):負責傳輸(鐵路)
- 緩衝區(Buffer):負責存儲(火車)
緩衝區(buffer)
一、根據數據類型的不同,提供了相應類型的緩衝區(Boolean 除外)
ByteBuffer
CharBuffer
IntBuffer
LongBuffer
ShortBuffer
FloatBuffer
DoubleBuffer
上述緩衝區的管理方式幾乎一致,通過 allocate() 獲取緩衝區
二、緩衝區存取數據的兩個核心方法
- put() : 存入數據到緩衝區
- get() : 獲取緩衝區中的數據
三、緩衝區的四個核心屬性
- capacity:容量,緩衝區中最大存儲數據的容量,一旦聲明不能改變
- limit:界限,緩衝區中可以操作的數據的大小,(limit之後的數據不能進行讀寫)
- position:位置,緩衝區中正在操作的數據的位置
- mark:記錄當前 position 的位置,可以通過 reset(),恢復到 mark 的位置
- 0 <= mark <= position <= limit <= capacity
- position 和 limit 就像兩個指針(或者遊標)在 0 到 capacity 之間移動
四、直接緩衝區,非直接緩衝區
- 非直接緩衝區:通過 allocate() 方法分配緩衝區,將緩衝區建立在 JVM 的內存中
- 直接緩衝區:通過 alocateDir() 方法分配直接緩衝區,將緩衝區建立在物理內存,可以提高效率
- 判斷是否爲直接緩衝區:buffer.isDirect();
五、代碼實踐
void contextLoads() {
// 分配指定大小的緩衝區
System.out.println("============== allocate ================");
ByteBuffer buffer = ByteBuffer.allocate(1024);
System.out.println(buffer.capacity()); // 1024
System.out.println(buffer.position()); // 0
System.out.println(buffer.limit()); // 1024
// 添加數據
System.out.println("============== put ================");
String str = "brave";
buffer.put(str.getBytes());
System.out.println(buffer.capacity()); // 1024
System.out.println(buffer.position()); // 5
System.out.println(buffer.limit()); // 1024
// 切換到讀數據模式
System.out.println("============== flip ================");
buffer.flip();
System.out.println(buffer.capacity()); // 1024
System.out.println(buffer.position()); // 0
System.out.println(buffer.limit()); // 5
// 讀取數據
System.out.println("============== get ================");
byte[] bst = new byte[buffer.limit()];
buffer.get(bst);
System.out.println(new String(bst)); // brave
System.out.println(buffer.capacity()); // 1024
System.out.println(buffer.position()); // 5
System.out.println(buffer.limit()); // 5
// 可重複讀
System.out.println("============== rewind ================");
buffer.rewind();
System.out.println(buffer.capacity()); // 1024
System.out.println(buffer.position()); // 0
System.out.println(buffer.limit()); // 5
// 清空緩衝區(數據依然存在)
System.out.println("============== clear ================");
buffer.clear();
System.out.println(buffer.capacity()); // 1024
System.out.println(buffer.position()); // 0
System.out.println(buffer.limit()); // 1024
System.out.println((char)buffer.get()); // b
}
void bufTest(){
System.out.println("========= mark =========");
ByteBuffer buffer = ByteBuffer.allocate(1024);
String str = "brave";
buffer.put(str.getBytes());
System.out.println(buffer.capacity()); // 1024
System.out.println(buffer.position()); // 5
System.out.println(buffer.limit()); // 1024
buffer.flip();
byte[] bst = new byte[buffer.limit()];
buffer.get(bst,0,2);
System.out.println(new String(bst,0,2)); // br
System.out.println(buffer.capacity()); // 1024
System.out.println(buffer.position()); // 2
System.out.println(buffer.limit()); // 5
// 標記 position = 2
buffer.mark();
buffer.get(bst,2,2);
System.out.println(new String(bst,2,2)); // av
System.out.println(buffer.capacity()); // 1024
System.out.println(buffer.position()); // 4
System.out.println(buffer.limit()); // 5
// 返回到標記點 position = 2
buffer.reset();
System.out.println(buffer.capacity()); // 1024
System.out.println(buffer.position()); // 2
System.out.println(buffer.limit()); // 5
// 緩衝區中是否還有數據
if(buffer.hasRemaining()){
// 返回緩衝區中剩餘的數據個數
System.out.println(buffer.remaining()); // 3
}
}
通道(channel)
一、通道的主要實現類
- java.nio.channels.Channel 接口:
|-- FileChannel(本地文件)
|-- SocketChannel(TCP)
|-- ServerSocketChannel(TCP)
|-- DategramChannel(UDP)
二、獲取通道
- 1、java 針對支持通道的類提供了 getChannel() 方法
- 本地IO:
FileInputStream / FileOutputStream
RandomAccessFile - 網絡IO:
Socket
ServerSocket
DatagramSocket
- 本地IO:
- 2、在 JDK1.7 中的 NIO.2 針對各個通道提供了靜態方法 open()
- 3、在 JDK1.7 中的 NIO.2 的 Files 工具類的 newByteChannel()
三、通道之間的數據傳輸
- transferFrom()
- transferTo()
四、分散(Scatter)與聚集(Gather)
- 分散讀取(Scattering Reads):將通道中的數據分散到多個緩衝區中
- 聚集寫入(Gathering Writes):將多個緩衝區中的數據聚集到通道中
五、字符集:CharSet
- 編碼:字符串 -> 字符數組
- 解碼:字符數組 -> 字符串
使用 NIO 完成本地通信
- 利用通道完成文件複製
@Test
void channelTest() throws Exception{
FileInputStream fis = new FileInputStream("1.jpg");
FileOutputStream fos = new FileOutputStream("2.jpg");
// 獲取通道
FileChannel inChannel = fis.getChannel();
FileChannel outChannel = fos.getChannel();
// 分配指定大小的緩衝區
ByteBuffer buf = ByteBuffer.allocate(1024);
// 將通道中的數據存入緩衝區
while(inChannel.read(buf) != -1){
// 切換到讀數據模式
buf.flip();
// 將緩衝區中的數據寫入通道
outChannel.write(buf);
// 清空通道
buf.clear();
}
outChannel.close();
inChannel.close();
fos.close();
fis.close();
}
- 使用直接緩衝區完成文件的複製(內存映射文件)
@Test
void channelTest() throws Exception{
FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("3.jpg"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE_NEW);
// 內存映射文件
MappedByteBuffer inMappedByteBuffer = inChannel.map(MapMode.READ_ONLY,0, inChannel.size());
MappedByteBuffer outMappedByteBuffer = outChannel.map(MapMode.READ_WRITE,0, inChannel.size());
// 直接對緩衝區進行數據的讀寫操作
byte[] dst = new byte[inMappedByteBuffer.limit()];
inMappedByteBuffer.get(dst);
outMappedByteBuffer.put(dst);
outChannel.close();
inChannel.close();
}
- 通道之間的數據傳輸(直接緩衝區)
@Test
void channelTest() throws Exception{
FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("5.jpg"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE_NEW);
// inChannel.transferTo(0, inChannel.size(),outChannel);
outChannel.transferFrom(inChannel, 0, inChannel.size());
outChannel.close();
inChannel.close();
}
- 分散讀取,聚集寫入
@Test
void channelTest() throws Exception{
RandomAccessFile raf1 = new RandomAccessFile("1.txt", "rw");
// 獲取通道
FileChannel channel = raf1.getChannel();
// 分配指定大小的緩衝區
ByteBuffer buf1 = ByteBuffer.allocate(100);
ByteBuffer buf2 = ByteBuffer.allocate(1024);
// 分散讀取
ByteBuffer[] bufs = {buf1, buf2};
channel.read(bufs);
for(ByteBuffer byteBuffer : bufs){
byteBuffer.flip();
}
System.out.println(new String(bufs[0].array(),0,bufs[0].limit()));
System.out.println("---------------------");
System.out.println(new String(bufs[1].array(),0,bufs[1].limit()));
// 聚集寫入
RandomAccessFile raf2 = new RandomAccessFile("2.txt", "rw");
FileChannel channel2 = raf2.getChannel();
channel2.write(bufs);
raf1.close();
raf2.close();
}
- 字符編碼
// 查看所有支持的編碼
void charset() throws Exception{
Map<String,Charset> map = Charset.availableCharsets();
Set<Entry<String,Charset>> set = map.entrySet();
for(Entry<String,Charset> entry:set){
System.out.println(entry.getKey()+"="+entry.getValue());
}
}
// 編碼器,解碼器
void charsetTest() throws Exception{
Charset cs1 = Charset.forName("GBK");
// 獲取編碼器
CharsetEncoder ce = cs1.newEncoder();
// 獲取解碼器
CharsetDecoder cd = cs1.newDecoder();
CharBuffer cBuf = CharBuffer.allocate(1024);
String str = "心有猛虎細嗅薔薇";
cBuf.put(str);
cBuf.flip();
// 編碼
ByteBuffer bBuf = ce.encode(cBuf);
for(int i= 0;i < str.length() * 2;i++){
System.out.println(bBuf.get());
}
// 解碼
bBuf.flip();
CharBuffer cBuf2 = cd.decode(bBuf);
System.out.println(cBuf2.toString());
}
使用 NIO 完成網絡通信
一、通道(Channel) :負責連接
- java . nio. channels .Channel 接口
- SelectableChannel
- SocketChannel
- ServerSocketChannel
- Datagr amChannel
- Pipe. SinkChannel
- Pipe. SourceChannel
- SelectableChannel
二、緩衝區(Buffer) :負責數據的存取
三、迭擇器(Selector) :
- 是 SelectableChannel 的多路複用器。用於監控 SelectableChannel 的 IO 狀況
四、代碼實踐
- 阻塞式 IO
public class Aserver {
public static void main(String[] args) throws Exception{
// 獲取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
FileChannel outChannel = FileChannel.open(Paths.get("3.txt"),StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW);
// 綁定連接
ssChannel.bind(new InetSocketAddress(9898));
// 獲取客戶端鏈接的通道
SocketChannel sChannel = ssChannel.accept();
// 分配指定大小的緩衝區
ByteBuffer buf = ByteBuffer.allocate(1024);
// 接收客戶端的數據,保存到本地
while(sChannel.read(buf) != -1){
buf.flip();
outChannel.write(buf);
buf.clear();
}
// 發送反饋給客戶端
buf.put("服務端接收數據成功".getBytes());
buf.flip();
sChannel.write(buf);
// 關閉通道
sChannel.close();
outChannel.close();
ssChannel.close();
}
}
public class Aclient {
public static void main(String[] args) throws Exception{
// 獲取通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
FileChannel inChannel = FileChannel.open(Paths.get("1.txt"), StandardOpenOption.READ);
// 分配指定大小的緩衝區
ByteBuffer buf = ByteBuffer.allocate(1024);
// 讀取本地文件,發送到服務器
while(inChannel.read(buf) != -1){
buf.flip();
sChannel.write(buf);
buf.clear();
}
sChannel.shutdownOutput();
// 接收服務端反饋
int len = 0;
while((len = sChannel.read(buf)) != -1){
buf.flip();
System.out.println(new String(buf.array(),0,len));
buf.clear();
}
// 關閉通道
inChannel.close();
sChannel.close();
}
}
- 非阻塞式 IO
public class Aserver {
public static void main(String[] args) throws Exception{
// 獲取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
// 切換成非阻塞模式
ssChannel.configureBlocking(false);
// 綁定連接
ssChannel.bind(new InetSocketAddress(9898));
// 獲取選擇器
Selector selector = Selector.open();
// 將通道註冊到選擇器,指定監聽事件
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
// 輪詢式的獲取選擇器上已經準備就緒的事件
while(selector.select() >0 ){
// 獲取當前選擇器中所有註冊的"選擇鍵(已就緒的監聽事件)"
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while(it.hasNext()){
// 獲取準備就緒的事件
SelectionKey sk = it.next();
// 判斷具體是什麼事件準備就緒
if(sk.isAcceptable()){
// 若“接收就緒”,獲取客戶端連接
SocketChannel sChannel = ssChannel.accept();
// 切換成非阻塞模式
sChannel.configureBlocking(false);
// 將通道註冊到選擇器上
sChannel.register(selector, SelectionKey.OP_READ);
}else if (sk.isReadable()){
// 獲取當前選擇器上“讀就緒”狀態的通道
SocketChannel sChannel = (SocketChannel) sk.channel();
// 讀取數據
ByteBuffer buf = ByteBuffer.allocate(1024);
int len = 0;
while((len = sChannel.read(buf)) >0 ){
buf.flip();
System.out.println(new String(buf.array(),0,len));
buf.clear();
}
}
// 取消 SelectionKey
it.remove();
}
}
// 關閉通道
// ssChannel.close();
}
}
public class Aclient {
public static void main(String[] args) throws Exception{
// 獲取通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
// 切換成非阻塞模式
sChannel.configureBlocking(false);
// 分配指定大小的緩衝區
ByteBuffer buf = ByteBuffer.allocate(1024);
// 發送數據給服務端
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
String str = scanner.next();
buf.put((new Date().toString() + " " + str).getBytes());
buf.flip();
sChannel.write(buf);
buf.clear();
}
// 關閉通道
sChannel.close();
}
}
- 基於 UDP 實現的非阻塞式 IO
public class Areceive {
public static void main(String[] args) throws IOException {
DatagramChannel dc = DatagramChannel.open();
dc.configureBlocking(false);
dc.bind(new InetSocketAddress(9898));
Selector selector = Selector.open();
dc.register(selector, SelectionKey.OP_READ);
while(selector.select() > 0){
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()){
SelectionKey sk = it.next();
if(sk.isReadable()){
ByteBuffer buf = ByteBuffer.allocate(1024);
dc.receive(buf);
buf.flip();
System.out.println(new String(buf.array(),0,buf.limit()));
buf.clear();
}
}
it.remove();
}
}
}
public class Asend {
public static void main(String[] args) throws IOException {
DatagramChannel dc = DatagramChannel.open();
dc.configureBlocking(false);
ByteBuffer buf = ByteBuffer.allocate(1024);
Scanner scan = new Scanner(System.in);
while(scan.hasNext()){
String str = scan.next();
buf.put((new Date().toString() + " "+ str).getBytes());
buf.flip();
dc.send(buf,new InetSocketAddress("127.0.0.1",9898));
buf.clear();
}
dc.close();
}
}
管道
- Java NIO 管道是2個線程之間的單向數據連接。
- Pipe 有一個 source 通道和一個 sink 通道。
- 數據會被寫到 sink 通道,從 source 通道 讀取。
public class Test {
public static void main(String[] args) throws IOException {
// 獲取管道
Pipe pipe = Pipe.open();
// 將緩衝區中的數據寫入管道
ByteBuffer buf = ByteBuffer.allocate(1024);
Pipe.SinkChannel sinkChannel = pipe.sink();
buf.put("通過單向管道發送數據".getBytes());
buf.flip();
sinkChannel.write(buf);
// 讀取緩衝區中的數據
Pipe.SourceChannel sourceChannel = pipe.source();
buf.flip();
int len = sourceChannel.read(buf);
System.out.println(new String(buf.array(),0,len));
sinkChannel.close();
sourceChannel.close();
}
}