直接緩沖區與非直接緩沖區的概念
一、非直接緩沖區
1)創建方式
通過
static ByteBuffer allocate(int capacity)
創建的緩沖區,在JVM中內存中創建,在每次調用基礎操作系統的一個本機IO之前或者之后,虛擬機都會將緩沖區的內容復制到中間緩沖區(或者從中間緩沖區復制內容),緩沖區的內容駐留在JVM內,因此銷毀容易,但是占用JVM內存開銷,處理過程中有復制操作。
2)寫入步驟
1.創建一個臨時的直接ByteBuffer對象。
2.將非直接緩沖區的內容復制到臨時緩沖中。
3.使用臨時緩沖區執行低層次I/O操作。
4.臨時緩沖區對象離開作用域,並最終成為被回收的無用數據。
二、直接緩沖區
1)創建方式
通過
static ByteBuffer allocateDirect(int capacity)
創建的緩沖區,在JVM內存外開辟內存,在每次調用基礎操作系統的一個本機IO之前或者之后,虛擬機都會避免將緩沖區的內容復制到中間緩沖區(或者從中間緩沖區復制內容),緩沖區的內容駐留在物理內存內,會少一次復制過程,如果需要循環使用緩沖區,用直接緩沖區可以很大地提高性能。雖然直接緩沖區使JVM可以進行高效的I/O操作,但它使用的內存是操作系統分配的,繞過了JVM堆棧,建立和銷毀比堆棧上的緩沖區要更大的開銷。
直接緩沖區與非直接緩沖區的區別
- 字節緩沖區要么是直接的,要么是非直接的。如果為直接字節緩沖區,則 Java 虛擬機會盡最大努力直接在此緩沖區上執行本機 I/O 操作。也就是說,在每次調用基礎操作系統的一個本機 I/O 操作之前(或之后),虛擬機都會盡量避免將緩沖區的內容復制到中間緩沖區中(或從中間緩沖區中復制內容)。
- 直接字節緩沖區可以通過調用此類的 allocateDirect() 工廠方法來創建。此方法返回的緩沖區進行分配和取消分配所需成本通常高於非直接緩沖區。直接緩沖區的內容可以駐留在常規的垃圾回收堆之外,因此,它們對應用程序的內存需求量造成的影響可能並不明顯。所以,建議將直接緩沖區主要分配給那些易受基礎系統的本機 I/O 操作影響的大型、持久的緩沖區。一般情況下,最好僅在直接緩沖區能在程序性能方面帶來明顯好處時分配它們。
- 直接字節緩沖區還可以通過 FileChannel 的 map() 方法 將文件區域直接映射到內存中來創建。該方法返回MappedByteBuffer 。 Java 平台的實現有助於通過 JNI 從本機代碼創建直接字節緩沖區。如果以上這些緩沖區中的某個緩沖區實例指的是不可訪問的內存區域,則試圖訪問該區域不會更改該緩沖區的內容,並且將會在訪問期間或稍后的某個時間導致拋出不確定的異常。
- 字節緩沖區是直接緩沖區還是非直接緩沖區可通過調用其 isDirect() 方法來確定。提供此方法是為了能夠在性能關鍵型代碼中執行顯式緩沖區管理。
直接緩沖區與非直接緩沖區區別圖形示意
物理磁盤->內核地址空間->用戶地址空間->應用程序
OS -> JVM
直接緩沖區:內核地址空間和用戶地址空間之間形成了一個物理內存映射文件,減少了之間的copy過程。
這邊可以看下jvm結構里的直接內存:JVM虛擬機(一):java虛擬機的基本結構
代碼示例
1 package com.expgiga.NIO; 2 3 import java.nio.ByteBuffer; 4 5 /** 6 * 一、緩沖區(Buffer):在Java NIO中負責數據的存取,緩沖區就是數組,用於存儲不同數據類型的數據。 7 * 根據數據類型不同(boolean除外),提供了相應類型的緩沖區 8 * 9 * ByteBuffer 10 * CharBuffer 11 * ShortBuffer 12 * IntBuffer 13 * LongBuffer 14 * FloatBuffer 15 * DoubleBuffer 16 * 17 * 這些緩沖區的管理方式幾乎一致,通過allocate()獲取緩沖區。 18 * 19 * 二、緩沖區存取數據的兩個核心的方法: 20 * put() 存入數據到緩沖區 21 * get() 獲取緩沖區的數據 22 * 23 * 三、緩沖區中的四個核心屬性: 24 * capacity:容量,表示緩沖區中最大的存儲數據的容量,一旦聲明不能改變 25 * limit:界限,表示緩沖區中可以操作數據的大小。(limit后數據不能進行讀寫) 26 * position:位置,表示緩沖區中正在操作數據的位置。 27 * 0 <= mark <= position <= limit <= capacity 28 * mark:標記,表示記錄當前position的位置,通過reset()恢復到mark的位置 29 * 30 * 四、直接緩沖區和非直接緩沖區 31 * 非直接緩沖區:通過allocate()分配緩沖區,將緩沖區建立在JVM的內存中 32 * 直接緩沖區:通過allocateDirect()分配直接緩沖區,將緩沖區建立在物理內存中,可以提高效率。 33 */ 34 public class TestBuffer { 35 36 public static void main(String[] args) { 37 38 String str = "abcde"; 39 40 //1.分配一個指定大小的緩沖區 41 ByteBuffer buf = ByteBuffer.allocate(1024); 42 43 System.out.println("-----------allocate()----------"); 44 System.out.println(buf.position()); 45 System.out.println(buf.limit()); 46 System.out.println(buf.capacity());//0 1024 1024 47 48 //2.利用put()存入數據到緩沖區 49 buf.put(str.getBytes()); 50 System.out.println("-----------put()----------"); 51 System.out.println(buf.position()); 52 System.out.println(buf.limit()); 53 System.out.println(buf.capacity()); //5 1024 1024 54 55 //3.利用flip()切換成讀數據模式 56 buf.flip(); 57 System.out.println("-----------flip()----------"); 58 System.out.println(buf.position()); 59 System.out.println(buf.limit()); 60 System.out.println(buf.capacity()); //0 5 1024 61 62 //4.利用get()讀取緩沖區中的數據 63 byte[] dst = new byte[buf.limit()]; 64 buf.get(dst); 65 System.out.println(new String(dst, 0, dst.length)); 66 System.out.println("-----------get()----------"); 67 System.out.println(buf.position()); 68 System.out.println(buf.limit()); 69 System.out.println(buf.capacity()); //5 5 1024 70 71 //5.rewind()可重復讀數據 72 buf.rewind(); 73 System.out.println("-----------rewind()----------"); 74 System.out.println(buf.position()); 75 System.out.println(buf.limit()); 76 System.out.println(buf.capacity()); //0 5 1024 77 78 //6.清空緩沖區,但是緩沖區里面的數據依然存在,數據存在被遺忘狀態 79 buf.clear(); 80 System.out.println("-----------clear()----------"); 81 System.out.println(buf.position()); 82 System.out.println(buf.limit()); 83 System.out.println(buf.capacity()); //0 1024 1024 84 85 System.out.println((char)buf.get());//a 86 87 88 //-------------------------------------------------------------- 89 String str2 = "abcde"; 90 ByteBuffer buf2 = ByteBuffer.allocate(1024); 91 buf2.put(str2.getBytes()); 92 93 buf2.flip(); 94 95 byte[] dst2 = new byte[buf.limit()]; 96 buf2.get(dst2, 0, 2); 97 98 System.out.println(new String(dst2, 0, 2)); 99 System.out.println(buf2.position());//2 100 101 //mark() 標記 102 buf2.mark(); 103 104 buf2.get(dst2, 2, 2); 105 System.out.println(new String(dst2, 2, 2)); 106 System.out.println(buf2.position());//4 107 108 //reset()恢復到mark的位置 109 buf2.reset(); 110 System.out.println(buf2.position());//2 111 112 //-------------------------------------------------------------- 113 //分配直接緩沖區 114 ByteBuffer buffer = ByteBuffer.allocateDirect(1024); 115 System.out.println(buffer.isDirect()); //判斷是否是直接緩沖區 116 } 117 }