??Buffer的意思就是緩沖區(qū)顽馋,它的作用就是在內(nèi)存中預(yù)留出一定空間的內(nèi)存大小尘喝,主要用來(lái)作為臨時(shí)數(shù)據(jù)的存儲(chǔ)压昼,那么這部分內(nèi)存區(qū)域炕倘,我們就稱(chēng)之為緩沖區(qū)钧大,這樣做的好處有倆個(gè):
??1、減少實(shí)際的物理讀寫(xiě)次數(shù)
??2罩旋、緩沖區(qū)在創(chuàng)建時(shí)就被分配內(nèi)存啊央,這塊內(nèi)存區(qū)域一直被重用,可以減少動(dòng)態(tài)分配和回收內(nèi)存的次數(shù)
??比如涨醋,現(xiàn)在有一批貨需要從A地移到B地瓜饥,有100個(gè)貨物,B地雖然有很大的位置給A的貨物放置浴骂,但是不知道A具體需要放多少東西乓土,那只能一次給一個(gè)貨物的位置,那么A拉貨的時(shí)候就很煩了靠闭,因?yàn)锽告訴A你每次只能拉一個(gè)東西過(guò)來(lái)帐我,因?yàn)槲也恢滥阋嗌贃|西坎炼,我位置不能亂給,那么A就苦逼了拦键,要拉100次谣光,而且每次都要B重新給了貨物的位置,那么最終的結(jié)果就是A和B都很煩芬为,那現(xiàn)在有個(gè)方案了萄金,A告訴B,我現(xiàn)在要50個(gè)貨物的位置媚朦,你給我留著氧敢,我肯定能用上,B說(shuō)询张,好啊孙乖,那你拉吧,這下A開(kāi)心了份氧,一次就拉了50個(gè)貨物過(guò)去唯袄,結(jié)果只要拉倆次就把貨物拉完了,A和B都很開(kāi)心蜗帜。
??那上面的例子就是能夠反映出緩沖區(qū)的作用恋拷,預(yù)留貨物的位置就可以稱(chēng)之為緩沖區(qū)。
??那么再來(lái)看Buffer厅缺,Buffer本身是抽象類(lèi)蔬顾,我們看它的子類(lèi)。
它的子類(lèi)有:
ByteBuffer湘捎,CharBuffer诀豁,ShortBuffer,IntBuffer消痛,LongBuffer且叁,F(xiàn)loatBuffer,DoubleBuffer
??那我們選ByteBuffer看,一看秩伞,我擦逞带,還是抽象的,但是不要緊纱新,ByteBuffer里面的一些方法基本上就可以讓我們知道緩沖區(qū)是如何使用的展氓。
那下面說(shuō)說(shuō)關(guān)于它的一些介紹。
Fields
屬性 | 描述 |
---|---|
Capacity | 容量脸爱,即可以容納的最大數(shù)據(jù)量遇汞;在緩沖區(qū)創(chuàng)建時(shí)被設(shè)定并且不能改變 |
Limit | 表示緩沖區(qū)的當(dāng)前終點(diǎn),不能對(duì)緩沖區(qū)超過(guò)極限的位置進(jìn)行讀寫(xiě)操作。且極限是可以修改的 |
Position | 位置空入,下一個(gè)要被讀或?qū)懙脑氐乃饕缢看巫x寫(xiě)緩沖區(qū)數(shù)據(jù)時(shí)都會(huì)改變改值,為下次讀寫(xiě)作準(zhǔn)備 |
Mark | 標(biāo)記歪赢,調(diào)用mark()來(lái)設(shè)置mark=position化戳,再調(diào)用reset()可以讓position恢復(fù)到標(biāo)記的位置 |
實(shí)例化
??ByteBuffer類(lèi)提供了4個(gè)靜態(tài)工廠方法來(lái)獲得ByteBuffer的實(shí)例:
方法 | 描述 |
---|---|
allocate(int capacity) | 從堆空間中分配一個(gè)容量大小為capacity的byte數(shù)組作為緩沖區(qū)的byte數(shù)據(jù)存儲(chǔ)器 |
allocateDirect(int capacity) | 是不使用JVM堆棧而是通過(guò)操作系統(tǒng)來(lái)創(chuàng)建內(nèi)存塊用作緩沖區(qū),它與當(dāng)前操作系統(tǒng)能夠更好的耦合埋凯,因此能進(jìn)一步提高I/O操作速度点楼。但是分配直接緩沖區(qū)的系統(tǒng)開(kāi)銷(xiāo)很大,因此只有在緩沖區(qū)較大并長(zhǎng)期存在白对,或者需要經(jīng)常重用時(shí)掠廓,才使用這種緩沖區(qū) |
wrap(byte[] array) | 這個(gè)緩沖區(qū)的數(shù)據(jù)會(huì)存放在byte數(shù)組中,bytes數(shù)組或buff緩沖區(qū)任何一方中數(shù)據(jù)的改動(dòng)都會(huì)影響另一方甩恼。其實(shí)ByteBuffer底層本來(lái)就有一個(gè)bytes數(shù)組負(fù)責(zé)來(lái)保存buffer緩沖區(qū)中的數(shù)據(jù)蟀瞧,通過(guò)allocate方法系統(tǒng)會(huì)幫你構(gòu)造一個(gè)byte數(shù)組 |
wrap(byte[] array,int offset, int length) | 在上一個(gè)方法的基礎(chǔ)上可以指定偏移量和長(zhǎng)度,這個(gè)offset也就是包裝后byteBuffer的position媳拴,而length呢就是limit-position的大小黄橘,從而我們可以得到limit的位置為length+position(offset) |
Methods
方法 | 描述 |
---|---|
limit(), limit(10)等 | 其中讀取和設(shè)置這4個(gè)屬性的方法的命名和jQuery中的val(),val(10)類(lèi)似,一個(gè)負(fù)責(zé)get屈溉,一個(gè)負(fù)責(zé)set |
reset() | 把position設(shè)置成mark的值,相當(dāng)于之前做過(guò)一個(gè)標(biāo)記抬探,現(xiàn)在要退回到之前標(biāo)記的地方 |
clear() | position = 0;limit = capacity;mark = -1; 有點(diǎn)初始化的味道子巾,但是并不影響底層byte數(shù)組的內(nèi)容 |
flip() | limit = position;position = 0;mark = -1; 翻轉(zhuǎn),也就是讓flip之后的position到limit這塊區(qū)域變成之前的0到position這塊小压,翻轉(zhuǎn)就是將一個(gè)處于存數(shù)據(jù)狀態(tài)的緩沖區(qū)變?yōu)橐粋€(gè)處于準(zhǔn)備取數(shù)據(jù)的狀態(tài) |
rewind() | 把position設(shè)為0线梗,mark設(shè)為-1,不改變limit的值 |
remaining() | return limit - position; 返回limit和position之間相對(duì)位置差 |
hasRemaining() | return position < limit返回是否還有未讀內(nèi)容 |
compact() | 把從position到limit中的內(nèi)容移到0到limit-position的區(qū)域內(nèi)怠益,position和limit的取值也分別變成limit-position仪搔、capacity。如果先將positon設(shè)置到limit蜻牢,再compact烤咧,那么相當(dāng)于clear() |
get() | 相對(duì)讀,從position位置讀取一個(gè)byte抢呆,并將position+1煮嫌,為下次讀寫(xiě)作準(zhǔn)備 |
get(int index) | 絕對(duì)讀,讀取byteBuffer底層的bytes中下標(biāo)為index的byte抱虐,不改變position |
get(byte[] dst, int offset, int length) | 從position位置開(kāi)始相對(duì)讀昌阿,讀length個(gè)byte,并寫(xiě)入dst下標(biāo)從offset到offset+length的區(qū)域 |
put(byte b) | 相對(duì)寫(xiě),向position的位置寫(xiě)入一個(gè)byte懦冰,并將postion+1灶轰,為下次讀寫(xiě)作準(zhǔn)備 |
put(int index, byte b) | 絕對(duì)寫(xiě),向byteBuffer底層的bytes中下標(biāo)為index的位置插入byte b刷钢,不改變position |
put(ByteBuffer src) | 用相對(duì)寫(xiě)笋颤,把src中可讀的部分(也就是position到limit)寫(xiě)入此byteBuffer |
put(byte[] src, int offset, int length) | 從src數(shù)組中的offset到offset+length區(qū)域讀取數(shù)據(jù)并使用相對(duì)寫(xiě)寫(xiě)入此byteBuffer |
??那說(shuō)了這么多方法的使用后,我們可以去看一看這個(gè)預(yù)留內(nèi)存到底是怎么安排的闯捎,查看byteBuffer椰弊,我們可以發(fā)現(xiàn)有一個(gè)變量hb,是final byte[] hb,其余的變量都是其余基本數(shù)據(jù)類(lèi)型瓤鼻,應(yīng)該不是我們想要的秉版,那么圍繞這個(gè)查找,在put方法里面我們可以查看這樣一段代碼
if (this.hb != null && src.hb != null) {
System.arraycopy(src.hb, src.position() + src.offset, hb, position() + offset, n);
} else {
final Object srcObject = src.isDirect() ? src : src.hb;
int srcOffset = src.position();
if (!src.isDirect()) {
srcOffset += src.offset;
}
final ByteBuffer dst = this;
final Object dstObject = dst.isDirect() ? dst : dst.hb;
int dstOffset = dst.position();
if (!dst.isDirect()) {
dstOffset += dst.offset;
}
Memory.memmove(dstObject, dstOffset, srcObject, srcOffset, n);
}
??在這里我們可以看到會(huì)對(duì)hb是否為空進(jìn)行判斷茬祷,如果不為空?qǐng)?zhí)行
System.arraycopy(src.hb, src.position() + src.offset, hb, position() + offset, n);
??如果為空清焕,則執(zhí)行下面長(zhǎng)長(zhǎng)的代碼,一般的代碼不必多說(shuō)祭犯,看這一行代碼
Memory.memmove(dstObject, dstOffset, srcObject, srcOffset, n);
??整個(gè)代碼片段里面我們可以從這倆個(gè)方法秸妥,但是其實(shí)這倆個(gè)方法都是底層實(shí)現(xiàn)的,那這里我們解釋下具體作用是什么沃粗。
System.arraycopy()
??首先說(shuō)個(gè)知識(shí)前提粥惧,Java數(shù)組的復(fù)制操作可以分為深度復(fù)制和淺度復(fù)制,簡(jiǎn)單來(lái)說(shuō)深度復(fù)制最盅,可以將對(duì)象的值和對(duì)象的內(nèi)容復(fù)制;淺復(fù)制是指對(duì)對(duì)象引用的復(fù)制突雪。
System.arraycopy的函數(shù)原型是
public static void arraycopy(Object src,int srcPos,Object dest,int destPos,int length)
??其中,src表示源數(shù)組涡贱,srcPos表示源數(shù)組要復(fù)制的起始位置咏删,desc表示目標(biāo)數(shù)組,length表示要復(fù)制的長(zhǎng)度问词,那么對(duì)于一維數(shù)組來(lái)說(shuō)督函,這種復(fù)制屬性值傳遞,修改副本不會(huì)影響原來(lái)的值激挪。對(duì)于二維或者一維數(shù)組中存放的是對(duì)象時(shí)辰狡,復(fù)制結(jié)果是一維的引用變量傳遞給副本的一維數(shù)組,修改副本時(shí)灌灾,會(huì)影響原來(lái)的數(shù)組搓译。這個(gè)各位看官可以自己去測(cè)試一下,這里就不再舉例了锋喜。
Memory.memmove()
??memcpy和memmove()都是C語(yǔ)言中的庫(kù)函數(shù)些己,在頭文件string.h中豌鸡,作用是拷貝一定長(zhǎng)度的內(nèi)存的內(nèi)容,原型分別如下
void *memcpy(void *dst, const void *src, size_t count);
void *memmove(void *dst, const void *src, size_t count);
??他們的作用是一樣的段标,唯一的區(qū)別是涯冠,當(dāng)內(nèi)存發(fā)生局部重疊的時(shí)候,memmove保證拷貝的結(jié)果是正確的逼庞,memcpy不保證拷貝的結(jié)果的正確蛇更。
我們看看這倆個(gè)方法的具體實(shí)現(xiàn)
void* my_memcpy(void* dst, const void* src, size_t n)
{
char *tmp = (char*)dst;
char *s_src = (char*)src;
while(n--) {
*tmp++ = *s_src++;
}
return dst;
}
??從實(shí)現(xiàn)中可以看出memcpy()是從內(nèi)存左側(cè)一個(gè)字節(jié)一個(gè)字節(jié)地將src中的內(nèi)容拷貝到dest的內(nèi)存中
再看看另一個(gè)方法
void* my_memmove(void* dst, const void* src, size_t n)
{
char* s_dst;
char* s_src;
s_dst = (char*)dst;
s_src = (char*)src;
if(s_dst>s_src && (s_src+n>s_dst)) { //內(nèi)存覆蓋的情形。
s_dst = s_dst+n-1;
s_src = s_src+n-1;
while(n--) {
*s_dst-- = *s_src--;
}
}else {
while(n--) {
*s_dst++ = *s_src++;
}
}
return dst;
}
??那么我們?cè)倏纯磧?nèi)存覆蓋是什么情況
??從內(nèi)存可能覆蓋的情況來(lái)看赛糟,當(dāng)使用memcpy的時(shí)候派任,這種實(shí)現(xiàn)方式導(dǎo)致了對(duì)于圖中第二種內(nèi)存重疊情形下,最后兩個(gè)字節(jié)的拷貝值明顯不是原先的值了璧南,新的值是變成了src的最開(kāi)始的2個(gè)字節(jié)了掌逛,而memmove是可以正常工作的。
??到這里關(guān)于Buffer的介紹就結(jié)束了司倚,最后感謝以下文章的提供者豆混。
??ByteBuffer常用方法詳解
??System.arraycopy()方法詳解
??memmove 和 memcpy的區(qū)別以及處理內(nèi)存重疊問(wèn)題
??喜歡技術(shù)研究的可以加入QQ群一起討論:561176094