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 (日文)
*/
}
}