java nio

来源:互联网 发布:tag在vb中什么意思 编辑:程序博客网 时间:2024/06/05 17:53

Java NIO读取大文件已经不是什么新鲜事了,但根据网上示例写出的代码来处理具体的业务总会出现一些奇怪的Bug。

针对这种情况,我总结了一些容易出现Bug的经验

1.编码格式

由于是使用NIO读文件通道的方式,拿到的内容都是byte[],在生成String对象时一定要设置与读取文件相同的编码,而不是项目编码。

2.换行符

一般在业务中,多数情况都是读取文本文件,在解析byte[]时发现有换行符时则认为该行已经结束。

在我们写Java程序时,大多数都认为\r\n为一个文本的一行结束,但这个换行符根据当前系统的不同,换行符也不相同,比如在Linux/Unix下换行符是\n,而在Windows下则是\r\n。如果将换行符定为\r\n,在读取由Linux系统生成的文本文件则会出现乱码。

3.读取正常,但中间偶尔会出现乱码

[java] view plaincopy
  1. public static void main(String[] args) throws Exception {  
  2.     int bufSize = 1024;  
  3.     byte[] bs = new byte[bufSize];  
  4.     ByteBuffer byteBuf = ByteBuffer.allocate(1024);  
  5.     FileChannel channel = new RandomAccessFile("d:\\filename","r").getChannel();  
  6.     while(channel.read(byteBuf) != -1) {  
  7.       int size = byteBuf.position();  
  8.       byteBuf.rewind();  
  9.       byteBuf.get(bs);  
  10.       // 把文件当字符串处理,直接打印做为一个例子。  
  11.       System.out.print(new String(bs, 0, size));  
  12.       byteBuf.clear();  
  13.     }  
  14.   }  

这是网上大多数使用NIO来读取大文件的例子,但这有个问题。中文字符根据编码不同,会占用2到3个字节,而上面程序中每次都读取1024个字节,那这样就会出现一个问题,如果该文件中第1023,1024,1025三个字节是一个汉字,那么一次读1024个字节就会将这个汉字切分成两瓣,生成String对象时就会出现乱码。

解决思路是判断这读取的1024个字节,最后一位是不是\n,如果不是,那么将最后一个\n以后的byte[]缓存起来,加到下一次读取的byte[]头部。

以下为代码结构:

NioFileReader

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. package com.okey.util;  
  2.   
  3. import java.io.*;  
  4. import java.nio.ByteBuffer;  
  5. import java.nio.channels.FileChannel;  
  6.   
  7. /** 
  8.  * Created with Okey 
  9.  * User: Okey 
  10.  * Date: 13-3-14 
  11.  * Time: 上午11:29 
  12.  * 读取文件工具 
  13.  */  
  14. public class NIOFileReader {  
  15.   
  16.     // 每次读取文件内容缓冲大小,默认为1024个字节  
  17.     private int bufSize = 1024;  
  18.     // 换行符  
  19.     private byte key = "\n".getBytes()[0];  
  20.     // 当前行数  
  21.     private long lineNum = 0;  
  22.     // 文件编码,默认为gb2312  
  23.     private String encode = "gb2312";  
  24.     // 具体业务逻辑监听器  
  25.     private ReaderListener readerListener;  
  26.   
  27.     /** 
  28.      * 设置回调方法 
  29.      * @param readerListener 
  30.      */  
  31.     public NIOFileReader(ReaderListener readerListener) {  
  32.         this.readerListener = readerListener;  
  33.     }  
  34.   
  35.     /** 
  36.      * 设置回调方法,并指明文件编码 
  37.      * @param readerListener 
  38.      * @param encode 
  39.      */  
  40.     public NIOFileReader(ReaderListener readerListener, String encode) {  
  41.         this.encode = encode;  
  42.         this.readerListener = readerListener;  
  43.     }  
  44.   
  45.     /** 
  46.      * 普通io方式读取文件 
  47.      * @param fullPath 
  48.      * @throws Exception 
  49.      */  
  50.     public void normalReadFileByLine(String fullPath) throws Exception {  
  51.         File fin = new File(fullPath);  
  52.         if (fin.exists()) {  
  53.             BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(fin), encode));  
  54.             String lineStr;  
  55.             while ((lineStr = reader.readLine()) != null) {  
  56.                 lineNum++;  
  57.                 readerListener.outLine(lineStr.trim(), lineNum, false);  
  58.             }  
  59.             readerListener.outLine(null, lineNum, true);  
  60.             reader.close();  
  61.         }  
  62.     }  
  63.   
  64.     /** 
  65.      * 使用NIO逐行读取文件 
  66.      * 
  67.      * @param fullPath 
  68.      * @throws java.io.FileNotFoundException 
  69.      */  
  70.     public void readFileByLine(String fullPath) throws Exception {  
  71.         File fin = new File(fullPath);  
  72.         if (fin.exists()) {  
  73.             FileChannel fcin = new RandomAccessFile(fin, "r").getChannel();  
  74.             try {  
  75.                 ByteBuffer rBuffer = ByteBuffer.allocate(bufSize);  
  76.                 // 每次读取的内容  
  77.                 byte[] bs = new byte[bufSize];  
  78.                 // 缓存  
  79.                 byte[] tempBs = new byte[0];  
  80.                 String line = "";  
  81.                 while (fcin.read(rBuffer) != -1) {  
  82.                     int rSize = rBuffer.position();  
  83.                     rBuffer.rewind();  
  84.                     rBuffer.get(bs);  
  85.                     rBuffer.clear();  
  86.                     byte[] newStrByte = bs;  
  87.                     // 如果发现有上次未读完的缓存,则将它加到当前读取的内容前面  
  88.                     if (null != tempBs) {  
  89.                         int tL = tempBs.length;  
  90.                         newStrByte = new byte[rSize + tL];  
  91.                         System.arraycopy(tempBs, 0, newStrByte, 0, tL);  
  92.                         System.arraycopy(bs, 0, newStrByte, tL, rSize);  
  93.                     }  
  94.                     int fromIndex = 0;  
  95.                     int endIndex = 0;  
  96.                     // 每次读一行内容,以 key(默认为\n) 作为结束符  
  97.                     while ((endIndex = indexOf(newStrByte, fromIndex)) != -1) {  
  98.                         byte[] bLine = substring(newStrByte, fromIndex, endIndex);  
  99.                         line = new String(bLine, 0, bLine.length, encode);  
  100.                         lineNum++;  
  101.                         // 输出一行内容,处理方式由调用方提供  
  102.                         readerListener.outLine(line.trim(), lineNum, false);  
  103.                         fromIndex = endIndex + 1;  
  104.                     }  
  105.                     // 将未读取完成的内容放到缓存中  
  106.                     tempBs = substring(newStrByte, fromIndex, newStrByte.length);  
  107.                 }  
  108.                 // 将剩下的最后内容作为一行,输出,并指明这是最后一行  
  109.                 String lineStr = new String(tempBs, 0, tempBs.length, encode);  
  110.                 readerListener.outLine(lineStr.trim(), lineNum, true);  
  111.             } catch (Exception e) {  
  112.                 e.printStackTrace();  
  113.             } finally {  
  114.                 fcin.close();  
  115.             }  
  116.   
  117.         } else {  
  118.             throw new FileNotFoundException("没有找到文件:" + fullPath);  
  119.         }  
  120.     }  
  121.   
  122.     /** 
  123.      * 查找一个byte[]从指定位置之后的一个换行符位置 
  124.      * @param src 
  125.      * @param fromIndex 
  126.      * @return 
  127.      * @throws Exception 
  128.      */  
  129.     private int indexOf(byte[] src, int fromIndex) throws Exception {  
  130.   
  131.         for (int i = fromIndex; i < src.length; i++) {  
  132.             if (src[i] == key) {  
  133.                 return i;  
  134.             }  
  135.         }  
  136.         return -1;  
  137.     }  
  138.   
  139.     /** 
  140.      * 从指定开始位置读取一个byte[]直到指定结束位置为止生成一个全新的byte[] 
  141.      * @param src 
  142.      * @param fromIndex 
  143.      * @param endIndex 
  144.      * @return 
  145.      * @throws Exception 
  146.      */  
  147.     private byte[] substring(byte[] src, int fromIndex, int endIndex) throws Exception {  
  148.         int size = endIndex - fromIndex;  
  149.         byte[] ret = new byte[size];  
  150.         System.arraycopy(src, fromIndex, ret, 0, size);  
  151.         return ret;  
  152.     }  
  153.   
  154. }  

ReaderListener

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. package com.okey.util;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5.   
  6. /** 
  7.  * Created with Okey 
  8.  * User: Okey 
  9.  * Date: 13-3-14 
  10.  * Time: 下午3:19 
  11.  * NIO逐行读数据回调方法 
  12.  */  
  13. public abstract class ReaderListener {  
  14.   
  15.     // 一次读取行数,默认为500  
  16.     private int readColNum = 500;  
  17.   
  18.     private List<String> list = new ArrayList<String>();  
  19.   
  20.     /** 
  21.      * 设置一次读取行数 
  22.      * @param readColNum 
  23.      */  
  24.     protected void setReadColNum(int readColNum) {  
  25.         this.readColNum = readColNum;  
  26.     }  
  27.   
  28.     /** 
  29.      * 每读取到一行数据,添加到缓存中 
  30.      * @param lineStr 读取到的数据 
  31.      * @param lineNum 行号 
  32.      * @param over 是否读取完成 
  33.      * @throws Exception 
  34.      */  
  35.     public void outLine(String lineStr, long lineNum, boolean over) throws Exception {  
  36.         if(null != lineStr)  
  37.             list.add(lineStr);  
  38.         if (!over && (lineNum % readColNum == 0)) {  
  39.             output(list);  
  40.             list.clear();  
  41.         } else if (over) {  
  42.             output(list);  
  43.             list.clear();  
  44.         }  
  45.     }  
  46.   
  47.     /** 
  48.      * 批量输出 
  49.      * 
  50.      * @param stringList 
  51.      * @throws Exception 
  52.      */  
  53.     public abstract void output(List<String> stringList) throws Exception;  
  54.   
  55. }  

ReadTxt(具体业务逻辑)


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. package com.okey.util;  
  2.   
  3.   
  4. import java.io.File;  
  5. import java.util.HashMap;  
  6. import java.util.List;  
  7. import java.util.Map;  
  8.   
  9.   
  10. /** 
  11.  * Created with IntelliJ IDEA. 
  12.  * User: Okey 
  13.  * Date: 14-3-6 
  14.  * Time: 上午11:02 
  15.  * To change this template use File | Settings | File Templates. 
  16.  */  
  17. public class ReadTxt {  
  18.     public static void main(String[] args) throws Exception{  
  19.         String filename = "E:/address_city.utf8.txt";  
  20.         ReaderListener readerListener = new ReaderListener() {  
  21.             @Override  
  22.             public void output(List<String> stringList) throws Exception {  
  23.                 for (String s : stringList) {  
  24.                     System.out.println("s = " + s);  
  25.                 }  
  26.             }  
  27.         };  
  28.         readerListener.setReadColNum(100000);  
  29.         NIOFileReader nioFileReader = new NIOFileReader(readerListener,"utf-8");  
  30.         nioFileReader.readFileByLine(filename);  
  31.     }  
  32. }  
0 0
原创粉丝点击