- 緩沖區(qū)(Buffer)
一個用于特定基本數(shù)據(jù)類型的容器,由 java.nio 包定義的各吨,所有緩沖區(qū)都是 Buffer 抽象類的子類。Java NIO 中的 Buffer 主要用于與 NIO 通道進行交互,數(shù)據(jù)是從通道讀入緩沖區(qū)珊泳,從緩沖區(qū)寫入通道中的严沥。
緩沖區(qū)對象本質上是一個數(shù)組猜极,但它其實是一個特殊的數(shù)組,緩沖區(qū)對象內置了一些機制消玄,能夠跟蹤和記錄緩沖區(qū)的狀態(tài)變化情況跟伏,如果我們使用get()方法從緩沖區(qū)獲取數(shù)據(jù)或者使用put()方法把數(shù)據(jù)寫入緩沖區(qū)丢胚,都會引起緩沖區(qū)狀態(tài)的變化。它可以保存多個相同類型的數(shù)據(jù)受扳。根據(jù)數(shù)據(jù)類型不同(boolean 除外) 携龟,有以下 Buffer 常用子類:ByteBuffer、CharBuffer勘高、ShortBuffer骨宠、IntBuffer、LongBuffer相满、FloatBuffer层亿、DoubleBuffer,上述 Buffer 類 他們都采用相似的方法進行管理數(shù)據(jù)立美,只是各自管理的數(shù)據(jù)類型不同而已匿又,都是通過如下方法獲取一個 Buffer對象:
static XxxBuffer allocate(int capacity) : 創(chuàng)建一個容量為 capacity 的 XxxBuffer 對象。
在緩沖區(qū)中建蹄,最重要的屬性有下面三個碌更,它們一起合作完成對緩沖區(qū)內部狀態(tài)的變化跟蹤:
- position:指定了下一個將要被寫入或者讀取的元素索引,它的值由get()/put()方法自動更新洞慎,在新創(chuàng)建一個Buffer對象時痛单,position被初始化為0。
- limit:指定還有多少數(shù)據(jù)需要取出(在從緩沖區(qū)寫入通道時)劲腿,或者還有多少空間可以放入數(shù)據(jù)(在從通道讀入緩沖區(qū)時)旭绒。
- capacity:指定了可以存儲在緩沖區(qū)中的最大數(shù)據(jù)容量,實際上焦人,它指定了底層數(shù)組的大小挥吵,或者至少是指定了準許我們使用的底層數(shù)組的容量。
另外:標記 (mark)與重置 (reset): 標記是一個索引花椭,通過 Buffer 中的 mark() 方法指定 Buffer 中一個特定的 position忽匈,之后可以通過調用 reset() 方法恢復到這個 position。
下一步把讀取的數(shù)據(jù)寫入到輸出通道中该抒,相當于從緩沖區(qū)中讀取數(shù)據(jù)慌洪,在此之前,必須調用flip()方法凑保,該方法將會完成兩件事情:
- 把limit設置為當前的position值 冈爹。
- 把position設置為0。
- 直接與非直接緩沖區(qū)
字節(jié)緩沖區(qū)要么是直接的廓译,要么是非直接的结胀。如果為直接字節(jié)緩沖區(qū),則 Java 虛擬機會盡最大努力直接在此緩沖區(qū)上執(zhí)行本機 I/O 操作责循。也就是說糟港,在每次調用基礎操作系統(tǒng)的一個本機 I/O 操作之前(或之后),虛擬機都會盡量避免將緩沖區(qū)的內容復制到中間緩沖區(qū)中(或從中間緩沖區(qū)中復制內容)院仿。
直接字節(jié)緩沖區(qū)可以通過調用此類的 allocateDirect() 工廠方法來創(chuàng)建秸抚。此方法返回的緩沖區(qū)進行分配和取消分配所需成本通常高于非直接緩沖區(qū)。直接緩沖區(qū)的內容可以駐留在常規(guī)的垃圾回收堆之外歹垫,因此剥汤,它們對應用程序的內存需求量造成的影響可能并不明顯。所以排惨,建議將直接緩沖區(qū)主要分配給那些易受基礎系統(tǒng)的本機 I/O 操作影響的大型吭敢、持久的緩沖區(qū)。一般情況下暮芭,最好僅在直接緩沖區(qū)能在程序性能方面帶來明顯好處時分配它們鹿驼。直接字節(jié)緩沖區(qū)還可以通過 FileChannel 的 map() 方法 將文件區(qū)域直接映射到內存中來創(chuàng)建欲低。該方法返回MappedByteBuffer 。 Java 平臺的實現(xiàn)有助于通過 JNI 從本機代碼創(chuàng)建直接字節(jié)緩沖區(qū)畜晰。如果以上這些緩沖區(qū)中的某個緩沖區(qū)實例指的是不可訪問的內存區(qū)域砾莱,則試圖訪問該區(qū)域不會更改該緩沖區(qū)的內容,并且將會在訪問期間或稍后的某個時間導致拋出不確定的異常凄鼻。
字節(jié)緩沖區(qū)是直接緩沖區(qū)還是非直接緩沖區(qū)可通過調用其 isDirect() 方法來確定腊瑟。提供此方法是為了能夠在性能關鍵型代碼中執(zhí)行顯式緩沖區(qū)管理。
非直接緩沖區(qū)
直接緩沖區(qū)
下面我們看下直接緩沖區(qū)的操作樣例和重點:
import org.junit.Test;
import java.nio.ByteBuffer;
public class TestBuffer {
@Test
public void test3() {
//分配直接緩沖區(qū)
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());
//判斷緩沖區(qū)中是否還有剩余數(shù)據(jù)
if (buf.hasRemaining()) {
//獲取緩沖區(qū)中可以操作的數(shù)量
System.out.println(buf.remaining());
}
}
@Test
public void test1() {
String str = "abcde";
//1. 分配一個指定大小的緩沖區(qū)
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() 存入數(shù)據(jù)到緩沖區(qū)中
buf.put(str.getBytes());
System.out.println("-----------------put()----------------");
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//3. 切換讀取數(shù)據(jù)模式
buf.flip();
System.out.println("-----------------flip()----------------");
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//4. 利用 get() 讀取緩沖區(qū)中的數(shù)據(jù)
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() : 清空緩沖區(qū). 但是緩沖區(qū)中的數(shù)據(jù)依然存在块蚌,但是處于“被遺忘”狀態(tài)
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 類似于傳統(tǒng)的“流”峭范。只不過 Channel本身不能直接訪問數(shù)據(jù)河胎, Channel 只能與Buffer 進行交互。下面我們通過幾張圖來引入通道:
Java 為 Channel 接口提供的最主要實現(xiàn)類如下:
- FileChannel:用于讀取、寫入铸抑、映射和操作文件的通道贡耽;
- DatagramChannel:通過 UDP 讀寫網絡中的數(shù)據(jù)通道;
- SocketChannel:通過 TCP 讀寫網絡中的數(shù)據(jù);
- ServerSocketChannel:可以監(jiān)聽新進來的 TCP 連接蒲赂,對每一個新進來的連接都會創(chuàng)建一個 SocketChannel阱冶。
- 獲取通道
獲取通道的一種方式是對支持通道的對象調用getChannel() 方法。支持通道的類如下: FileInputStream凳宙、FileOutputStream、RandomAccessFile
职祷、DatagramSocket氏涩、Socket、ServerSocket有梆,獲取通道的其他方式是使用 Files 類的靜態(tài)方法 newByteChannel() 獲取字節(jié)通道是尖。或者通過通道的靜態(tài)方法 open() 打開并返回指定通道泥耀。 -
通道的數(shù)據(jù)傳輸
將 Buffer 中數(shù)據(jù)寫入 Channel饺汹,例如:
從 Channel 讀取數(shù)據(jù)到 Buffer,例如: - 分散(Scatter)和聚集(Gather)
-
分散讀忍荡摺(Scattering Reads)是指從 Channel 中讀取的數(shù)據(jù)“分散” 到多個 Buffer 中兜辞。
注意:按照緩沖區(qū)的順序,從 Channel 中讀取的數(shù)據(jù)依次將 Buffer 填滿夸溶。
- 聚集寫入(Gathering Writes)是指將多個 Buffer 中的數(shù)據(jù)“聚集”到 Channel逸吵。
注意:按照緩沖區(qū)的順序,寫入 position 和 limit 之間的數(shù)據(jù)到 Channel 缝裁。
transferFrom()扫皱, 將數(shù)據(jù)從源通道傳輸?shù)狡渌?Channel 中:
transferTo(),將數(shù)據(jù)從源通道傳輸?shù)狡渌?Channel 中:
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):用于源節(jié)點與目標節(jié)點的連接韩脑。在 Java NIO 中負責緩沖區(qū)中數(shù)據(jù)的傳輸。Channel 本身不存儲數(shù)據(jù)粹污,因此需要配合緩沖區(qū)進行傳輸段多。
*
* 二、通道的主要實現(xiàn)類
* 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 針對各個通道提供了靜態(tài)方法 open()
* 3. 在 JDK 1.7 中的 NIO.2 的 Files 工具類的 newByteChannel()
*
* 四衩匣、通道之間的數(shù)據(jù)傳輸
* transferFrom()
* transferTo()
*
* 五、分散(Scatter)與聚集(Gather)
* 分散讀戎嗪健(Scattering Reads):將通道中的數(shù)據(jù)分散到多個緩沖區(qū)中
* 聚集寫入(Gathering Writes):將多個緩沖區(qū)中的數(shù)據(jù)聚集到通道中
*
* 六琅捏、字符集:Charset
* 編碼:字符串 -> 字節(jié)數(shù)組
* 解碼:字節(jié)數(shù)組 -> 字符串
*
*/
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. 分配指定大小的緩沖區(qū)
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);
}
//通道之間的數(shù)據(jù)傳輸(直接緩沖區(qū))
@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();
}
//使用直接緩沖區(qū)完成文件的復制(內存映射文件)
@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());
//直接對緩沖區(qū)進行數(shù)據(jù)的讀寫操作
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));
}
//利用通道完成文件的復制(非直接緩沖區(qū))
@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();
//②分配指定大小的緩沖區(qū)
ByteBuffer buf = ByteBuffer.allocate(1024);
//③將通道中的數(shù)據(jù)存入緩沖區(qū)中
while(inChannel.read(buf) != -1){
buf.flip(); //切換讀取數(shù)據(jù)的模式
//④將緩沖區(qū)中的數(shù)據(jù)寫入通道中
outChannel.write(buf);
buf.clear(); //清空緩沖區(qū)
}
} 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));
}
}