1. 程式人生 > >Java基礎Demo -- NIO讀檔案

Java基礎Demo -- NIO讀檔案

NIO:

  • 通道 read write
  • 緩衝區 put get
  • 字符集
  • 選擇器
  • Path介面
  • Files工具類
  • BasicFileAttributes檔案屬性類

/*
* NIO:不是替代IO,是對IO的一種補充,可以更精細的管控流和訪問檔案系統
*
* 通道 :讀取/寫入流的通道類 getChannel()方法 支援通道的類有:FileInputStream FileOutputStream Socket 常用方法有:讀read() 寫write() 
* 緩衝區:依託通道--從流中讀出的內容可放置緩衝區,緩衝區內的內容可寫入流 常用緩衝區 ByteBuffer ...等對應7種基本型別的,以及 MappedByteBuffer用於將檔案對映到緩衝區
          緩衝區的三大要素:當前位置、界限、容量。常用方法有:容量capacity() 清空clear() 位置position() 重置rewind() 界限limit() 寫put() 讀get() 分配allocate() 切分slice()
* 字符集:依託字符集,將位元組對映成字元 例如:UTF-8 GBK
* 選擇器:適用於套接字的通道,使用選擇器可以通過多個通道執行IO
*/

/**
* jdk7 增加了Path Files BasicFileAttributes
*
* Path介面:封裝了路徑,提供了操作路徑的大量方法. Path的toFile() <--互轉--> File的toPath()
            獲取Path物件的工廠方法:Paths.get(String pathname,String...parts); 或者 Paths.get(URI uri);
*
* Files工具類:更加細膩的管控檔案的大量方法. jdk8中該類新增4個方法 list() walk() lines() find() 都返回Stream,可以用lambda表示式來處理業務
*              常用方法有:刪除delete() 建立目錄createDirectory() 建立檔案createFile() 是否存在exists() 是否目錄isDirectory() 是否檔案isRegularFile() 
                           檔案大小size() 檔案屬性readAttributes() 是否可讀isReadable() 是否可寫isWritable() 是否隱藏的isHidden() 是否可執行的isExecutable()
                           檔案拷貝copy() 檔案移動move() 檔案的流物件newInputStream() newOutputStream()
                           獲取檔案通道newByteChannel()
               OpenOption檔案開啟時的一些選項:列舉值有:APPEND追加寫入 CREATE_NEW檔案不存在時就建立 CREATE老檔案要有就幹掉,建立新的
*
* 檔案屬性類:BasicFileAttributes 常用方法有:檔案建立時間createTime() 檔案最後修改時間lastModifiedTime() 檔案最後訪問時間lastAccessTime()
                                              是否目錄isDirectory() 是否檔案isRegularFile() 檔案大小size()
*/

/**
* BufferUnderflowException BufferOverflowException 錯誤原因:讀取的長度超出了允許的長度
*
* 例如下面的程式碼:
* ByteBuffer buf = ByteBuffer.allocate(2); /這裡只分配了2個位元組
* buf.order(ByteOrder.LITTLE_ENDIAN);
* byte[] tmp = new byte[3]; 
* buf.get(tmp); //這裡buf.get(tmp);卻get了3個位元組的資料。所以導致 java.nio.BufferUnderflowException 異常
*
* 如何解決這個問題呢?新增讀取長度與 ByteBuffer 中可讀取的長度的判斷:例如:
* while (buf.remaining() > 0) {    //如果每次讀取1個位元組,那就判斷大於0就行;如果每次讀取2個位元組,那就判斷大於1就行        
*      byte b = buf.get();        
* }
*
* 總結:
* 當 ByteBuffer.remaining() 小於要讀取或寫入的長度時,再執行讀取或寫入操作都會產生異常;
*    讀取則產生 java.nio.BufferUnderflowException 異常
*    寫入則產生 java.nio.BufferOverflowException 異常
* 當 ByteBuffer.remaining()==0 時,不能再執行讀取或寫入操作
*/

/**
* \r 回車鍵 十進位制表示為13 十六進位制0x0D
* \n 換行鍵 十進位制表示為10 十六進位制0x0A
* windows系統 檔案內容中的換行是\r\n
* unix系統 檔案內容中的換行是/n
* mac系統  檔案內容中的換行是/r
*/

/*
* NIO:不是替代IO,是對IO的一種補充,可以更精細的管控流和訪問檔案系統
*
* 通道 :讀取/寫入流的通道類 getChannel()方法 支援通道的類有:FileInputStream FileOutputStream Socket 常用方法有:讀read() 寫write() 
* 緩衝區:依託通道--從流中讀出的內容可放置緩衝區,緩衝區內的內容可寫入流 常用緩衝區 ByteBuffer ...等對應7種基本型別的,以及 MappedByteBuffer用於將檔案對映到緩衝區
          緩衝區的三大要素:當前位置、界限、容量。常用方法有:容量capacity() 清空clear() 位置position() 重置rewind() 界限limit() 寫put() 讀get() 分配allocate() 切分slice()
* 字符集:依託字符集,將位元組對映成字元 例如:UTF-8 GBK
* 選擇器:適用於套接字的通道,使用選擇器可以通過多個通道執行IO
*/

/**
* jdk7 增加了Path Files BasicFileAttributes
*
* Path介面:封裝了路徑,提供了操作路徑的大量方法. Path的toFile() <--互轉--> File的toPath()
            獲取Path物件的工廠方法:Paths.get(String pathname,String...parts); 或者 Paths.get(URI uri);
*
* Files工具類:更加細膩的管控檔案的大量方法. jdk8中該類新增4個方法 list() walk() lines() find() 都返回Stream,可以用lambda表示式來處理業務
*              常用方法有:刪除delete() 建立目錄createDirectory() 建立檔案createFile() 是否存在exists() 是否目錄isDirectory() 是否檔案isRegularFile() 
                           檔案大小size() 檔案屬性readAttributes() 是否可讀isReadable() 是否可寫isWritable() 是否隱藏的isHidden() 是否可執行的isExecutable()
			               檔案拷貝copy() 檔案移動move() 檔案的流物件newInputStream() newOutputStream()
						   獲取檔案通道newByteChannel()
               OpenOption檔案開啟時的一些選項:列舉值有:APPEND追加寫入 CREATE_NEW檔案不存在時就建立 CREATE老檔案要有就幹掉,建立新的
*
* 檔案屬性類:BasicFileAttributes 常用方法有:檔案建立時間createTime() 檔案最後修改時間lastModifiedTime() 檔案最後訪問時間lastAccessTime()
                                              是否目錄isDirectory() 是否檔案isRegularFile() 檔案大小size()
*/

/**
* BufferUnderflowException BufferOverflowException 錯誤原因:讀取的長度超出了允許的長度
*
* 例如下面的程式碼:
* ByteBuffer buf = ByteBuffer.allocate(2); /這裡只分配了2個位元組
* buf.order(ByteOrder.LITTLE_ENDIAN);
* byte[] tmp = new byte[3]; 
* buf.get(tmp); //這裡buf.get(tmp);卻get了3個位元組的資料。所以導致 java.nio.BufferUnderflowException 異常
*
* 如何解決這個問題呢?新增讀取長度與 ByteBuffer 中可讀取的長度的判斷:例如:
* while (buf.remaining() > 0) {	//如果每次讀取1個位元組,那就判斷大於0就行;如果每次讀取2個位元組,那就判斷大於1就行		
*	  byte b = buf.get();		
* }
*
* 總結:
* 當 ByteBuffer.remaining() 小於要讀取或寫入的長度時,再執行讀取或寫入操作都會產生異常;
*    讀取則產生 java.nio.BufferUnderflowException 異常
*    寫入則產生 java.nio.BufferOverflowException 異常
* 當 ByteBuffer.remaining()==0 時,不能再執行讀取或寫入操作
*/

/**
* \r 回車鍵 十進位制表示為13 十六進位制0x0D
* \n 換行鍵 十進位制表示為10 十六進位制0x0A
* windows系統 檔案內容中的換行是\r\n
* unix系統 檔案內容中的換行是\n
* mac系統  檔案內容中的換行是\r
*/

/**
* 以下示例主要針對NIO的三方面的表現著手
*
* 1:基於通道使用NIO
* 2:基於流使用NIO
* 3:基於路徑和檔案來使用NIO
*/

import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.file.*;
import java.util.*;

/**
* 基於通道讀檔案
* 一般步驟:
* a:封裝Path
* b:獲取通道getChannel(path)
* c:準備緩衝區Buffer
* d:通過通道讀寫檔案fChannel.read(buf); fChannel.write(buf);
*/
public class NIOdemo 
{

	/**
	* 通過緩衝區以及通道,實現讀檔案(讀ISO8859-1格式的檔案可以,讀UTF/GBK格式儲存的檔案很麻煩)
	*/
	public static void readFileByByteBufferAndChannel(){

		int count=0; //讀寫緩衝區時,緩衝區內部的遊標不停的向前滾動,表示已經讀到了多少個位元組
		int j=0; //第幾次緩衝區已滿

		
		try(SeekableByteChannel fchan = Files.newByteChannel(Paths.get("test.txt")) ){ //獲取通道
			
			/*舉例: 假定當前測試檔案儲存20個英文字母,系統預設ASNI編碼格式,兩行資料儲存的. windows下換行時有(回車)(換行)兩個位元組,所以內容大小22
			System.out.println("test.txt檔案的內容:0123456789\\r\\naaaaaaaaaa (檔案的大小:"+fchan.size()+") 其中: \\r回車一個位元組 \\n換行一個位元組"); 
			System.out.println("檔案內容windows下分成兩行儲存的,採用ASNI編碼格式,如下:"); 
			System.out.println("0123456789"); 
			System.out.println("aaaaaaaaaa"); 
			*/
			
			long fSize = fchan.size();
			String fileEncoding = judgeFileEncoding();

			ByteBuffer buf = ByteBuffer.allocate(10); //準備緩衝區

			byte[] tmpB = new byte[2]; //臨時存放中文字元截斷的前序位元組(編碼不同,有可能前序就1個位元組,也有可能前序2個位元組)
			int k = 0; //計數器:臨時儲存上一位元組的次數

			/**
			* buf.rewind()方法:為什麼使用?因為想遊標重置後,從緩衝區頭開始幹活(對buf的讀/寫都是時刻往後滾動遊標的)
			*/
			do{
				++j; //第幾次從檔案流讀至緩衝區

				buf.rewind(); //buf重置遊標,歸零處,為了從緩衝區頭開始寫
				count = fchan.read(buf);

				//System.out.println("\r\n當前第("+j+")次從檔案流讀至緩衝區,當前緩衝區位置是:"+buf.position());
				
				if(count!=-1){

					buf.rewind(); //buf重置遊標,歸零處,,為了從緩衝區頭開始讀

					
					//消費緩衝區資料:
					//這種形式處理ISO8859-1編碼格式的檔案是可以的,因為ISO8859-1是西歐標準,一共表示256個字元,都是單位元組的字元(ISO8859-1是對ASCII(127個單位元組的字元)的擴充)
					//這種形式無法處理UTF格式的以及中文字元(原因是多位元組字元,有可能每次緩衝區的最後1個位元組或最後2個位元組是斷碼的)
					//byte[] bs = new byte[count];
					//buf.get(bs);
					//System.out.print(new String(bs));					

					/**
					* 以下的這種形式:通過緩衝區的形式獲取任意編碼格式儲存的檔案內容。!!!太麻煩!!!
					*/
					if( fileEncoding.equals("Unicode") || fileEncoding.equals("UTF-16LE") || fileEncoding.equals("UTF-16BE") ){ //UTF-16或Unicode編碼時,英文中文都佔2個位元組
						
						//由於檔案帶有BOM資訊儲存成UTF-16LE編碼格式的,所以檔案頭部的兩個位元組FFFE僅用來表示檔案編碼格式的,不是真正的檔案內容要剔除掉
						if( j==1 ){
							System.out.print(fileEncoding+"檔案內容頭部的BOM資訊:");
							System.out.printf("%X",buf.get()); //丟棄FF
							System.out.printf("%X",buf.get()); //丟棄FE
							System.out.println();
						}
						
						do{
							byte b1 = buf.get();
							byte b2 = buf.get();
							byte[] bs = {b1,b2};
							System.out.print(new String(bs,fileEncoding));
						}while(buf.position()!=count);
						
					}
					else if( fileEncoding.equals("gb2312") ){ //gb2312時,英文佔1位元組,中文佔2個位元組,檔案頭部沒有編碼格式
						
						do{
							if(k>0){
								byte[] bs = {tmpB[0],buf.get()};
								System.out.print(new String(bs,"gb2312"));
								k=0;
							}

							byte b1 = buf.get();
							if( b1>=0 ){
								System.out.print((char)b1);
							}
							else{//中文處理
								if( buf.position() == count ) {
									tmpB[0] = b1;
									++k;
								}else{
									byte b2 = buf.get();
									byte[] bs = {b1,b2};
									System.out.print(new String(bs,"gb2312"));
								}
								
							}
						}while(buf.position()!=count);
					}
					/**
					* UTF-8 有以下編碼規則:
					* 如果一個位元組,最高位(第8位)為0,表示這是一個 ASCII 字元(00 - 7F)。可見,所有 ASCII 編碼已經是 UTF-8 了。
					* 如果一個位元組,以 11 開頭,連續的 1 的個數暗示這個字元的位元組數,例如:110xxxxx 代表它是雙位元組 UTF-8 字元的首位元組。
					* 如果一個位元組,以 10 開始,表示它不是首位元組,需要向前查詢才能得到當前字元的首位元組
					*/
					else if( fileEncoding.equals("UTF-8") ){ //UTF-8編碼時,英文佔1位元組,中文佔3個位元組

						if( j==1 ){
							System.out.print("UTF-8檔案內容頭部的BOM資訊:");
							System.out.printf("%X",buf.get()); //丟棄EF
							System.out.printf("%X",buf.get()); //丟棄BB
							System.out.printf("%X",buf.get()); //丟棄BF
							System.out.println();
						}

						do{
							if(k==1){
								byte[] bs = {tmpB[0],buf.get()};
								System.out.print(new String(bs,"UTF-8"));
								k=0;
							}
							else if(k==2){
								byte[] bs = {tmpB[0],tmpB[1],buf.get()};
								System.out.print(new String(bs,"UTF-8"));
								k=0;
							}

							byte b1 = buf.get();
							byte b2 = 0;
							if( ((b1>>>5&0x05) == 5 || (b1>>>5&0x05) == 4) && buf.remaining()==1 ){
								b2 = buf.get();
							}

							if(b1>0) System.out.print((char)b1); //表示單位元組的UTF-8格式時的一個字元
							else{
								if( buf.position() == count ) {
									if( (b1>>>5&0x05) == 5 ){
										tmpB[0] = b1;
										++k;
										tmpB[1] = b2;
										++k;                                            
									}
									else if( (b1>>>5&0x05) == 4 ){
										tmpB[0] = b1;
										++k;
									}										
								}
								else{
									if( (b1>>>5&0x05) == 5 ){ //表示位元組為111xxxxx 需要連續讀取3個位元組,代表一個字元
										b2 = buf.get();
										byte b3 = buf.get();
										byte[] bs = {b1,b2,b3};
										System.out.print(new String(bs,"UTF-8")); 
									}
									else if( (b1>>>5&0x05) == 4 ){//表示位元組為110xxxxx 需要連續讀取2個位元組,代表一個字元
										b2 = buf.get();
										byte[] bs = {b1,b2};
										System.out.print(new String(bs,"UTF-8"));
									}
								}									
							}
						}while(buf.position()!=count);
					}

					System.out.println("");
				}
			}while(count!=-1);

		}catch(Exception e){
			e.printStackTrace();
		}
	}

	/**
	* 讀取檔案:NIO緩衝區和通道,檔案內容一次性全部處理
	*/
	public static void readFileByByteBufferAndChannel2(){

		int count=0; //讀寫緩衝區時,緩衝區內部的遊標不停的向前滾動,表示已經讀到了多少個位元組
		
		try(SeekableByteChannel fchan = Files.newByteChannel(Paths.get("test.txt")) ){ //獲取通道
			
			long fSize = fchan.size();
			String fileEncoding = judgeFileEncoding();

			ByteBuffer buf = ByteBuffer.allocate(10); //準備緩衝區

			ArrayList<Byte> arr = new ArrayList();

			do{
				buf.rewind();
				count = fchan.read(buf);		
				if(count!=-1){
					buf.rewind(); //buf重置遊標,歸零處,,為了從緩衝區頭開始讀
					byte[] bb = new byte[count];
					buf.get(bb);
					for (byte t:bb )
						arr.add(t);
				}			
			}while(count!=-1);

			int startIndex = 0;
			if(	fileEncoding.equals("Unicode") || fileEncoding.equals("UTF-16LE") || fileEncoding.equals("UTF-16BE") )
				startIndex = 2;
			else if ( fileEncoding.equals("UTF-8") ){
				startIndex = 3;
			}

			Object[] ob = arr.toArray() ;
			byte[] by = new byte[ob.length-startIndex];
			for(int u=startIndex;u<ob.length;u++)
				by[u-startIndex] = ((Byte)ob[u]).byteValue();

			System.out.println(new String(by,fileEncoding));

		}catch(Exception e){
			e.printStackTrace();
		}
	}

	/**
	* 讀取檔案:NIO緩衝區和通道,檔案內容逐行處理
	*/
	public static void readFileByByteBufferAndChannel3(){

		int count=0; //讀寫緩衝區時,緩衝區內部的遊標不停的向前滾動,表示已經讀到了多少個位元組
		
		try(SeekableByteChannel fchan = Files.newByteChannel(Paths.get("test.txt")) ){ //獲取通道
			
			long fSize = fchan.size();
			System.out.println("fSize is :" + fSize);

			String fileEncoding = judgeFileEncoding();

			ByteBuffer buf = ByteBuffer.allocate(10); //準備緩衝區

			ArrayList<Byte> arr = new ArrayList();

			int j = 0; //緩衝區是10大小時,迴圈多少次
			int lineNum = 0; //檔案內容的第幾行
			int sum = 0; //共讀取的檔案位元組總數
			boolean firstLineFlag = true; //第一行的標誌

			do{
				++j;
				//System.out.println("\r\ndo的第("+j+")次:\r\n");

				int startIndex = 0;
				if( firstLineFlag ){
					if(	fileEncoding.equals("Unicode") || fileEncoding.equals("UTF-16LE") || fileEncoding.equals("UTF-16BE") )
						startIndex = 2;
					else if ( fileEncoding.equals("UTF-8") ){
						startIndex = 3;
					}
					firstLineFlag = false;
				}

				buf.rewind();
				count = fchan.read(buf);			

				if(count!=-1){

					buf.rewind(); //buf重置遊標,歸零處,,為了從緩衝區頭開始讀
					
					//丟棄檔案內容頭部的BOM資訊
					if(startIndex==2){

						//輸出4位8進位制的num
						//printf("%04o/n",num);
						//輸出2位16進位制的num
						//printf("%02X",num);
						System.out.print("檔案內容頭部BOM資訊:");
						System.out.printf( "%02X", buf.get() );
						System.out.printf( "%02X\r\n", buf.get() );
						sum = 2; 
					}
					else if(startIndex==3){
						buf.get();
						buf.get();
						buf.get();
						sum = 3; 
					}

					//處理不同系統檔案內容中的換行符
					if (System.getProperty("line.separator").equals("\r\n")) { //windows系統

						while (buf.remaining()>0)
						{
							if(	fileEncoding.equals("Unicode") || fileEncoding.equals("UTF-16LE") || fileEncoding.equals("UTF-16BE") ){
								
								byte b1 = buf.get();
								byte b2 = buf.get();
								arr.add(b1);
								arr.add(b2);
								sum = sum+2;
								
								byte judgeByte = b1;
								if ( fileEncoding.equals("UTF-16BE") )
									judgeByte = b2 ;

								if(judgeByte==13){
									if( buf.remaining()>0 ){
										byte b3 = buf.get();
										byte b4 = buf.get();
										arr.add(b3);
										arr.add(b4);
										sum = sum+2;

										byte[] by = listToArray(arr);

										System.out.print("正統消費第("+(++lineNum)+")行:"+new String(by,fileEncoding)); //消費一行內容
										arr.clear();
									}
								}
								else if ( judgeByte==10 )
								{
									byte[] by = listToArray(arr);

									System.out.print("跨緩衝區的行,消費第("+(++lineNum)+")行:"+new String(by,fileEncoding)); //消費一行內容
									arr.clear();
								}
								else if (sum==fSize)
								{
									byte[] by = listToArray(arr);

									System.out.print("結束消費第("+(++lineNum)+")行:"+new String(by,fileEncoding)); //消費一行內容
									System.out.println();
									return;
								}
							}

							else{//非UTF-16編碼

								byte t1 = buf.get();
								arr.add(t1);
								sum++;

								if( t1==13 ){
									if( buf.remaining()>0 ){
										byte t2 = buf.get();
										arr.add(t2);
										sum++;

										byte[] by = listToArray(arr);

										System.out.print("正統消費第("+(++lineNum)+")行:"+new String(by,fileEncoding)); //消費一行內容
										arr.clear();
									}
								}
								else if ( t1==10 )
								{
									byte[] by = listToArray(arr);

									System.out.print("跨緩衝區的行,消費第("+(++lineNum)+")行:"+new String(by,fileEncoding)); //消費一行內容
									arr.clear();
								}
								else if (sum==fSize)
								{
									byte[] by = listToArray(arr);

									System.out.print("結束消費第("+(++lineNum)+")行:"+new String(by,fileEncoding)); //消費一行內容
									System.out.println();
									return;
								}
							}
						}
						
					} 
					else if (System.getProperty("line.separator").equals("/r")) { 
						
						while (buf.remaining()>0)
						{
							byte t1 = buf.get();
							arr.add(t1);
							sum++;

							if( t1==13 ){ // /r換行符 mac系統下
								if( buf.remaining()>0 ){

									byte[] by = listToArray(arr);

									System.out.print("正統消費第("+(++lineNum)+")行:"+new String(by,fileEncoding)); //消費一行內容
									arr.clear();
								}
							}
							else if (sum==fSize)
							{
								byte[] by = listToArray(arr);

								System.out.print("/r結束消費第("+(++lineNum)+")行:"+new String(by,fileEncoding)); //消費一行內容
								System.out.println();
								return;
							}
						}    
					} 
					else if (System.getProperty("line.separator").equals("/n")) {    
						
						while (buf.remaining()>0)
						{
							byte t1 = buf.get();
							arr.add(t1);
							sum++;

							if( t1==10 ){ // /n換行符 unix系統下
								if( buf.remaining()>0 ){

									byte[] by = listToArray(arr);

									System.out.print("正統消費第("+(++lineNum)+")行:"+new String(by,fileEncoding)); //消費一行內容
									arr.clear();
								}
							}
							else if (sum==fSize)
							{
								byte[] by = listToArray(arr);

								System.out.print("/n結束消費第("+(++lineNum)+")行:"+new String(by,fileEncoding)); //消費一行內容
								System.out.println();
								return;
							}
						}    
					}

				}

			}while(count!=-1);



		}catch(Exception e){
			e.printStackTrace();
		}
	}
	private static byte[] listToArray(List<Byte> arr){
		
		Object[] ob = arr.toArray() ;
		byte[] by = new byte[ob.length];
		for(int u=0;u<ob.length;u++)
			by[u] = ((Byte)ob[u]).byteValue();
		
		return by;
	}
	

	/**
	* 檔案對映到緩衝區:整個檔案的內容(如果是UTF-8/UTF-16/Unicode編碼格式儲存的檔案,內容頭部有編碼格式的位元組碼,除非是這三種形式無BOM儲存時才沒檔案內容字首)
	* 檔案內容頭部位元組碼:3個位元組EFBBBF表示UTF-8編碼格式儲存的檔案
	*                     2個位元組FFFE表示Unicode(windows系統預設Unicode為UTF-16LE格式)儲存的檔案,英文字母高8位位元組補0
	*                     2個位元組FEFF表示Unicode為UTF-16BE格式儲存的檔案,英文字母低8位位元組補0
	*/
	public static void readFileByMappedByteBufferAndChannel(){
	
		try(FileChannel fchan = (FileChannel)Files.newByteChannel(Paths.get("test.txt")) ){ //獲取通道
					
			String fileEncoding = judgeFileEncoding();
			
			long fSize = fchan.size();

			MappedByteBuffer buf = fchan.map( FileChannel.MapMode.READ_ONLY, 0, fSize ); //對映到緩衝區
			
			ArrayList<Byte>  arr = new ArrayList();
			for(int i=0; i<fSize; i++){

				if( fileEncoding.equals("Unicode") || fileEncoding.equals("UTF-16LE") || fileEncoding.equals("UTF-16BE") ){ //UTF-16或Unicode編碼時,英文中文都佔2個位元組
										
					if(i==0) {
						System.out.print(fileEncoding+"檔案內容頭部的BOM資訊:");
					}
					if(i<=1) {//由於使用了帶有BOM內容訊息頭的儲存的檔案,所以檔案頭部的2個位元組FFFE或FEFF僅用來表示檔案編碼格式的,不是真正的檔案內容要剔除掉
						System.out.printf("%X",buf.get());
						continue;
					}
					if(i==2) System.out.println("\r\n\r\n檔案內容如下:\r\n");

					/*
					 * 每次迴圈讀取兩個位元組來處理,逐個字元的消費模式
					i=i+1;
					byte b1 = buf.get();
					byte b2 = buf.get();
					byte[] bs = {b1,b2};
					System.out.print(new String(bs,fileEncoding));
					*/
					
					/*
					 * 每次迴圈從緩衝區讀出1個位元組來消費,壓入arrayList,一把消費整個檔案內容字元的模式
					 */
					arr.add(buf.get());
					if(buf.remaining()==0){

						Object[] ob = arr.toArray() ;
						byte[] by = new byte[ob.length];
						for(int u=0;u<ob.length;u++)
							by[u] = ((Byte)ob[u]).byteValue();

						System.out.print(new String(by,fileEncoding));
					}

				}
				else if( fileEncoding.equals("gb2312") ){ //gb2312時,英文佔1位元組,中文佔2個位元組,檔案頭部沒有編碼格式
					
					byte b1 = buf.get();
					if( b1>=0 ){
						System.out.print((char)b1);
					}
					else{//中文處理
						i=i+1;
						byte b2 = buf.get();
						byte[] bs = {b1,b2};
						System.out.print(new String(bs));
					}
				}
				/**
				* UTF-8 有以下編碼規則:
				* 如果一個位元組,最高位(第8位)為0,表示這是一個 ASCII 字元(00 - 7F)。可見,所有 ASCII 編碼已經是 UTF-8 了。
				* 如果一個位元組,以 11 開頭,連續的 1 的個數暗示這個字元的位元組數,例如:110xxxxx 代表它是雙位元組 UTF-8 字元的首位元組。
				* 如果一個位元組,以 10 開始,表示它不是首位元組,需要向前查詢才能得到當前字元的首位元組
				*/
				else if( fileEncoding.equals("UTF-8") ){ //UTF-8編碼時,英文佔1位元組,中文佔3個位元組

					if(i==0) System.out.print("UTF-8檔案內容頭部的BOM資訊:");
					if(i<=2) {//由於使用了帶有BOM內容訊息頭的儲存的檔案,所以檔案頭部的3個位元組EFBBBF僅用來表示檔案編碼格式的,不是真正的檔案內容要剔除掉
						System.out.printf("%X",buf.get());
						continue;
					}
					if(i==3) System.out.println("\r\n\r\n檔案內容如下:\r\n");

					/* UTF-8編碼:逐個字元的消費模式
					 * 每次迴圈: ISO8859-1(包含ASCII)的字元為單位元組,1位元組的字元的消費模式
					 *           非中文且非ISO8859-1的字元為雙位元組,2位元組的字元的消費模式
					 *           中文的字元為三位元組,2位元組的中文字元的消費模式
					byte b1 = buf.get();
					if(b1>0) System.out.print((char)b1); //表示ASCII碼單位元組的UTF-8格式時的一個字元
					else{
						if( (b1>>>5&0x01) == 1 ){ //表示位元組為111xxxxx 需要連續讀取3個位元組,代表一個字元
							i=i+2;
							byte b2 = buf.get();
							byte b3 = buf.get();
							byte[] bs = {b1,b2,b3};
							System.out.print(new String(bs,"UTF-8")); 
						}
						else if( (b1>>>5&0x01) == 0 ){//表示位元組為110xxxxx 需要連續讀取2個位元組,代表一個字元
							i=i+1;
							byte b2 = buf.get();
							byte[] bs = {b1,b2};
							System.out.print(new String(bs,"UTF-8"));
						}
					}
					*/

					/*
					 * 每次迴圈從緩衝區讀出1個位元組來消費,壓入arrayList,一把消費整個檔案內容字元的模式
					 */
					arr.add(buf.get());
					if(buf.remaining()==0){

						Object[] ob = arr.toArray() ;
						byte[] by = new byte[ob.length];
						for(int u=0;u<ob.length;u++)
							by[u] = ((Byte)ob[u]).byteValue();

						System.out.print(new String(by,fileEncoding));
					}
				}
				
			}

			System.out.println("");
			
		}catch(Exception e){
			e.printStackTrace();
		}
	}


	/**
	* 傳統IO方式的讀檔案
	*/
	public static void readFileByBufferedReader(){
		
		String fileEncoding = judgeFileEncoding();
		boolean deleteBOM = false;

		if(	fileEncoding.equals("Unicode") 
			|| fileEncoding.equals("UTF-16LE") 
			|| fileEncoding.equals("UTF-16BE") 
			|| fileEncoding.equals("UTF-8")
		){
			deleteBOM = true;
		}

		try( BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("test.txt"),fileEncoding)) ){	

			StringBuffer sb = new StringBuffer();

			String line ;
			do{
				line = br.readLine();
				if(null!=line) {
					if( deleteBOM ){ line = line.substring(1); } //清理檔案內容頭部的BOM資訊
					sb.append(line).append(System.getProperty("line.separator"));
				}
			}while(null!=line);

			System.out.println("\r\n傳統IO方式 test.txt content is: \r\n"+sb.toString());
		}
		catch(Exception e){
		}	
	}


	/**
	* 判斷檔案的編碼格式
	* UTF-16BE (big endian), 俗稱大頭 
	* UTF-16LE(little endian), 俗稱小頭 這個是比較常用的(高8位表示ascii值,低8位為0) windows作業系統預設儲存為Unicode時就是指的UTF-16LE
	* 舉例:
	* ascii字母 'a' 16進製表示為0x61 當UTF-16BE表示時為0x0061
	*                                當UTF-16LE表示時為0x6100 據說是為了提高速度而迎合CPU的胃口, CPU就是這到倒著吃資料的
	*/
	public static String judgeFileEncoding(){

		String fileEncoding = "gb2312"; //中國大陸的簡體版中文windows作業系統,預設檔案編碼格式是ANSI,也就是簡體中文的gb2312編碼格式 英文佔1個位元組 簡體中文佔2個位元組

		try(InputStream inputStream = new FileInputStream("test.txt")){
			
			byte[] head = new byte[3];    
			inputStream.read(head);
			
			if( head[0] == -1 && head[1] == -2 ) //head[0]為0xFF head[1]為FE 剛開始的三個位元組如果是FFFE打頭的,說明檔案內容是UTF-16儲存的 類似Unicode編碼 英文和中文都是佔2個位元組
				fileEncoding = "UTF-16LE";    
			
			else if( head[0] == -2 && head[1] == -1 ) //head[0]為0xFE head[1]為FF 剛開始的三個位元組如果是FEFF打頭的,說明檔案內容是Unicode儲存的 英文和中文都是佔2個位元組   
			    fileEncoding = "UTF-16BE";    
			
			else if( head[0]==-17 && head[1]==-69 && head[2] ==-65) //head[0]為0xEF head[1]為0xBB head[1]為0xBF 剛開始的三個位元組如果是EFBBBF打頭的,說明檔案內容是UTF-8儲存的 英文佔1個位元組 中文佔3個位元組
			    fileEncoding = "UTF-8";
		}
		catch(Exception e){}  
		
		return fileEncoding ;
	}

	public static void main(String[] args) 
	{
		//讀取檔案:NIO緩衝區和通道,檔案內容每個字元處理
		//readFileByByteBufferAndChannel();
		//讀取檔案:NIO緩衝區和通道,檔案內容一次性全部處理(如果檔案內容過大,建議使用逐行處理)
		//readFileByByteBufferAndChannel2();
		
		//讀取檔案:NIO緩衝區和通道,檔案內容逐行處理
		readFileByByteBufferAndChannel3();


		//readFileByMappedByteBufferAndChannel();
		
		//讀取檔案:傳統IO模式,檔案內容逐行處理
		//readFileByBufferedReader();

		//judgeFileEncoding();

		/*
		System.out.println("-17的十六進位制輸出"+Integer.toHexString(-17));
		System.out.println("-69的十六進位制輸出"+Integer.toHexString(-69));
		System.out.println("-65的十六進位制輸出"+Integer.toHexString(-65));

		int a = -17;
		System.out.println("-17的二進位制輸出"+Integer.toBinaryString(a));
		System.out.println("-17的八進位制輸出"+Integer.toOctalString(a));
		System.out.println("-17的十六進位制輸出"+Integer.toHexString(a));
		*/

		/*
		 UTF-8 (Unicode表示時如下) 
		 - u4e00-u9fa5 (中文) 0x3400~0x4DB5
		 - x3130-x318F (韓文)  
		 - xAC00-xD7A3 (韓文)  
		 - u0800-u4e00 (日文)
		*/

	}
}