藉助之前使用FileVisitor<T>接口來刪除文件的一個契機,來開始學習瞭解一下NIO的文件讀取。
NIO中讀寫數據有幾個關鍵的部分:Buffer,Charset,Channels以及Selector。所以我就打算一個一個的來學習瞭解。
一、簡單介紹
Buffer是各種緩衝區類的點擊父類,它包含的子類有:ByteBuffer、CharBuffer、FloatBuffer、IntBuffer等等。
以ByteBuffer爲例,因爲NIO中的文件讀寫是以ByteBuffer爲緩衝區的。下面來看一下它的信息:
public abstract class ByteBuffer
extends Buffer
implements Comparable<ByteBuffer>
ByteBuffer本身是一個抽象類,繼承了Buffer,實現了Comparable<ByteBuffer>接口,也就是說ByteBuffer具有比較功能。由於ByteBuffer是一個抽象類,所以ByteBuffer本身不能被實例化,但又並不是由其子類來進行實例化,而是由靜態的工廠方法來實例化的。
使用靜態工廠方法實例化大體分爲以下幾種:
1、allocateDirect(int capacity)
通過allocateDirect工廠方法來創建一個直接字節緩衝區,
此方法返回的緩衝區通常比非直接緩衝區具有更高的分配和釋放成本。 直接緩衝區的內容可能駐留在正常的垃圾回收堆之外(內存的開銷是不再JVM中的),因此它們對應用程序的內存佔用的影響可能不明顯。 因此,建議直接緩衝區主要用於受基礎系統本機I / O操作影響的大型長壽命緩衝區。 一般來說,最好只在產生程序性能可測量的增益時才分配直接緩衝區。
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
2、allocate(int capacity)
通過allocate靜態工廠方法來創建一個非直接字節緩衝區,此方法返回的緩衝區內存開銷是由JVM負責的。
ByteBuffer buffer = ByteBuffer.allocate(1024);
3、wrap(byte[] array)
將一個字節數組包裝到緩衝區裏,那麼這個緩衝區將是由這個字節數組支持的,對緩衝區的修改,直接導致這個數組被修改,反之也是一樣的。起始位置爲0,緩衝區的容量與限制爲這個數組的長度:array.length。
byte[] array = new byte[1024];
ByteBuffer buffer = ByteBuffer.wrap(array);
4、wrap(byte[] array,int offset,int length)
這與第三種方式一樣,只是設置了偏移量:起始位置爲offset,容量爲length,限制爲offset+length.( length<=array.length-offset )
byte[] array = new byte[1024];
ByteBuffer buffer = ByteBuffer.wrap(array,0,1024);
二、緩衝區的創建
ByteBuffer的聲明以allocate爲例。
先引入幾個概念:
capacity | 此緩衝區的容量,緩衝區能容的最大量,創建時聲明,不可以修改 |
position | 此緩衝區的位置,下一次讀寫標記的索引,每次的讀寫,position值都會改變。如:put()一個字節數據,position+1 |
limit | 此緩衝區的限制,讀寫的一個終點,讀寫內容長度不能超過limt限制,這個限制可修改,但limit<=capacity |
mark |
將此緩衝區的標記設置在其位置上,使用mark():mark=position,再調用reset()可以讓position恢復到標記的位置 |
當使用allocate靜態工廠方法聲明ByteBuffer緩衝區時,經過一下的源碼:
public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
return new HeapByteBuffer(capacity, capacity);
}
HeapByteBuffer(int cap, int lim) { // package-private
super(-1, 0, lim, cap, new byte[cap], 0);
/*
hb = new byte[cap];
offset = 0;
*/
}
ByteBuffer(int mark, int pos, int lim, int cap, // package-private
byte[] hb, int offset){
super(mark, pos, lim, cap);
this.hb = hb;
this.offset = offset;
}
經過上面源碼的一頓操作,形成了下面如圖所示的緩衝對象:
經過上面代碼,是進行了日常聲明對象的一些參數初始化:position = 0,mark = -1,limit = capacity;
三、使用
先直接上一個讀取文件的代碼:
public class BufferDemo {
public static void main(String[] args){
buffer();
}
/**
* 如果不對byteBuffer進行flip或者clear操作,那麼position指定到最後的位置,也就沒有空間進行文件數據讀取,
* 就導致在while循環裏進行死循環
*/
public static void buffer(){
FileChannel fileChannel = null;
try {
ByteBuffer byteBuffer = ByteBuffer.allocate(5); // 通過allocate()工廠方法建立非直接字節緩衝區
// 建立文件的NIO連接通道
fileChannel = FileChannel.open(Paths.get("F:\\Web_project\\io_demo.txt"), StandardOpenOption.READ);
while (true){
// 通過連接通道,讀取文件數據到緩衝區,如果讀到末尾,返回 -1
int length = fileChannel.read(byteBuffer);
if (length == -1)
break;
// 翻轉就是將一個處於存數據狀態的緩衝區變爲一個處於準備取數據的狀態
byteBuffer.flip(); // 翻轉這個緩衝區
System.out.println(Arrays.toString(byteBuffer.array()));
byteBuffer.clear(); // 重置一下緩衝區
}
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
try {
if (fileChannel != null)
fileChannel.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
好吧,使用的話,無非就那麼幾個步驟,通過靜態工廠聲明緩衝對象,將這個對象丟到通道里存儲數據,存儲的數據使用前先翻轉一下這個緩衝區,然後使用,最後需要的話可以clear重置一下。就這幾個步驟,是不是很簡單。至於其他方法,可以在具體使用的時候查看API文檔即可。講一下一個很重要的flip()方法。
flip()這個方法,翻轉這個緩衝區。 該限制設置爲當前位置,然後將該位置設置爲零。 如果標記被定義,則它被丟棄。在通道讀取或放置操作的序列之後,調用此方法來準備一系列通道寫入或相對獲取操作
就是用於翻轉這個緩衝區,將limit移動到position位置,position歸置爲0,這個flip翻轉的目的就是爲了讀緩衝裏面的數據做準備,不翻轉就是個坑。看一下源碼:
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}