本文介绍 Java多线程读取大文件性能提升的高效方案。
前沿
我们在读取一个正常文件的时候,将使用,BufferedReader.read() 的三种方法:
BufferedReader.read() 单字节,这个效率最低,基本不考了
BufferedReader.read(char[] cbuf) 根据传入 cbuf 的长度,顺序读取。此方法如果单线程的时候,可以考虑,显然多线程不行。
BufferedReader.read(char[] cbuf, int off, int len) 本文中多线程高效读取,需要用到,因为大可以分段读取呀,有个 off 和 len 伙伴们,就是他了!!!
那么我们就来实现把。在实习前我仍然需要在强调两点:
1、InputStream 是个阻塞顺序读取的流对象,也就意味着如果使用多线程是,创建一个全局 InputStream 然后分别传给不同的子线程,且在子线程中进行分段读取,那么恭喜你你中奖了,你会得到很郁闷的结果。结果就是子线程将不能正常分段获取!!!
2、BufferedReader.read(char[] cbuf, int off, int len) 中
cbuf 为缓冲数组
off 为保存字符的开始位置
len 一次最多读入字符个数
说明:这里的 off 是个比较郁闷的字段,什么是保存字符的开始位置,笔者在这里进行了验证,当我把 off 以 0传入的时候正常,以 1 传入想从第1个字符截取,也就是线程分段截取的时候,异常就来了。这就麻烦了,如果off 并不是理解上的起始读取位置,那么怎么用多线程分段读取呢???莫慌,且看这里:reader.skip(this.start); 用这个,伙伴们看到这个你们就成功了。
3、伙伴们请注意看,本文中采用的多线程的模式,是假定并不知道文本总长度的情况下进行多线程读取,假定:特殊场景不知道文件总长度!
接下来,话不多说,上代码:
package com.pft.views.webservices.readutil;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
public class ReadUtil {
private int theadNum = 6 /* 线程数 */, timeReadLine = 2048 /* 子线程一次循环读取字节长度 */, timeForTime = 5; /* 子线程一次循环读取数 */
private StringBuffer sbf = new StringBuffer();
public static void main(String[] args) {
long start = System.currentTimeMillis();
ReadUtil rUtil = new ReadUtil();
rUtil.read(new File("D:\\dslrcv.txt"), 0);
System.out.println("获取字符总长度:" + rUtil.getSbf().length() + ". 总耗时:" +(System.currentTimeMillis() - start) + "毫秒!");
}
/**
* 假定:特殊场景不知道文件总长度! 情景下的 递归+多线程 模式。 可通过File得到总长度,此时可更好的进行子线程数的分配和控制。
* @param file 文件
* @param start 其实位置
*/
public void read(File file, int start){
List<ReadItem> list = new ArrayList<ReadUtil.ReadItem>();
for(int i = 0; i < theadNum; i++){
list.add(new ReadItem(file, start + i*timeReadLine*timeForTime)); //创建多个子线程
list.get(list.size()-1).start();
}
for(int i = 0; i < list.size(); i++){
try {
list.get(i).join();
sbf.append(list.get(i).getSb());
} catch (Exception e) {
e.printStackTrace();
}
}
if(list.get(list.size() - 1).getLastNum() == timeReadLine){
read(file, start + theadNum * timeReadLine * timeForTime);
}
}
public StringBuffer getSbf() {
return sbf;
}
/**
* 子线程
* @author James
*/
public class ReadItem extends Thread{
private BufferedReader reader;
private int start, lastNum;
private StringBuffer sb;
public ReadItem(File file, int start) {
try {
this.reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
} catch (Exception e) {
e.printStackTrace();
}
this.start = start;
this.sb = new StringBuffer();
}
@Override
public void run() {
char[] buf = new char[timeReadLine];
try {
reader.skip(this.start);
for (int i = 0; i < timeForTime && (lastNum = reader.read(buf)) != -1; i++ ) {
sb.append(new String(buf,0,lastNum));
}
} catch (Exception e) {
e.printStackTrace();
}
}
public int getLastNum() {
return lastNum;
}
public StringBuffer getSb() {
return sb;
}
}
}