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

	}
}

猜你喜欢

转载自blog.csdn.net/mmlz00/article/details/84996924