JAVA使用多线程读取超大文件

接上次写的“JAVA读取超大文件”。在读取超过10G的文件时会发现一次读一行的速度实在是不能接受,想到使用多线程+FileChannel来做一个使用多线程版本。

基本思路如下:

1.计算出文件总大小

2.分段处理,计算出每个线程读取文件的开始与结束位置

  (文件大小/线程数)*N,N是指第几个线程,这样能得到每个线程在读该文件的大概起始位置

使用"大概起始位置",作为读文件的开始偏移量(fileChannel.position("大概起始位置")),来读取该文件,直到读到第一个换行符,记录下这个换行符的位置,作为该线程的准确起始位置.同时它也是上一个线程的结束位置.最后一个线程的结束位置也直接设置为-1

3.启动线程,每个线程从开始位置读取到结束位置为止


代码如下:

读文件工具类

[java]  view plain copy
  1. import java.io.*;  
  2. import java.nio.ByteBuffer;  
  3. import java.nio.channels.FileChannel;  
  4. import java.util.Observable;  
  5.   
  6. /** 
  7.  * Created with IntelliJ IDEA. 
  8.  * User: okey 
  9.  * Date: 14-4-2 
  10.  * Time: 下午3:12 
  11.  * 读取文件 
  12.  */  
  13. public class ReadFile extends Observable {  
  14.   
  15.     private int bufSize = 1024;  
  16.     // 换行符  
  17.     private byte key = "\n".getBytes()[0];  
  18.     // 当前行数  
  19.     private long lineNum = 0;  
  20.     // 文件编码,默认为gb2312  
  21.     private String encode = "gb2312";  
  22.     // 具体业务逻辑监听器  
  23.     private ReaderFileListener readerListener;  
  24.   
  25.     public void setEncode(String encode) {  
  26.         this.encode = encode;  
  27.     }  
  28.   
  29.     public void setReaderListener(ReaderFileListener readerListener) {  
  30.         this.readerListener = readerListener;  
  31.     }  
  32.   
  33.     /** 
  34.      * 获取准确开始位置 
  35.      * @param file 
  36.      * @param position 
  37.      * @return 
  38.      * @throws Exception 
  39.      */  
  40.     public long getStartNum(File file, long position) throws Exception {  
  41.         long startNum = position;  
  42.         FileChannel fcin = new RandomAccessFile(file, "r").getChannel();  
  43.         fcin.position(position);  
  44.         try {  
  45.             int cache = 1024;  
  46.             ByteBuffer rBuffer = ByteBuffer.allocate(cache);  
  47.             // 每次读取的内容  
  48.             byte[] bs = new byte[cache];  
  49.             // 缓存  
  50.             byte[] tempBs = new byte[0];  
  51.             String line = "";  
  52.             while (fcin.read(rBuffer) != -1) {  
  53.                 int rSize = rBuffer.position();  
  54.                 rBuffer.rewind();  
  55.                 rBuffer.get(bs);  
  56.                 rBuffer.clear();  
  57.                 byte[] newStrByte = bs;  
  58.                 // 如果发现有上次未读完的缓存,则将它加到当前读取的内容前面  
  59.                 if (null != tempBs) {  
  60.                     int tL = tempBs.length;  
  61.                     newStrByte = new byte[rSize + tL];  
  62.                     System.arraycopy(tempBs, 0, newStrByte, 0, tL);  
  63.                     System.arraycopy(bs, 0, newStrByte, tL, rSize);  
  64.                 }  
  65.                 // 获取开始位置之后的第一个换行符  
  66.                 int endIndex = indexOf(newStrByte, 0);  
  67.                 if (endIndex != -1) {  
  68.                     return startNum + endIndex;  
  69.                 }  
  70.                 tempBs = substring(newStrByte, 0, newStrByte.length);  
  71.                 startNum += 1024;  
  72.             }  
  73.         } catch (Exception e) {  
  74.             e.printStackTrace();  
  75.         } finally {  
  76.             fcin.close();  
  77.         }  
  78.         return position;  
  79.     }  
  80.   
  81.     /** 
  82.      * 从设置的开始位置读取文件,一直到结束为止。如果 end设置为负数,刚读取到文件末尾 
  83.      * @param fullPath 
  84.      * @param start 
  85.      * @param end 
  86.      * @throws Exception 
  87.      */  
  88.     public void readFileByLine(String fullPath, long start, long end) throws Exception {  
  89.         File fin = new File(fullPath);  
  90.         if (fin.exists()) {  
  91.             FileChannel fcin = new RandomAccessFile(fin, "r").getChannel();  
  92.             fcin.position(start);  
  93.             try {  
  94.                 ByteBuffer rBuffer = ByteBuffer.allocate(bufSize);  
  95.                 // 每次读取的内容  
  96.                 byte[] bs = new byte[bufSize];  
  97.                 // 缓存  
  98.                 byte[] tempBs = new byte[0];  
  99.                 String line = "";  
  100.                 // 当前读取文件位置  
  101.                 long nowCur = start;  
  102.                 while (fcin.read(rBuffer) != -1) {  
  103.                     nowCur += bufSize;  
  104.   
  105.                     int rSize = rBuffer.position();  
  106.                     rBuffer.rewind();  
  107.                     rBuffer.get(bs);  
  108.                     rBuffer.clear();  
  109.                     byte[] newStrByte = bs;  
  110.                     // 如果发现有上次未读完的缓存,则将它加到当前读取的内容前面  
  111.                     if (null != tempBs) {  
  112.                         int tL = tempBs.length;  
  113.                         newStrByte = new byte[rSize + tL];  
  114.                         System.arraycopy(tempBs, 0, newStrByte, 0, tL);  
  115.                         System.arraycopy(bs, 0, newStrByte, tL, rSize);  
  116.                     }  
  117.                     // 是否已经读到最后一位  
  118.                     boolean isEnd = false;  
  119.                     // 如果当前读取的位数已经比设置的结束位置大的时候,将读取的内容截取到设置的结束位置  
  120.                     if (end > 0 && nowCur > end) {  
  121.                         // 缓存长度 - 当前已经读取位数 - 最后位数  
  122.                         int l = newStrByte.length - (int) (nowCur - end);  
  123.                         newStrByte = substring(newStrByte, 0, l);  
  124.                         isEnd = true;  
  125.                     }  
  126.                     int fromIndex = 0;  
  127.                     int endIndex = 0;  
  128.                     // 每次读一行内容,以 key(默认为\n) 作为结束符  
  129.                     while ((endIndex = indexOf(newStrByte, fromIndex)) != -1) {  
  130.                         byte[] bLine = substring(newStrByte, fromIndex, endIndex);  
  131.                         line = new String(bLine, 0, bLine.length, encode);  
  132.                         lineNum++;  
  133.                         // 输出一行内容,处理方式由调用方提供  
  134.                         readerListener.outLine(line.trim(), lineNum, false);  
  135.                         fromIndex = endIndex + 1;  
  136.                     }  
  137.                     // 将未读取完成的内容放到缓存中  
  138.                     tempBs = substring(newStrByte, fromIndex, newStrByte.length);  
  139.                     if (isEnd) {  
  140.                         break;  
  141.                     }  
  142.                 }  
  143.                 // 将剩下的最后内容作为一行,输出,并指明这是最后一行  
  144.                 String lineStr = new String(tempBs, 0, tempBs.length, encode);  
  145.                 readerListener.outLine(lineStr.trim(), lineNum, true);  
  146.             } catch (Exception e) {  
  147.                 e.printStackTrace();  
  148.             } finally {  
  149.                 fcin.close();  
  150.             }  
  151.   
  152.         } else {  
  153.             throw new FileNotFoundException("没有找到文件:" + fullPath);  
  154.         }  
  155.         // 通知观察者,当前工作已经完成  
  156.         setChanged();  
  157.         notifyObservers(start+"-"+end);  
  158.     }  
  159.   
  160.     /** 
  161.      * 查找一个byte[]从指定位置之后的一个换行符位置 
  162.      * 
  163.      * @param src 
  164.      * @param fromIndex 
  165.      * @return 
  166.      * @throws Exception 
  167.      */  
  168.     private int indexOf(byte[] src, int fromIndex) throws Exception {  
  169.   
  170.         for (int i = fromIndex; i < src.length; i++) {  
  171.             if (src[i] == key) {  
  172.                 return i;  
  173.             }  
  174.         }  
  175.         return -1;  
  176.     }  
  177.   
  178.     /** 
  179.      * 从指定开始位置读取一个byte[]直到指定结束位置为止生成一个全新的byte[] 
  180.      * 
  181.      * @param src 
  182.      * @param fromIndex 
  183.      * @param endIndex 
  184.      * @return 
  185.      * @throws Exception 
  186.      */  
  187.     private byte[] substring(byte[] src, int fromIndex, int endIndex) throws Exception {  
  188.         int size = endIndex - fromIndex;  
  189.         byte[] ret = new byte[size];  
  190.         System.arraycopy(src, fromIndex, ret, 0, size);  
  191.         return ret;  
  192.     }  
  193.   
  194. }  

读文件线程

[java]  view plain copy
  1. /** 
  2.  * Created with IntelliJ IDEA. 
  3.  * User: okey 
  4.  * Date: 14-4-2 
  5.  * Time: 下午4:50 
  6.  * To change this template use File | Settings | File Templates. 
  7.  */  
  8. public class ReadFileThread extends Thread {  
  9.   
  10.     private ReaderFileListener processPoiDataListeners;  
  11.     private String filePath;  
  12.     private long start;  
  13.     private long end;  
  14.   
  15.     public ReadFileThread(ReaderFileListener processPoiDataListeners,long start,long end,String file) {  
  16.         this.setName(this.getName()+"-ReadFileThread");  
  17.         this.start = start;  
  18.         this.end = end;  
  19.         this.filePath = file;  
  20.         this.processPoiDataListeners = processPoiDataListeners;  
  21.     }  
  22.   
  23.     @Override  
  24.     public void run() {  
  25.         ReadFile readFile = new ReadFile();  
  26.         readFile.setReaderListener(processPoiDataListeners);  
  27.         readFile.setEncode(processPoiDataListeners.getEncode());  
  28. //        readFile.addObserver();  
  29.         try {  
  30.             readFile.readFileByLine(filePath, start, end + 1);  
  31.         } catch (Exception e) {  
  32.             e.printStackTrace();  
  33.         }  
  34.     }  
  35. }  

具体业务逻辑监听

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


线程调度

[java]  view plain copy
  1. import java.io.File;  
  2. import java.io.FileInputStream;  
  3. import java.io.IOException;  
  4.   
  5. /** 
  6.  * Created with IntelliJ IDEA. 
  7.  * User: okey 
  8.  * Date: 14-4-1 
  9.  * Time: 下午6:03 
  10.  * To change this template use File | Settings | File Templates. 
  11.  */  
  12. public class BuildData {  
  13.     public static void main(String[] args) throws Exception {  
  14.         File file = new File("E:\\1396341974289.csv");  
  15.         FileInputStream fis = null;  
  16.         try {  
  17.             ReadFile readFile = new ReadFile();  
  18.             fis = new FileInputStream(file);  
  19.             int available = fis.available();  
  20.             int maxThreadNum = 50;  
  21.             // 线程粗略开始位置  
  22.             int i = available / maxThreadNum;  
  23.             for (int j = 0; j < maxThreadNum; j++) {  
  24.                 // 计算精确开始位置  
  25.                 long startNum = j == 0 ? 0 : readFile.getStartNum(file, i * j);  
  26.                 long endNum = j + 1 < maxThreadNum ? readFile.getStartNum(file, i * (j + 1)) : -2;  
  27.                 // 具体监听实现  
  28.                 ProcessDataByPostgisListeners listeners = new ProcessDataByPostgisListeners("gbk");  
  29.                 new ReadFileThread(listeners, startNum, endNum, file.getPath()).start();  
  30.             }  
  31.         } catch (IOException e) {  
  32.             e.printStackTrace();  
  33.         } catch (Exception e) {  
  34.             e.printStackTrace();  
  35.         }  
  36.     }  
  37. }  

你可能感兴趣的:(JAVA使用多线程读取超大文件)