Java-NIO(2)
緩衝區
常規I/O操作存在很大缺點,主要是因爲它們是阻塞的,而NIO正是爲了解決常規I/O執行效率低的問題,採用非阻塞高性能運行的方式來避免出現笨拙的同步I/O帶來的效率低的問題
緩衝區Buffer,在NIO的使用中地位很高,因爲數據就是放到緩衝區,對數據進行處理的
NIO中的Buffer是一個用於存儲基本數據類型的容器,它以類似於數組有序的方式來存儲和組織數據,每個基本數據類型(除boolean)都有一個子類相對應
Buffer類
Buffer類及其子類都是抽象類,不能直接實例化,使用方式是將對應的數據類型的數組包裝進入緩衝區,藉助靜態方法wrap實現
API
NIO緩衝區中,有四個重要的參數:
-
capacity-容量
-
limit-限制
-
position-位置
-
mark-標記
四個之間的大小關係
0 <= mark <= position <= limit <= capacity
capacity
代碼包含元素的數量。值不能爲負數,且不可更改
方法:
/**
* Returns this buffer's capacity.
*
* @return The capacity of this buffer
*/
public final int capacity() {
return capacity;
}
public class T1 {
public static void main(String[] args) {
byte[] bytes = new byte[]{1,2,3};
short[] shorts = new short[]{1,2,3,4};
int[] ints = new int[]{1,2,3,4,5};
long[] longs = new long[]{1,2,3,4,5,6};
float[] floats = new float[]{1,2,3,4,5,6,7};
double[] doubles = new double[]{1,2,3,4,5,6,7,8};
char[] chars = new char[]{'a','b','c','d','e'};
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
ShortBuffer shortBuffer = ShortBuffer.wrap(shorts);
IntBuffer intBuffer = IntBuffer.wrap(ints);
LongBuffer longBuffer = LongBuffer.wrap(longs);
FloatBuffer floatBuffer = FloatBuffer.wrap(floats);
DoubleBuffer doubleBuffer = DoubleBuffer.wrap(doubles);
CharBuffer charBuffer = CharBuffer.wrap(chars);
print(byteBuffer);
print(shortBuffer);
print(intBuffer);
print(longBuffer);
print(floatBuffer);
print(doubleBuffer);
print(charBuffer);
}
private static void print(Buffer buffer) {
System.out.println("Name : \n");
System.out.println(buffer.getClass().getName() + "\n");
System.out.println("Capacity : \n");
System.out.println(buffer.capacity());
System.out.println("----------------------------------");
}
}
看下ByteBuffer中wrap方法:
//將字節數組包裝到緩衝區中。
//新緩衝區將由給定的字節數組支持; 也就是說,對緩衝區的修改將導致數組被修改,反之亦然。 新緩衝區的容量和限制將爲array.length,其位置爲零,其標記爲未定義,其字節順序爲BIG_ENDIAN。 它的支持數組將是給定的數組,其數組偏移量將爲零。
public static ByteBuffer wrap(byte[] array) {
return wrap(array, 0, array.length);
}
limit
何爲限制?緩衝區的限制代表了第一個不應該讀取或者寫入元素的index(索引),limit不可以爲負,且不可以大於capacity,假設position大於limit,那麼將position設置爲新的limit,假設mark已定義且大於新的limit,則丟棄mark
limit應用圖示:
測試:
public class T2 {
public static void main(String[] args) {
char[] chars = new char[]{'a','b','c','d','e'};
CharBuffer charBuffer = CharBuffer.wrap(chars);
System.out.println("capacity : \n");
System.out.println(charBuffer.capacity());
System.out.println("");
System.out.println("Limit : \n");
System.out.println(charBuffer.limit());
System.out.println("");
charBuffer.limit(3);
System.out.println("設置調整之後 : \n");
System.out.println("capacity : \n");
System.out.println(charBuffer.capacity());
System.out.println("");
System.out.println("Limit : \n");
System.out.println(charBuffer.limit());
System.out.println("");
charBuffer.put(0,'0');
charBuffer.put(1,'1');
charBuffer.put(2,'2');
charBuffer.put(3,'3');//報錯
charBuffer.put(4,'4');
charBuffer.put(5,'5');
charBuffer.put(6,'6');
}
}
Limit使用場景就是當反覆向緩衝區存取數據時使用,如下圖,第一次向緩衝區存儲9個數據,然後讀取全部9個數據,完成之後再進行第二次向緩衝區存儲數據,第二次只存4個數據:
當讀取會出現問題,如果讀取全部1 2 3 4 E F G H I是錯誤的,所以要結合limit限制讀取的範圍,在E設置Limit,實現讀取1 2 3 4這4個正確的數據
position
何爲位置?它代表下一個要讀取或寫入元素的index,不能爲負,且不能大於limit
圖示:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ZShlpodq-1586246156751)(https://upload-images.jianshu.io/upload_images/5653258-449c865aa0d5838e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
position對應index爲3,說明從此位置開始寫入或者讀取,直到limit結束
public class T3 {
public static void main(String[] args) {
char[] chars = new char[]{'a','b','c','d','e'};
CharBuffer charBuffer = CharBuffer.wrap(chars);
print(charBuffer);
System.out.println("-----------");
charBuffer.position(2);
print(charBuffer);
System.out.println("-----------");
charBuffer.put('1');
for (int i = 0; i < chars.length; i++) {
System.out.println(chars[i] + " ");
}
}
private static void print(Buffer buffer) {
System.out.println("Capacity : " + buffer.capacity()
+" " + "limit : " + buffer.limit() + " "
+ "position : " + buffer.position());
}
}
remaining
方法remaining作用:返回當前位置與limit之間的元素個數
圖示:
源碼:
/**
* Returns the number of elements between the current position and the
* limit.
*
* @return The number of elements remaining in this buffer
*/
public final int remaining() {
return limit - position;
}
public class T4 {
public static void main(String[] args) {
char[] chars = new char[]{'a','b','c','d','e'};
CharBuffer charBuffer = CharBuffer.wrap(chars);
print(charBuffer);
System.out.println("-----------");
charBuffer.position(2);
print(charBuffer);
System.out.println("-----------");
System.out.println("remaining " + charBuffer.remaining());
}
private static void print(Buffer buffer) {
System.out.println("Capacity : " + buffer.capacity()
+" " + "limit : " + buffer.limit() + " "
+ "position : " + buffer.position());
}
}
mark()
緩衝區位置設置標記
標記作用:緩衝區的標記是一個索引,在調用reset方法時,會將緩衝區的position位置重置爲該索引
緩衝區的mark類似於爬山時在關鍵路口設置路標,爲了在原路返回時找到回去的路
public class T6 {
public static void main(String[] args) {
char[] chars = new char[]{'a','b','c','d','e'};
CharBuffer charBuffer = CharBuffer.wrap(chars);
System.out.println("capacity " + charBuffer.capacity());
charBuffer.position(1);
charBuffer.mark(); // 位置1設置標記
System.out.println("position " + charBuffer.position());
System.out.println("-----------");
charBuffer.position(2);
System.out.println("position " + charBuffer.position());
charBuffer.reset();
System.out.println("---------");
System.out.println("reset \n");
System.out.println("position " + charBuffer.position());
}
}
判斷只讀
public class T7 {
public static void main(String[] args) {
byte[] bytes = new byte[]{1,2,3};
short[] shorts = new short[]{1,2,3,4};
int[] ints = new int[]{1,2,3,4,5};
long[] longs = new long[]{1,2,3,4,5,6};
float[] floats = new float[]{1,2,3,4,5,6,7};
double[] doubles = new double[]{1,2,3,4,5,6,7,8};
char[] chars = new char[]{'a','b','c','d','e'};
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
ShortBuffer shortBuffer = ShortBuffer.wrap(shorts);
IntBuffer intBuffer = IntBuffer.wrap(ints);
LongBuffer longBuffer = LongBuffer.wrap(longs);
FloatBuffer floatBuffer = FloatBuffer.wrap(floats);
DoubleBuffer doubleBuffer = DoubleBuffer.wrap(doubles);
CharBuffer charBuffer = CharBuffer.wrap(chars);
print(byteBuffer);
print(shortBuffer);
print(intBuffer);
print(longBuffer);
print(floatBuffer);
print(doubleBuffer);
print(charBuffer);
}
private static void print(Buffer buffer) {
System.out.println("is readOnly ? : \n" +
buffer.isReadOnly());
}
}
直接緩衝區
何爲直接與非直接緩衝區?
圖示:
使用非直接緩衝區存取數據,需要將數據暫存在JVM的中間緩衝區,如果有頻繁的數據操作,則會大大降低軟件對於數據的吞吐量,效率低下,所以就使用直接緩衝區來解決這個問題
public class T8 {
public static void main(String[] args) {
ByteBuffer byteBuffer = ByteBuffer.allocate(100);
System.out.println(byteBuffer.isDirect());
ByteBuffer byteBuffer1 = ByteBuffer.allocateDirect(100);
System.out.println(byteBuffer1.isDirect());
}
}
還原緩衝區狀態
final Buffer clear():
還原緩衝區到初始狀態:
-
位置設爲0
-
限制設爲容量
-
丟棄標記
源碼實現:
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
此方法使用場景就是在對緩存區存儲數據之前調用此方法,如:
buf.clear();
in.read(buf);
但clear方法不是真正的清除緩存區中的數據,它的清除是通過將position位置歸0,再執行寫入新數據的代碼,將最新數據由索引位置0開始進行覆蓋,新值覆蓋舊值,間接的清除數據
public class T9 {
public static void main(String[] args) {
byte[] bytes = new byte[]{1,2,3,4,5,6,7};
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
byteBuffer.position(2);
byteBuffer.limit(3);
byteBuffer.mark();
byteBuffer.clear();
System.out.println("position: \n" + byteBuffer.position());
System.out.println("");
System.out.println("limit : \n" + byteBuffer.limit());
System.out.println("");
}
}
緩衝區反轉
源碼:
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
效果:
-
限制設爲當前位置
-
位置設爲0
-
如果定義了標記就丟棄
當向緩衝區存儲數據,然後再從緩衝區讀取數據時,就是使用flip方法的好地方:
A : buf.allocate(10);
B : buf.put(8);
C : 先向buf寫入數據
D : buf.flip();
E : 再從buf讀數據
圖示:
public class T2 {
public static void main(String[] args) {
CharBuffer charBuffer = CharBuffer.allocate(20);
print(charBuffer);
charBuffer.put(" I am from China");
System.out.println("-----------------------------");
print(charBuffer);
System.out.println("-----------------------------");
charBuffer.position(0);
print(charBuffer);
System.out.println("-----------------------------");
// 以下循環多輸出了6個空格 爲無效的數據
// 正確的應該是隻打印前面有效的字符 空格不在輸出
for (int i = 0; i < charBuffer.limit(); i++) {
System.out.println(charBuffer.get());
}
/*
上面爲錯誤的讀取數據示例
*/
System.out.println("-----------------------------");
print(charBuffer);
System.out.println("-----------------------------");
// 還原緩衝區狀態
charBuffer.clear();
print(charBuffer);
System.out.println("-----------------------------");
charBuffer.put(" I ams usa");
print(charBuffer);
System.out.println("-----------------------------");
/*
以下操作是自己手動實現了 flip方法的邏輯
*/
charBuffer.limit(charBuffer.position());
charBuffer.position(0);
print(charBuffer);
System.out.println("-----------------------------");
for (int i = 0; i < charBuffer.limit(); i++) {
System.out.println(charBuffer.get());
}
}
private static void print(Buffer buffer){
System.out.println("position : \n"
+ buffer.position()
+"\n"
+"limit : \n"
+buffer.limit());
}
}
判斷是否有底層實現的數組
源碼:
public final boolean hasArray() {
return (hb != null) && !isReadOnly;
}
public class T3 {
public static void main(String[] args) {
ByteBuffer byteBuffer = ByteBuffer.allocate(100);
byteBuffer.put((byte) 1);
byteBuffer.put((byte) 2);
System.out.println(byteBuffer.hasArray());
/* 對直接緩衝區判斷 */
ByteBuffer dircet_byteBuffer = ByteBuffer.allocateDirect(100);
dircet_byteBuffer.put((byte) 1);
dircet_byteBuffer.put((byte) 2);
System.out.println(dircet_byteBuffer.hasArray());
}
}
-
打印true是因爲使用了byte[] hb存儲數據,所以hb[]對象爲非空,結果就是true
-
打印false是因爲代表byte[] hb數組爲null,數據直接存在內存了
判斷當前位置與限制之間是否還有剩餘元素
源碼:
public final boolean hasRemaining() {
return position < limit;
}
以及返回當前位置與限制之間的元素個數:
public final int remaining() {
return limit - position;
}
public static void main(String[] args) {
byte[] bytes = new byte[]{1,2,3,4,5};
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
byteBuffer.limit(3);
byteBuffer.position(2);
System.out.println("hasRemainging : \n"
+byteBuffer.hasRemaining()
+"Remainging : \n"
+byteBuffer.remaining());
}
重繞緩衝區
源碼實現:
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
在一系列通道重新寫入或獲取的操作之前調用此方法:
out.write(buf); // Write remaining data
buf.rewind(); // Rewind buffer
buf.get(array); // Copy data into array</pre></blockquote>
rewind():標記清除,位置position歸0,limit不變
rewind經常在重新讀取緩衝區數據時使用
而clear()方法主要使用場景是在對緩衝區進行存儲數據之前調用
flip()是縮小limit範圍
三者側重點不同:
-
rewind:側重於在“重新”,在重新讀取,重新寫入時使用
-
clear():側重於還原一切狀態
-
flip():側重於substring截取
public class T5 {
public static void main(String[] args) {
byte[] bytes = new byte[]{1,2,3,4,5,6,7,8};
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
print(byteBuffer);
System.out.println("");
byteBuffer.position(1);
byteBuffer.limit(3);
byteBuffer.mark();
print(byteBuffer);
System.out.println("");
byteBuffer.rewind();
print(byteBuffer);
byteBuffer.reset();
}
private static void print(Buffer buffer){
System.out.println("capacity : \n"
+ buffer.capacity()
+"\n"
+"limit : \n"
+buffer.limit()
+"\n"
+"position : \n"
+ buffer.position());
}
}
List.toArray(T[])轉出數組類型
public class T6 {
public static void main(String[] args) {
ByteBuffer b1 = ByteBuffer.wrap(new byte[]{'1','2','3'});
ByteBuffer b2 = ByteBuffer.wrap(new byte[]{'1','2','3'});
ByteBuffer b3 = ByteBuffer.wrap(new byte[]{'1','2','3'});
ByteBuffer b4 = ByteBuffer.wrap(new byte[]{'1','2','3'});
ByteBuffer b5 = ByteBuffer.wrap(new byte[]{'1','2','3'});
List<ByteBuffer> list = new ArrayList<>();
list.add(b1);
list.add(b2);
list.add(b3);
list.add(b4);
list.add(b5);
ByteBuffer[] byteBuffers = new ByteBuffer[list.size()];
list.toArray(byteBuffers);
System.out.println("--------------------------");
System.out.println(byteBuffers.length);
for (int i = 0; i < byteBuffers.length; i++) {
ByteBuffer byteBuffer = byteBuffers[i];
while (byteBuffer.hasRemaining()) {
System.out.println((char)byteBuffer.get());
}
System.out.println("--------------------------");
}
}
}
參考資料
《NIO與Socket編程技術指南》