JAVA中NIO再深入
在上一章節(jié)的JAVA中的I/O和NIO我們學習了如何使用NIO扶叉,接下來再深入了解一下關于NIO的知識勿锅。
緩沖器內部的細節(jié)
Buffer
由數(shù)據(jù)和可以高效地訪問及操作這些數(shù)據(jù)的四個索引組成。這四個索引是
-
mark
:標記辜梳,就像游戲中設置了一個存檔一樣粱甫,可以調用reset()
方法進行回歸到mark標記的地方泳叠。 -
position
:位置作瞄,其實緩沖器實際上就是一個美化過的數(shù)組,從通道中讀取數(shù)據(jù)就是放到了底層的數(shù)組危纫。所以其實就像索引一樣宗挥。所以positon變量跟蹤已經寫了多少數(shù)據(jù)乌庶。 -
limit
:界限,即表明還有多少數(shù)據(jù)需要取出契耿,或者還有多少空間能夠寫入瞒大。 -
capacity
:容量,表明緩沖器中可以存儲的最大容量搪桂。
在緩沖器中每一個讀寫操作都會改變緩沖器的狀態(tài)透敌,用于反應所發(fā)生的變化。通過記錄和跟蹤這些變化踢械,緩沖器就能夠內部地管理自己的資源酗电。下面是用于設置和復位索引以及查詢其索引值的方法
方法名 | 解釋 |
---|---|
capacity() | 返回緩沖器的容量 |
clear() | 清空緩沖器,將position設置為0内列,limit設置容量撵术。調用此方法復寫緩沖器 |
flip() | 將limit設置為position,position設置為0.此方法用于準備從緩沖區(qū)讀取已經寫入的數(shù)據(jù) |
limit() | 返回limit值 |
limit(int lim) | 設置limit值 |
mark() | 將mark設置為positon |
position() | 返回position的值 |
position(int pos) | 設置postion的值 |
remaining() | 返回limit-position的值 |
接下來我們寫個例子模擬這四個索引的變化情況话瞧,例如有一個字符串BuXueWuShu
嫩与。我們交換相鄰的字符。
private static void symmetricScranble(CharBuffer buffer){
while (buffer.hasRemaining()){
buffer.mark();
char c1 = buffer.get();
char c2 = buffer.get();
buffer.reset();
buffer.put(c2).put(c1);
}
}
public static void main(String[] args) {
char [] data = "BuXueWuShu".toCharArray();
ByteBuffer byteBuffer = ByteBuffer.allocate(data.length*2);
CharBuffer charBuffer = byteBuffer.asCharBuffer();
charBuffer.put(data);
System.out.println(charBuffer.rewind());
symmetricScranble(charBuffer);
System.out.println(charBuffer.rewind());
symmetricScranble(charBuffer);
System.out.println(charBuffer.rewind());
}
rewind()
方法是將position
設為0 交排,mark
設為-1
在剛進入symmetricScranble ()
方法時的各個索引如下圖所示
然后第一次調用了mark()
方法以后就相當于給mark
賦值了划滋,相當于在此設置了一個回檔點。此時索引如下所示
然后每次調用get()
方法Position
索引都會改變埃篓,在第一次調用了兩次get()
方法以后古毛,各個索引如下
然后調用了reset()
方法另Position=Mark
,此時的索引如下
然后每次調用put()
方法也會改變Position
索引的值都许,
注意此時前兩個字符已經互換了位置稻薇。然后在第二輪while開始再次改變了Mark
索引的值,各個索引如下
此時我們應該就知道前面我們說的調用clear()
方法并不會清除緩沖器里面的數(shù)據(jù)的原因了胶征,因為只是將其索引變了而已塞椎。
內存映射文件
內存映射文件不是Java引入的概念,而是操作系統(tǒng)提供的一種功能睛低,大部分操作系統(tǒng)都支持案狠。
內存映射文件允許我們創(chuàng)建和修改那些因為太大而不能放入內存的文件。有了內存映射文件钱雷,我們就可以假定整個文件都放在內存中骂铁,而且可以完全將其視為非常大的數(shù)組進行訪問。所以對于文件的操作就變?yōu)榱藢τ趦却嬷械淖止?jié)數(shù)組的操作罩抗,然后對于字節(jié)數(shù)組的操作會映射到文件中拉庵。這種映射可以映射整個文件,也可以只映射文件中的一部分套蒂。什么時候字節(jié)數(shù)組中的的操作會映射到文件上呢钞支?這是由操作系統(tǒng)內部決定的茫蛹。
內存放不下整個文件也不要緊,操作系統(tǒng)會自動進行處理烁挟,將需要的內容讀到內存婴洼,將修改的內容保存到硬盤,將不再使用的內存釋放撼嗓。
如何用NIO將文件映射到內存中呢柬采?下面有個小例子表示將文件的前1024個字節(jié)映射到內存中。
FileChannel fileChannel = new FileInputStream("").getChannel();
MappedByteBuffer map = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
創(chuàng)建一個內存映射文件只需要在通道中調用map()
方法即可且警,MapMode
有以下三個參數(shù)
-
READ_ONLY
:創(chuàng)建一個只讀的映射文件 -
READ_WRITE
:創(chuàng)建一個既能讀也能寫的映射文件 -
PRIVATE
:創(chuàng)建一個寫時拷貝(copy-on-write)的映射文件
我們可以簡單的對比一下用內存映射文件對文件進行讀寫操作和用緩存Buffer
對文件進行讀寫操作的速度比較警没。
public static void main(String[] args) throws IOException {
String fileName="/Users/hupengfei/Downloads/a.sql";
long t1=System.currentTimeMillis();
FileChannel fileChannel = new RandomAccessFile(fileName,"rw").getChannel();
IntBuffer map = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, fileChannel.size()).asIntBuffer();
map.put(0);
for (int i = 1; i < 50590; i++) {
map.put(map.get(i-1));
}
fileChannel.close();
long t=System.currentTimeMillis()-t1;
System.out.println("Mapped Read/Write:"+t);
long t2=System.currentTimeMillis();
RandomAccessFile randomAccessFile = new RandomAccessFile(new File(fileName),"rw");
randomAccessFile.writeInt(1);
for (int i = 0 ; i<50590;i++){
randomAccessFile.seek(randomAccessFile.length()-4);
randomAccessFile.writeInt(randomAccessFile.readInt());
}
randomAccessFile.close();
long t22=System.currentTimeMillis()-t2;
System.out.println("Stream Read/Write:"+t22);
}
發(fā)現(xiàn)打印如下
Mapped Read/Write:29
Stream Read/Write:2439
文件越大,那么這個差異會更明顯振湾。
文件加鎖
在JDK1.4中引入了文件加鎖的機制杀迹,它允許我們同步的訪問某個作為共享資源的文件。對于同一文件競爭的兩個線程可能是來自于不同的操作系統(tǒng)押搪,也可能是不同的進程树酪,也可能是相同的進程,例如Java中兩個線程對于文件的競爭大州。文件鎖對于其他的操作系統(tǒng)的進程是可見的续语,因為文件加鎖是直接映射到了本地操作系統(tǒng)的加鎖工具。
下面舉了一個簡單的關于文件加鎖的例子
public static void main(String[] args) throws IOException, InterruptedException {
FileOutputStream fileOutputStream = new FileOutputStream("/Users/hupengfei/Downloads/a.sql");
FileLock fileLock = fileOutputStream.getChannel().tryLock();
if (fileLock != null){
System.out.println("Locked File");
TimeUnit.MICROSECONDS.sleep(100);
fileLock.release();
System.out.println("Released Lock");
}
fileLock.close();
}
通過對FileChannel
調用tryLock()
或者lock()
方法厦画,就可以獲得整個文件的FileLock
-
tryLock()
:是非阻塞的疮茄,如果不能獲得鎖,那么他就會直接從方法調用中返回 -
lock()
:是阻塞的根暑,它要阻塞進程直到鎖可以獲得為止
調用FileLock.release()
可以釋放鎖力试。
當然也可以通過以下的方式對于文件的部分進行上鎖
tryLock(long position,long size,boolean shared)
lock(long position,long size,boolean shared)
對于加鎖的區(qū)域是通過position
和size
進行限定的,而第三個參數(shù)指定是否為共享鎖排嫌。無參數(shù)的加鎖方法會對整個文件進行加鎖畸裳,甚至文件變大以后也是如此。其中鎖的類型是獨占鎖還是共享鎖可以通過FileLock.isShared()
進行查詢淳地。