javaNIO 學習筆記(三)
Java NIO Buffer
緩衝區,可以通過channel
將數據寫入緩衝區,也可以從通道中讀取數據到緩衝區
緩衝區本質上是一個內存塊,您可以將數據寫入其中,然後再讀取數據。這個內存塊包裝在一個NIO緩衝區對象中,該對象提供了一組方法,使使用內存塊變得更容易。
使用緩衝區讀取和寫數據一般分爲下面四個步驟:
- 將數據寫入緩衝區
- 調用
flip()
方法 - 從緩衝區讀取數據
- 調用
clear()
或者compact()
當數據寫入數據到緩衝區時,緩衝區會記錄對應數據量的信息(這個需要看緩衝區大小和數據量大小)。
數據讀取後需要使用flip()方法切換緩衝區的讀寫模式(換個角度可以理解爲存取模式)。在讀取模式下,緩可以通過相應的方法將數據從緩衝區中讀取出來。讀取完數據後需要將緩衝區清空。這樣緩衝區就會再次進入寫入模式。(這裏調用的方式就是
clear
和compact
,clear
是清空,compact
則是清理掉已讀取的數據,未讀取的數據則放入緩衝區頭)
使用的例子上一篇學習筆記已經寫了。
我們來看下buffer
的工作模式。buffer
有三個主要屬性
-
capacity(buffer容量):這是
buffer
的大小即容量。在調用buffer.allocate()指定大小即是這個屬性 -
position(讀取模式的位置,在讀寫模式中表現不一樣)
-
limit(讀寫模式中表現也不一樣)
在寫入模式下,position
初始值爲0.當一個字節,沒寫入一個字節數字都會將position
+1.limit
初始值爲capacity
。在讀取模式下,會將limit
位置設置在```position位置放置爲0,此時讀取數據是從
position到limit
。看下clear
和flip
的源碼大概就可以理解了
public final Buffer clear() {
position = 0; //設置當前下標爲0
limit = capacity; //設置寫越界位置與和Buffer容量相同
mark = -1; //取消標記
return this;
}
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
buffer的主要類型如下:
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
- MappedByteBuffer
可以看到,這些緩衝區類型表示不同的數據類型。換句話說,它們允許您將緩衝區中的字節改爲char、short、int、long、float或double。
學習下buffer
的幾個常見方法:
// 可以通過調用Buffer.mark()方法標記緩衝區中的給定位置。然後,您可以通過調用Buffer.reset()方法將位置重置回標記的位置。
public final Buffer mark() {
mark = position;
return this;
}
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
-
equals()和compareTo ()
可以使用equals()和compareTo()比較兩個緩衝區。
equals ()
兩個緩衝區是相等的,如果:
它們是相同類型的(字節、char、int等)。
它們在緩衝區中有相同數量的剩餘字節、字符等。
所有剩餘的字節、字符等都是相等的。
可以看到,equals只比較緩衝區的一部分,而不是其中的每個元素。實際上,它只是比較緩衝區中剩餘的元素。
compareTo ()
方法比較兩個緩衝區的剩餘元素(字節,字符等),用於排序例程。一個緩衝區被認爲比另一個緩衝區“小”,如果:
第一個元素等於另一個緩衝區中對應的元素,小於另一個緩衝區中的元素。
所有的元素都是相等的,但是第一個緩衝區比第二個緩衝區早耗盡元素(它的元素更少)。
Java NIO Scatter / Gather
Java NIO 提供了內置功能 Scatter/Gather。可以將數據從一個通道寫入多個緩衝區。通道也可以從多個緩衝區收集數據。在需要分別處理傳輸數據的各個部分的情況下,Scatter/Gather非常有用。例如,如果消息由消息頭和消息體組成,則可以將消息頭和消息體保存在單獨的緩衝區中。這樣做可以更容易分別使用標題和主體。
package jniolearn;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* @Author: jimmy
* @Date: 2020/6/14 14:50
* @Description:
*/
public class NioScatter {
public static void main(String[] args) throws IOException {
// 創建一個rw模式的隨機文件
RandomAccessFile randomAccessFile =new RandomAccessFile("D:\\nioFile.txt", "rw");
// 獲取fileChinnel
FileChannel fileChannel = randomAccessFile.getChannel();
// 1、分配緩衝區
ByteBuffer header = ByteBuffer.allocate(10);
ByteBuffer body = ByteBuffer.allocate(128);
// 緩衝數組
ByteBuffer[] bufferArray = {header, body};
// 2、將數組讀入到數組
fileChannel.read(bufferArray);
// Buffer切換模式之前,即處於寫模式下,打印Buffer,查看position, limit, capacity屬性
System.out.println(header.toString());
System.out.println(body.toString());
// 3、切換模式
header.flip();
body.flip();
// 4、獲取數組
System.out.println("header:");
while (header.hasRemaining()) {
System.out.print((char) header.get());
}
System.out.println("\nbody:");
while (body.hasRemaining()) {
System.out.print((char) body.get());
}
header.clear();
body.clear();
fileChannel.close();
}
}
// 返回結果
java.nio.HeapByteBuffer[pos=10 lim=10 cap=10]
java.nio.HeapByteBuffer[pos=24 lim=128 cap=128]
header:
Hi, I am j
body:
immy;
be happy everyday
read
按照buffer
數組中的順序將從channel中讀取的數據寫入到buffer
,當一個buffer
被寫滿後,channel
緊接着向另一個buffer
中寫。Scattering Reads
在移動下一個buffer
前,必須填滿當前的buffer
,這也意味着它不適用於動態消息。這樣若要是想在消息中使用那麼就必須規定好每一段的字節長度,並且按照這個規定嚴格執行,不能存在偏差。不然就會出現部分信息少讀或者多讀的情況。
package jniolearn;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* @Author: jimmy
* @Date: 2020/6/14 15:07
* @Description:
*/
public class NioGather {
public static void main(String[] args) throws IOException {
RandomAccessFile randomAccessFile =new RandomAccessFile("D:\\gaterFile.txt", "rw");
// 獲取fileChinnel
FileChannel fileChannel = randomAccessFile.getChannel();
// 1、分配緩衝區
ByteBuffer header = ByteBuffer.allocate(32);
ByteBuffer body = ByteBuffer.allocate(128);
// 2、放入數據
header.put("hi, weather ?".getBytes());
header.put("body:raining".getBytes());
// 3、存入buffer數組
ByteBuffer[] bufferArray = {header, body};
System.out.println(header.toString());
System.out.println(body.toString());
header.flip();
body.flip();
fileChannel.write(bufferArray);
fileChannel.close();
}
}
查看文件信息:hi, weather ?body:raining
write
方法同樣是按照buffer
數組的順序,將數據寫入到channel
,注意只有position和limit之間的數據纔會被寫入。因此,如果一個buffer的容量爲128byte,但是僅僅包含14byte的數據,那麼這14byte的數據將被寫入到channel
中。和Scattering Reads
相反,Gathering Writes
能較好的處理動態消息。
Java NIO Channel to Channel Transfers
Java NIO還提供了一種通道間傳輸數據的方法,FileChannel
類有一個transferTo()
和一個transferFrom()
方法
---- transferFrom ----
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
toChannel.transferFrom(fromChannel, position, count);
---- transferTo ----
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
fromChannel.transferTo(position, count, toChannel);
小結下前面幾篇學習的內容:
-
如何使用
buffer
- 需要先分配一個緩衝區(我這裏理解爲初始化一個指定大小的緩衝區)使用
ByteBuffer.allocate()
- 讀則是從
channel read
,寫則是先要將數據放入緩衝區,然偶使用channel write
把數據寫入通道 - 注意
buffer
的模式切換使用flip
方法。 - 另外還有
clear
compact
rewind
等方法
- 需要先分配一個緩衝區(我這裏理解爲初始化一個指定大小的緩衝區)使用
-
buffer
的幾個主要屬性- capacity 緩衝區大小(比如緩衝區長度48,則capacity也是48.但是這裏要注意capacity下表是從0開始,所以這個可以理解爲內存溢出位)
- position 在讀模式的時候初始值爲0,讀數據的時候會逐步增加最大能讀到capacity -1。切換到寫模式的時候position爲0
- limit 讀模式的時候默認爲capacity。寫模式的時候,會將limit設置爲讀模式的position的位置。
-
scatter
和gather
- 可以理解爲分散收集,一個是讀,一個是寫。這個是內置的。就是一個
channel
和多個buffer
的讀寫。
- 可以理解爲分散收集,一個是讀,一個是寫。這個是內置的。就是一個
-
channel
如何獲取- 可以直接使用open方法,fileChannel則還可以通過類
RandomAccessFile
的實例getChannel
- 可以直接使用open方法,fileChannel則還可以通過類