通道(Channel)
由 java.nio.channels 包定義 的。Channel 表示 IO 源與目標打開的連接。 Channel 類似於傳統的“流”。只不過 Channel 本身不能直接訪問數據,Channel 只能與 Buffer 進行交互。
通道可以用來讀取和寫入數據,通道類似於之前的輸入/輸出流,但是程序不會直接操作通道的,所有的內容都是先讀到或寫入到緩衝區中,再通過緩衝區中取得獲寫入的。
通道與傳統的流操作不同,傳統的流操作是分爲輸入或輸出流的,而通道本身是雙向操作的,即可以完成輸入也可以完成輸出。
Java 爲 Channel 接口提供的最主要實現類如下
- FileChannel:用於讀取、寫入、映射和操作文件的通道。
- DatagramChannel:通過 UDP 讀寫網絡中的數據通道。
- SocketChannel:通過 TCP 讀寫網絡中的數據。
- ServerSocketChannel:可以監聽新進來的 TCP 連接,對每一個新進來 的連接都會創建一個 SocketChannel。
獲取通道
獲取通道的一種方式是對支持通道的對象調用 getChannel() 方法。支持通道的類如下:
- FileInputStream
- FileOutputStream
- RandomAccessFile
- DatagramSocket
- Socket
- ServerSocket
獲取通道的其他方式是使用 Files 類的靜態方法 newByteChannel() 獲 取字節通道。或者通過通道的靜態方法 open() 打開並返回指定通道。
通道的數據傳輸
- 將 Buffer 中數據寫入 Channel 例如:
//將Buffer中數據寫入Channel中
int bytesWritten = inChannel.write(buf);
- 從 Channel 讀取數據到 Buffer 例如:
//從Channel讀取數據到Buffer中
int bytesRead = inChannel.read(buf);
分散(Scatter)和聚集(Gather)
分散讀取(Scattering Reads)是指從 Channel 中讀取的數據“分 散”到多個 Buffer 中。
注意:按照緩衝區的順序,從 Channel 中讀取的數據依次將 Buffer 填滿。
聚集寫入(Gathering Writes)是指將多個 Buffer 中的數據“聚集” 到 Channel。
注意:按照緩衝區的順序,寫入 position 和 limit 之間的數據到 Channel 。
transferFrom()
將數據從源通道傳輸到其他 Channel 中:
transferTo()
將數據從源通道傳輸到其他 Channel 中:
Channel接口定義的方法
FileChannel的常用方法
FileChannel是Channel的子類,可以進行文件的讀寫操作。
測試
package com.atguigu.nio;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
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.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.junit.Test;
/*
* 一、通道(Channel):用於源節點與目標節點的連接。在 Java NIO 中負責緩衝區中數據的傳輸。Channel 本身不存儲數據,因此需要配合緩衝區進行傳輸。
*
* 二、通道的主要實現類
* java.nio.channels.Channel 接口:
* |--FileChannel
* |--SocketChannel
* |--ServerSocketChannel
* |--DatagramChannel
*
* 三、獲取通道
* 1. Java 針對支持通道的類提供了 getChannel() 方法
* 本地 IO:
* FileInputStream/FileOutputStream
* RandomAccessFile
*
* 網絡IO:
* Socket
* ServerSocket
* DatagramSocket
*
* 2. 在 JDK 1.7 中的 NIO.2 針對各個通道提供了靜態方法 open()
* 3. 在 JDK 1.7 中的 NIO.2 的 Files 工具類的 newByteChannel()
*
* 四、通道之間的數據傳輸
* transferFrom()
* transferTo()
*
* 五、分散(Scatter)與聚集(Gather)
* 分散讀取(Scattering Reads):將通道中的數據分散到多個緩衝區中
* 聚集寫入(Gathering Writes):將多個緩衝區中的數據聚集到通道中
*
* 六、字符集:Charset
* 編碼:字符串 -> 字節數組
* 解碼:字節數組 -> 字符串
*
*/
public class TestChannel {
//字符集
@Test
public void test6() throws IOException{
Charset cs1 = Charset.forName("GBK");
//獲取編碼器
CharsetEncoder ce = cs1.newEncoder();
//獲取解碼器
CharsetDecoder cd = cs1.newDecoder();
CharBuffer cBuf = CharBuffer.allocate(1024);
cBuf.put("尚硅谷威武!");
cBuf.flip();
//編碼
ByteBuffer bBuf = ce.encode(cBuf);
for (int i = 0; i < 12; i++) {
System.out.println(bBuf.get());
}
//解碼
bBuf.flip();
CharBuffer cBuf2 = cd.decode(bBuf);
System.out.println(cBuf2.toString());
System.out.println("------------------------------------------------------");
Charset cs2 = Charset.forName("GBK");
bBuf.flip();
CharBuffer cBuf3 = cs2.decode(bBuf);
System.out.println(cBuf3.toString());
}
@Test
public void test5(){
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());
}
}
//分散和聚集
@Test
public void test4() throws IOException{
RandomAccessFile raf1 = new RandomAccessFile("1.txt", "rw");
//1. 獲取通道
FileChannel channel1 = raf1.getChannel();
//2. 分配指定大小的緩衝區
ByteBuffer buf1 = ByteBuffer.allocate(100);
ByteBuffer buf2 = ByteBuffer.allocate(1024);
//3. 分散讀取
ByteBuffer[] bufs = {buf1, buf2};
channel1.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()));
//4. 聚集寫入
RandomAccessFile raf2 = new RandomAccessFile("2.txt", "rw");
FileChannel channel2 = raf2.getChannel();
channel2.write(bufs);
}
//通道之間的數據傳輸(直接緩衝區)
@Test
public void test3() throws IOException{
FileChannel inChannel = FileChannel.open(Paths.get("d:/1.mkv"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("d:/2.mkv"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
// inChannel.transferTo(0, inChannel.size(), outChannel);
outChannel.transferFrom(inChannel, 0, inChannel.size());
inChannel.close();
outChannel.close();
}
//使用直接緩衝區完成文件的複製(內存映射文件)
@Test
public void test2() throws IOException{//2127-1902-1777
long start = System.currentTimeMillis();
FileChannel inChannel = FileChannel.open(Paths.get("d:/1.mkv"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("d:/2.mkv"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
//內存映射文件
MappedByteBuffer inMappedBuf = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outMappedBuf = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
//直接對緩衝區進行數據的讀寫操作
byte[] dst = new byte[inMappedBuf.limit()];
inMappedBuf.get(dst);
outMappedBuf.put(dst);
inChannel.close();
outChannel.close();
long end = System.currentTimeMillis();
System.out.println("耗費時間爲:" + (end - start));
}
//利用通道完成文件的複製(非直接緩衝區)
@Test
public void test1(){//10874-10953
long start = System.currentTimeMillis();
FileInputStream fis = null;
FileOutputStream fos = null;
//①獲取通道
FileChannel inChannel = null;
FileChannel outChannel = null;
try {
fis = new FileInputStream("d:/1.mkv");
fos = new FileOutputStream("d:/2.mkv");
inChannel = fis.getChannel();
outChannel = fos.getChannel();
//②分配指定大小的緩衝區
ByteBuffer buf = ByteBuffer.allocate(1024);
//③將通道中的數據存入緩衝區中
while(inChannel.read(buf) != -1){
buf.flip(); //切換讀取數據的模式
//④將緩衝區中的數據寫入通道中
outChannel.write(buf);
buf.clear(); //清空緩衝區
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(outChannel != null){
try {
outChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(inChannel != null){
try {
inChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
long end = System.currentTimeMillis();
System.out.println("耗費時間爲:" + (end - start));
}
}