Java基础Demo -- NIO读文件

本文介绍了Java NIO的基础知识,包括通道、缓冲区的使用,字符集的概念,以及NIO在处理文件时的工具类如Path、Files和BasicFileAttributes。还探讨了在处理不同系统换行符和中文乱码问题时的注意事项,并强调了在处理大文件时采用分段读取的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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工具类:更加细腻的管控文件的大量方法.
*              常用方法有:
*              删除delete() 创建目录createDirectory() 创建文件createFile() 是否存在exists()
*              是否目录isDirectory() 是否文件isRegularFile() 文件大小size()
*              文件属性readAttributes() 是否可读isReadable() 是否可写isWritable()
*              是否隐藏的isHidden() 是否可执行的isExecutable() 文件拷贝copy() 文件移动move()
*              文件的流对象newInputStream() newOutputStream() 获取文件通道newByteChannel()
*
*              OpenOption文件打开时的一些选项:枚举值有:APPEND追加写入
*                                                        CREATE_NEW文件不存在时就创建
*                                                        CREATE老文件要有就干掉,创建新的
*
*              jdk8中该类新增4个方法 list() walk() lines() find() 都返回Stream流
*
* 文件属性类:BasicFileAttributes 常用方法有:
*                                 文件创建时间createTime() 是否目录isDirectory()
*                                 文件最后修改时间lastModifiedTime() 是否文件isRegularFile()
*                                 文件最后访问时间lastAccessTime() 文件大小size()                           
*/

/**
* BufferUnderflowException BufferOverflowException 错误原因:读取的长度超出了允许的长度
*
* 例如下面的代码:
* ByteBuffer buf = ByteBuffer.allocate(2); /这里只分配了2个字节
* buf.order(ByteOrder.LITTLE_ENDIAN);
* byte[] tmp = new byte[3]; 
* 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
*/

/**
* 基于通道读文件
* 一般步骤:
* a:封装Path
* b:获取通道getChannel(path)
* c:准备缓冲区Buffer
* d:通过通道读写文件fChannel.read(buf); fChannel.write(buf);
*/

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


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

	}
}

总结:硬性读取字节流,根据文件不同的编码格式,然后转换成中文,是何等的麻烦,那有没有更好的解决办法呢?有的,就是一把读出文件的字节流,然后字节流封装成大的字符串。当然这样做,有弊端,如果文件太大,就会撑爆内存,那么我们为了防止撑爆内存,可以采取分段读取大文件的策略,再前一段的文件内容的最后段和紧邻后一段文件内容的最前端,进行下字符的推导工作,按照我上面示例的怎么推导出一个字符的办法来做吧,老铁们。。。回头我在补上分段解析文件流的代码示例

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值