Java NIO 學習:緩衝區(Buffer)

  學習java.nio軟件包,咱們先從Buffer類開始,學會它的用法。html

  Buffer對象能夠看做是存儲數據的容器,對於每一個非布爾數據類型都有一個對應的子類,例如IntBuffer。java

  下面看一個簡單的使用IntBuffer的例子。ios

package com.henrysun.javaSE.niostudy;

import java.nio.IntBuffer;

public class IntBufferTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// 分配新的int緩衝區,參數爲緩衝區容量  
        // 新緩衝區的當前位置將爲零,其界限(限制位置)將爲其容量。它將具備一個底層實現數組,其數組偏移量將爲零。  
		IntBuffer ib=IntBuffer.allocate(8);
		for(int i=0;i<ib.capacity();++i)
		{
			int j=2*(i+1);
			// 將給定整數寫入此緩衝區的當前位置,當前位置遞增  
			ib.put(j);
		}
		// 重設此緩衝區,將限制設置爲當前位置,而後將當前位置設置爲0  
        ib.flip();  
        // 查看在當前位置和限制位置之間是否有元素  
        while (ib.hasRemaining()) {  
            // 讀取此緩衝區當前位置的整數,而後當前位置遞增  
            int j = ib.get();  
            System.out.print(j + "  ");  
        }  
  
	}

}

  若是是對於文件讀寫,上面幾種Buffer均可能會用到。可是對於網絡讀寫來講,用的最多的是ByteBuffer。數組

緩衝區基礎

  緩衝區是包在一個對象內的基本數據元素數組。Buffer 類相比一個簡單數組的優勢是它將關於數據的數據內容和信息包含在一個單一的對象中。Channel提供從文件、網絡讀取數據的渠道,可是讀取或寫入的數據都必須經由Buffer。具體看下面這張圖就理解了:緩存

        

  屬性

  容量(capacity):可以容納數據元素的最大數量,在緩衝區建立時指定,且沒法改變。網絡

  上界( Limit):指定還有多少數據須要取出(在從緩衝區寫入通道時),或者還有多少空間能夠放入                                           數據(在從通道讀入緩衝區時)。               app

  位置( Position):下一個要被讀或寫的元素索引。位置會自動由相應的 get( )put( )函數更新。dom

  標記( Mark):一個備忘位置。調用 mark( )來設定 mark = postion。調用 reset( )設定 position =mark。                                標記在設定前是未定義的(undefined)。函數

  這四個屬性之間老是遵循如下關係:post

  0 <= mark <= position <= limit <= capacity

  下圖展現了一個新建立的容量爲 10的 ByteBuffer 邏輯視圖

            

  位置被設爲 0,並且容量和上界被設爲 10,恰好通過緩衝區可以容納的最後一個字節。標記最初未定義。容量是固定的,但另外的三個屬性能夠在使用緩衝區時改變。

  如今咱們能夠從通道中讀取一些數據到緩衝區中,注意從通道讀取數據,至關於往緩衝區中寫入數據。若是讀取4個本身的數據,則此時position的值爲4,即下一個將要被寫入的字節索引爲4,而limit仍然是10,以下圖所示:

        

下一步把讀取的數據寫入到輸出通道中,至關於從緩衝區中讀取數據,在此以前,必須調用flip()方法,該方法將會完成兩件事情:

1. 把limit設置爲當前的position值 
2. 把position設置爲0

因爲position被設置爲0,因此能夠保證在下一步輸出時讀取到的是緩衝區中的第一個字節,而limit被設置爲當前的position,能夠保證讀取的數據正好是以前寫入到緩衝區中的數據,以下圖所示:

           

  如今調用get()方法從緩衝區中讀取數據寫入到輸出通道,這會致使position的增長而limit保持不變,但position不會超過limit的值,因此在讀取咱們以前寫入到緩衝區中的4個本身以後,position和limit的值都爲4,以下圖所示:

         

  在從緩衝區中讀取數據完畢後,limit的值仍然保持在咱們調用flip()方法時的值,調用clear()方法可以把全部的狀態變化設置爲初始化時的值,以下圖所示:

      

  用代碼來驗證這個過程:

package com.henrysun.javaSE.niostudy;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * Buffer的重要屬性學習
 * @author henrysun
 *
 */
public class BufferShuXing {

	/**
	 * @param args
	 * @throws IOException 
	 */
	public static void main(String[] args) throws IOException {
	 FileInputStream fin=new FileInputStream("C:\\Users\\henrysun\\Desktop\\test.txt");
	 FileChannel fc=fin.getChannel();
	 ByteBuffer buffer=ByteBuffer.allocate(10);
	 output("初始化", buffer);
	 
	 fc.read(buffer);
	 output("調用read", buffer);
	 
	   buffer.flip();  
       output("調用flip()", buffer);  
 
       while (buffer.remaining() > 0) {  
           byte b = buffer.get();  
           // System.out.print(((char)b));  
       }  
       output("調用get()", buffer);  
 
       buffer.clear();  
       output("調用clear()", buffer);  
 
       fin.close();  
	}
	
	
	public static void output(String step, Buffer buffer) {  
        System.out.println(step + " : ");  
        System.out.print("capacity: " + buffer.capacity() + ", ");  
        System.out.print("position: " + buffer.position() + ", ");  
        System.out.println("limit: " + buffer.limit());  
        System.out.println();
    }  

}

  緩衝區的分配

  在建立一個緩衝區對象時,會調用靜態方法allocate()來指定緩衝區的容量,其實調用 allocate()至關於建立了一個指定大小的數組,並把它包裝爲緩衝區對象。咱們也能夠直接將一個數組,包裝爲緩衝區對象。

public class BufferWrap {  
  
    public void myMethod()  
    {  
        // 分配指定大小的緩衝區  
        ByteBuffer buffer1 = ByteBuffer.allocate(10);  
          
        // 包裝一個現有的數組  
        byte array[] = new byte[10];  
        ByteBuffer buffer2 = ByteBuffer.wrap( array );  
    }  
}

  緩衝區分片

  即根據現有的緩衝區對象來建立一個子緩衝區,也就是在現有緩衝區上切出一片來做爲一個新的緩衝區,但現有的緩衝區與建立的子緩衝區在底層數組層面上是數據共享的,也就是說,子緩衝區至關因而現有緩衝區的一個視圖窗口。調用slice()方法能夠建立一個子緩衝區。

package com.henrysun.javaSE.niostudy;

import java.nio.ByteBuffer;

/**
 * 緩衝區分片
 * @author henrysun
 *
 */
public class BufferSlice {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		  ByteBuffer buffer = ByteBuffer.allocate( 10 );  
          
	        // 緩衝區中的數據0-9  
	        for (int i=0; i<buffer.capacity(); ++i) {  
	            buffer.put( (byte)i );  
	        }  
	          
	        // 建立子緩衝區  
	        buffer.position( 3 );  
	        buffer.limit( 7 );  
	        ByteBuffer slice = buffer.slice();  
	          
	        // 改變子緩衝區的內容  
	        for (int i=0; i<slice.capacity(); ++i) {  
	            byte b = slice.get( i );  
	            b *= 10;  	
	            slice.put( i, b );  
	        }  
	          
	        buffer.position( 0 );  
	        buffer.limit( buffer.capacity() );  
	          
	        while (buffer.remaining()>0) {  
	            System.out.println( buffer.get() );  
	        }  
	}

}

  只讀緩衝區

  只讀緩衝區能夠讀取它們,可是不能向它們寫入數據。能夠經過調用緩衝區的asReadOnlyBuffer()方法,將任何常規緩衝區轉換爲只讀緩衝區,這個方法返回一個與原緩衝區徹底相同的緩衝區,並與原緩衝區共享數據,只不過它是隻讀的。若是原緩衝區的內容發生了變化,只讀緩衝區的內容也隨之發生變化。

package com.henrysun.javaSE.niostudy;

import java.nio.ByteBuffer;


/**
 * 只讀緩衝區
 * @author Sam Flynn
 *
 */
public class BufferReadonly {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		ByteBuffer buffer=ByteBuffer.allocate(10);
		
		//緩衝區的數字0到9
		for(int i=0;i<buffer.capacity();++i)
		{
			buffer.put((byte)i);
		}
		
		//建立只讀緩衝區
		ByteBuffer readonly=buffer.asReadOnlyBuffer();
		
		//改變原緩衝區的內容
		for(int i=0;i<buffer.capacity();++i)
		{
		    byte b=buffer.get(i);
		    b*=10;
		    buffer.put(i,b);
		}
		
		readonly.position(0);
		readonly.limit(buffer.capacity());
		
		//只讀緩衝區的內容也跟着改變
		while(readonly.remaining()>0)
		{
			System.out.println(readonly.get());
		}
	}

}

  若是嘗試修改只讀緩衝區的內容,則會報ReadOnlyBufferException異常。只讀緩衝區對於保護數據頗有用。在將緩衝區傳遞給某個 對象的方法時,沒法知道這個方法是否會修改緩衝區中的數據。建立一個只讀的緩衝區能夠保證該緩衝區不會被修改。只能夠把常規緩衝區轉換爲只讀緩衝區,而不能將只讀的緩衝區轉換爲可寫的緩衝區。

  直接緩衝區

  直接緩衝區是爲加快I/O速度,使用一種特殊方式爲其分配內存的緩衝區,JDK文檔中的描述爲:給定一個直接字節緩衝區,Java虛擬機將盡最大努 力直接對它執行本機I/O操做。也就是說,它會在每一次調用底層操做系統的本機I/O操做以前(或以後),嘗試避免將緩衝區的內容拷貝到一箇中間緩衝區中 或者從一箇中間緩衝區中拷貝數據。要分配直接緩衝區,須要調用allocateDirect()方法,而不是allocate()方法,使用方式與普通緩衝區並沒有區別。

package com.henrysun.javaSE.niostudy;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * 直接緩衝區
 * @author Sam Flynn
 *
 */
public class BufferDirect {

	/**
	 * @param args
	 * @throws IOException 
	 */
	public static void main(String[] args) throws IOException {
		try {
			FileInputStream fis=new FileInputStream("F:\\Users\\Sam Flynn\\Desktop\\test1.txt");
			FileChannel fcin=fis.getChannel();
			
			FileOutputStream fos=new FileOutputStream("F:\\Users\\Sam Flynn\\Desktop\\test12.txt");
			FileChannel fcout=fos.getChannel();
			
			//使用allocateDirect,而不是allocate  
			ByteBuffer buffer=ByteBuffer.allocateDirect(10);
			
			 while (true) {  
		            buffer.clear();  
		              
		            int r = fcin.read( buffer );  
		              
		            if (r==-1) {  
		                break;  
		            }  
		              
		            buffer.flip();  
		              
		            fcout.write( buffer );  
		        }  
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		
	}

}

  內存映射文件I/O

  內存映射文件I/O是一種讀和寫文件數據的方法,它能夠比常規的基於流或者基於通道的I/O快的多。內存映射文件I/O是經過使文件中的數據出現爲 內存數組的內容來完成的,這其初聽起來彷佛不過就是將整個文件讀到內存中,可是事實上並非這樣。通常來講,只有文件中實際讀取或者寫入的部分纔會映射到內存中。

package com.henrysun.javaSE.niostudy;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

/**
 * 內存映射文件IO
 * @author Sam Flynn
 *
 */
public class BufferMapped {
	static private final int start = 0;
	static private final int size = 1024;  
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		RandomAccessFile raf;
		try {
			raf = new RandomAccessFile( "F:\\Users\\Sam Flynn\\Desktop\\test.txt", "rw" );
			FileChannel fc = raf.getChannel(); 
			MappedByteBuffer mbb;
			try {
				mbb = fc.map( FileChannel.MapMode.READ_WRITE,  
				          start, size );
				 mbb.put( 0, (byte)97 );  
			     mbb.put( 1023, (byte)122 );  
			     raf.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}  
			
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
	}

}

  API

  如下是Buffer類的方法簽名

package java.nio;
public abstract class Buffer {
public final int capacity( )
public final int position( )
public final Buffer position (int newPositio
public final int limit( )
public final Buffer limit (int newLimit)
public final Buffer mark( )
public final Buffer reset( )
public final Buffer clear( )
public final Buffer flip( )
public final Buffer rewind( )
public final int remaining( )
public final boolean hasRemaining( )
public abstract boolean isReadOnly( );
}

  這些函數大多數會返回到它們在( this)上被引用的對象。這是一個容許級聯調用的類設計方法。級聯調用容許這種類型的代碼:

buffer.mark( );
buffer.position(5);
buffer.reset( );

  被簡寫爲:buffer.mark().position(5).reset( );

  注意:緩衝區都是可讀的,但並不是均可寫,每一個具體的緩衝區類都經過執行 isReadOnly()來標示其是否容許該緩存區的內容被修改。

推薦閱讀:

  Java NIO使用及原理分析 (一)

  Java NIO原理 圖文分析及代碼實現

  JAVA NIO 簡介

  Java NIO 入門學習(讀寫文件)

相關文章
相關標籤/搜索