NIO技術-1-緩存區

1. I/O對程序的吞吐率有着決定性的因素。java

2. Java中舊的IO接口,對文件操做只能一個字節一個字節或一行一行的讀,對Socket IO會阻塞,能夠爲每個Socket建立一個Thread,可是這樣的系統開銷和資源浪費都太大,不是合理選擇;而NIO對Socket IO能夠實現非阻塞,能夠用單線程管理多個通道,而且NIO有了緩衝區的概念,不論是File IO仍是Socket IO都是在和Buffer相互讀取。程序員

如圖:數組

因此NIO能夠先將通道數據讀到緩衝區中再進行操做,避免了逐字節或逐行讀取的性能開銷。網絡

NIO有三個核心模塊:Selector(選擇器)、Channel(通道)、Buffer(緩衝區),另外java.nio.charsets包下新增的字符集類也是nio一個重要的模塊,但我的以爲不算是NIO的核心,只是一個供NIO核心類使用的工具類。app

a. Selector容許單個線程處理多個Channel,相比舊的IO爲防止阻塞而爲每個Channel建立一個Thread來講,性能高出很是多;工具

只用SelectableChannel才能註冊到Selector,單線程處理多個Channel.性能

將Channel註冊到Selector中,輪詢調用select(),這個方法會阻塞,當註冊的某個通道準備好要進行IO操做時,這個便返回已選擇鍵的個數,此時經過selectedKeys得到已選擇的鍵,就能夠進行相關的IO操做了;選擇鍵(SelectionKey)是用來鏈接Selector和Channel.大數據

b.Channel主要有:FileChannel、ServerSocketChannel、SocketChannel、DatagramChannel,這四種通道涵蓋了文件IO、TCP套接字IO、UDP數據報IO。this

c.Buffer主要有:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer,涵蓋了IO操做的基本數據類型。編碼

3. 緩衝區(Buffer)

概念:緩衝區(Buffer)就是在內存中預留指定字節數的存儲空間用來對輸入/輸出(I/O)的數據做臨時存儲,這部分預留的內存空間就叫作緩衝區。

在Java NIO中,緩衝區的做用也是用來臨時存儲數據,能夠理解爲是I/O操做中數據的中轉站。

緩衝區直接爲通道(Channel)服務,寫入數據到通道或從通道讀取數據,這樣的操利用緩衝區數據來傳遞就能夠達到對數據高效處理的目的。

NIO中主要有八種緩衝區類(其中MappedByteBuffer是專門用於內存映射的一種ByteBuffer):

緩衝區是包在一個對象內的基礎數據的數組,Buffer類相比通常簡單數組而言其優勢是將數據的內容和相關信息放在一個對象裏面,這個對象提供了處理緩衝區數據的豐富的API。

全部緩衝區都有4個屬性:capacity、limit、position、mark,並遵循:capacity>=limit>=position>=mark>=0,下表格是對着4個屬性的解釋:

屬性 描述
Capacity 容量,便可以容納的最大數據量;在緩衝區建立時被設定而且不能改變
Limit 上界,緩衝區中當前數據量
Position 位置,下一個要被讀或寫的元素的索引
Mark 標記,調用mark()來設置mark=position,再調用reset()可讓position恢復到標記的位置即position=mark

 

 

 

 

 

1、建立緩衝區

全部的緩衝區類都不能直接使用new關鍵字實例化,它們都是抽象類,可是它們都有一個用於建立相應實例的靜態工廠方法,以ByteBuffer類爲例子:

//建立一個容量爲10的byte緩衝區  
ByteBuffer buff = ByteBuffer.allocate(10);

上面代碼將會從堆空間中分配一個容量大小爲10的byte數組做爲緩衝區的byte數據存儲器。對於其餘緩衝區類上面方式也適用,如建立容量爲10的CharBuffer:

//建立一個容量爲10的char緩衝區  
CharBuffer buff = CharBuffer.allocate(10);

若是想用一個指定大小的數組做爲緩衝區的數據的存儲器,可使用wrap()方法:

//使用一個指定數組做爲緩衝區的存儲器  
byte[] bytes = new byte[10];  
ByteBuffer buff = ByteBuffer.wrap(bytes);

上面代碼中緩衝區的數據會存放在bytes數組中,bytes數組或buff緩衝區任何一方中數據的改動都會影響另外一方。還能夠建立指定初始位置(position)和上界(limit)的緩衝區:

//使用一個指定數組做爲緩衝區的存儲器  
//並建立一個position=3,limit=8,capacity=10的緩衝區  
byte[] bytes = new byte[10];  
ByteBuffer buff = ByteBuffer.wrap(bytes, 3, 8);

下圖是新建立的一個容量爲10的字節緩衝區的內存圖:

位置爲0,上屆和容量都爲10,初始標記爲-1,4個屬性中,capacity固定不變,mark limit position會在對緩衝區的操做中變化。

2、操做緩衝區

一、存取(Buffer.get() & Buffer.put())

使用get()從緩衝區中取數據,使用put()向緩衝區中存數據。

// 建立一個容量爲10的byte數據緩衝區
ByteBuffer buff = ByteBuffer.allocate(10);
// 存入4次數據
buff.put((byte) 'A');	
buff.put((byte) 'B');
buff.put((byte) 'C');	
buff.put((byte) 'D');
// 翻轉緩衝區
buff.flip();
// 讀取2次數據
System.out.println((char)buff.get());
System.out.println((char)buff.get());

上面有提過緩衝區四個屬性值必定遵循capacity>=limit>=position>=mark>=0,put()時,若position超過limit則會拋出BufferOverflowException;get()時,若position超過limit則會拋出BufferUnderflowException。

buff.flip()是將緩衝區翻轉,翻轉將在下面來講。

調用put()或get()時,每調用一次position的值會加1,指示下次存或取開始的位置;

上面代碼put()四次後的緩衝區內存示意圖:

上面代碼執行buff.flip()將緩衝區翻轉後的內存示意圖:

上面代碼執兩次get()後的緩衝區內存示意圖:

再向Buffer中讀寫數據時有2個方法也很是有用:

Buffer.remaining():返回從當前位置到上界的數據元素數量;

Buffer.hasRemaining():告訴咱們從當前位置到上界是否有數據元素;

二、翻轉(Buffer.flip())

翻轉就是將一個處於存數據狀態的緩衝區變爲一個處於準備取數據的狀態,使用flip()方式實現翻轉。Buffer.flip()的源碼以下:

public final Buffer flip() {
    limit = position;
	position = 0;
	mark = -1;
	return this;
}

相信看到了實現的源碼應該就會清楚flip()的做用了。rewind()方法與flip()很類似,區別在於rewind()不會影響limit,而flip()會重設limit屬性值,Buffer.rewind()的源碼以下:

public final Buffer rewind() {
	position = 0;
	mark = -1;
	return this;
}

三、壓縮(Buffer.compact())

壓縮就是將已讀取了的數據丟棄,保留未讀取的數據並將保留的數據從新填充到緩衝區的頂部,而後繼續向緩衝區寫入數據。

// 建立一個容量爲10的byte數據緩衝區
ByteBuffer buff = ByteBuffer.allocate(10);
// 填充緩衝區
buff.put((byte)'A');
buff.put((byte)'B');
buff.put((byte)'C');
buff.put((byte)'D');
System.out.println("first put : " + new String(buff.array()));
//翻轉
buff.flip();
//釋放
System.out.println((char)buff.get());
System.out.println((char)buff.get());
//壓縮
buff.compact();
System.out.println("compact after get : " + new String(buff.array()));
//繼續填充
buff.put((byte)'E');
buff.put((byte)'F');
//輸出全部
System.out.println("put after compact : " + new String(buff.array()));

以上代碼打印結果:

first put : ABCD
A
B
compact after get : CDCD
put after compact : CDEF

控制檯中輸出內容中有正方形的亂碼,是正常。由於字節緩衝區中沒有賦值的內存塊默認值是0,而Unicode編碼中沒有0編碼,因此亂碼。

四、標記(Buffer.mark())

標記就是記住當前位置(使用mark()方法標記),以後能夠將位置恢復到標記處(使用reset()方法恢復),mark()和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;
	}

五、比較兩個緩衝區是否相等

比較兩個緩衝區是否相等有2種方法:equals(Object ob) 和compareTo(ByteBuffer that),這兩個方法都是在Buffer的子類中實現的。

equals比較的兩個緩衝區中的每一個值,因此容許不一樣的Buffer對象進行比較;compareTo有類型限制,ByteBuffer只能和ByteBuffer進行比較;比較兩個緩衝區其實是比較兩個緩衝區中每一個緩衝區position到limit之間(不包括limit)的緩衝值。以下圖:

六、批量移動緩衝區的數據

緩衝區的目的就是高效傳輸數據,高效傳輸數據就應杜絕一個一個的傳輸,因此Buffer API提供了相應的方法來進行批量移動。下面是個例子:

byte[] bytes = "hello world!".getBytes();
// 建立一個容量等bytes容量的byte數據緩衝區
ByteBuffer buff = ByteBuffer.allocate(bytes.length);
//將byte數據寫入緩衝區,下面代碼和buff.put(bytes)效果一致
buff.put(bytes, 0, bytes.length);
//翻轉緩衝區
buff.flip();
//輪詢判斷是否有數據,有則將緩衝區的數據批量讀到array中
byte[] array = 	new byte[bytes.length];
while(buff.hasRemaining()){
	buff.get(array, 0, buff.remaining());
}
//輸出衝緩衝區讀出來的數據
System.out.println(new String(array));

以上面代碼爲例,

寫數據到緩衝區時,若bytes.length > buff.capacity()則會拋出java.nio.BufferOverflowException;

從緩衝區中讀數據時,若array.length < buff.limit()則會拋出java.lang.IndexOutOfBoundsException。

七、複製緩衝區

 複製一個與源緩衝區共享數據的緩衝區,各自管理本身的屬性

asReadOnlyBuffer():複製一個只讀緩衝區

duplicate():複製一個可讀可寫的緩衝區

slice():複製一個從源緩衝position到limit的新緩衝區

Buffer(緩衝區)之進階

NIO緩衝區有八種緩衝區實現類,在文件IO、套接字(Socket)IO都得使用ByteBuffer來操做數據,能夠說NIO中,ByteBuffer是很是核心和經常使用的緩衝區實現類,對ByteBuffer的實現由更深刻的瞭解才能靈活正確的運用。

字節(byte)是操做系統和全部I/O設備使用的基本數據類型,基本數據類型是供咱們程序員來直接操做的,實際是以bit(比特位)存儲在內存;那麼1字節等於多少位呢?相信你們都應該知道等於八位。

1byte等於8bit的背景:在計算機剛問世時,每一個字節能夠是3~12個比特位(bit),在市場的推進和前輩們的實踐下,最終決定使用8位做爲一個字節,由於8位足夠表達英文字符集中任何一個字符,使用8位表明一個字節能夠簡化硬件設計,而且8位剛好能夠容納2個十六進制數字,因此8的倍數能夠提供足夠的組合來存儲有效的數值;IBM在1960年率先推出的IBM360大型機使用的就是8位表示1字節。在這樣的背景下,最終統一使用8比特位表示1字節。

1、字節順序

 Java中有八種基本數據類型,其中除byte和boolean類型外的其餘的基本數據都是由組合在一塊兒的幾個字節組成(byte是一個字節,boolean的值是true或false,而1字節的值範圍是-128~127,boolean不能對應到一個或多個字節上),每一個基本數據類型都是以連續字節順序的形式存儲在內存中,字節順序分爲:大端字節順序和小端字節順序

什麼是字節順序?:

多字節的數值被存儲在內存的方式就是字節順序。數值的最高字節位於低位地址,系統使用的就是大端字節順序;數值的最低字節位於低位地址,系統使用就是小端字節順序。例如一個int類型的數值58700999,其十六進制表示爲0x037fb4c7,內存圖以下:

網絡協議(Internet Protocol,即IP)規定數據必須使用大端字節順序傳輸,可是有硬件使用的是小端字節順序,因此若本地系統是小端字節順序,就須要將多字節數值先轉換成大端字節順序後才能正確傳輸數據。

在Java NIO中使用ByteOrder類來封裝字節順序,使用ByteOrder.nativeOrder()能夠獲取本地系統的字節順序;ByteOrder的toString()將返回BIG_ENDIAN(大端字節順序)或LITTLE_ENDIAN(小端字節順序)。ByteBuffer默認使用的是大端字節順序,和本地系統字節順序無關,其餘全部的Buffer類使用的字節順序都和本地系統的字節順序一致。

NIO中全部的緩衝區類都有order()方法,其返回ByteOrder對象,經過調用ByteOrder的toString()方法即可查看某個Buffer類的字節順序。只有ByteBuffer才能夠自定義設置字節順序,經過調用ByteBuffer的order(ByteOrder bo)方法實現。

2、直接緩衝區

直接緩衝區是不使用JVM堆棧而是經過操做系統來建立內存塊用做緩衝區。因此可使用操做系統底層I/O,其處理數據性能要高於基於JVM堆棧的非直接緩衝區;可是直接緩衝區的建立和銷燬的性能開銷要低於基於JVM堆棧的非直接緩衝區。

NIO的八種緩衝區中,只有ByteBuffer才能建立直接緩衝區(還有MappedByteBuffer?MappedByteBuffer繼承ByteBuffer),經過調用ByteBuffer.allocateDirect(int capacity)來建立直接緩衝區。

全部的緩衝區類均可以經過調用isDirect()方法來判斷是不是直接緩衝區。

3、視圖緩衝區

視圖緩衝區就是新建立其餘的基礎數據類型緩衝區,新緩衝區和源緩衝區共享數據,但各自維護本身的屬性(capacity、limit、position、mark)。

只有ByteBuffer才能建立視圖緩衝區,而且能夠將緩衝區的數據轉換。

視圖緩衝區是基礎緩衝區的一部分,由基礎緩衝區的position和limit限制。建立一個ByteBuffer的CharBuffer視圖,以下圖:

ByteBuffer byteBuff = ByteBuffer.allocate(6);
byteBuff.position(2);
byteBuff.limit(4);
System.out.println();
CharBuffer charBuff = byteBuff.asCharBuffer();

asCharBuffer()
asShortBuffer()
asIntBuffer()
asLongBuffer()
asFloatBuffer()
asDoubleBuffer()

4、使用ByteBuffer提供的API直接存取基礎數據

putInt(int value)
putChar(char value)
putLong(long value)
putDouble(double value)
putShort(short value)
putFloat(float value)
getInt()
ByteBuffer buff = ByteBuffer.allocate(100);
buff.putShort((short)100);
buff.putInt(200);
buff.putLong(300L);
buff.putFloat(400.5f);
buff.putDouble(500.56);
buff.putChar('A');
buff.flip();
buff.getShort();//return 100
buff.getInt();//return 200
buff.getLong();//return 300
buff.getFloat();//return 400.5
buff.getDouble();//return 500.56
buff.getChar();//return 'A'

ByteBuffer存取無符號數值

Java中除了char類型外,其餘基本數據類型並無提供用來處理無符號數值的API,可能有時候須要將無符號的數據轉成數據流或文件,但是ByteBuffer並無提供API來作這樣的處理,咱們能夠本身實現使用ByteBuffer存取無符號數據。

下面是向ByteBuffer對象中存取無符號數值的工具類:

import java.nio.ByteBuffer;  
public class Unsigned {  
    public static short getUnsignedByte(ByteBuffer buff) {  
        return (short) (buff.get() & 0xff);  
    }  
    public static short getUnsignedByte(ByteBuffer buff, int position) {  
        return (short) (buff.get(position) & (short) 0xff);  
    }  
    public static void putUnsignedByte(ByteBuffer buff, int value) {  
        buff.put((byte) (value & 0xff));  
    }  
  
    public static void putUnsignedByte(ByteBuffer buff, int position, int value) {  
        buff.put(position, (byte) (value & 0xff));  
    }  
    // --------------------------------------------  
    public static int getUnsignedShort(ByteBuffer buff) {  
        return buff.getShort() & 0xffff;  
    }  
    public static int getUnsignedShort(ByteBuffer buff, int position) {  
        return buff.getShort(position) & (short) 0xffff;  
    }  
    public static void putUnsignedShort(ByteBuffer buff, int value) {  
        buff.putShort((short) (value & 0xffff));  
    }  
    public static void putUnsignedShort(ByteBuffer buff, int position, int value) {  
        buff.putShort(position, (short) (value & 0xffff));  
    }  
    // --------------------------------------------  
    public static long getUnsignedInt(ByteBuffer buff) {  
        return buff.getInt() & 0xffffffffL;  
    }  
    public static long getUnsignedInt(ByteBuffer buff, int position) {  
        return buff.getInt(position) & 0xffffffffL;  
    }  
    public static void putUnsignedInt(ByteBuffer buff, int value) {  
        buff.putInt((int) (value & 0xffffffffL));  
    }  
    public static void putUnsignedInt(ByteBuffer buff, int position, int value) {  
        buff.putInt(position, (int) (value & 0xffff));  
    }  
}

 

由於Java不提供無符號的byte類型,因此取出來的無符號數值要轉成比它大一級的基本數據類型,getUnsignedByte()返回short類型,getUnsignedShort()返回int類型,getUnsignedInt()返回long類型。沒有比long更大的基本數據類型,因此不能存取long類型的無符號數值(能夠用int替代)。

相關文章
相關標籤/搜索