队列多线程读取文件内容
为什么采用多线程队列的方式呢?主要是如果文件一行一行读取处理的话,页面上传文件后,需要等待服务器响应。如果文件的内容数据量很大的话,页面就一直等待服务器响应,毕竟服务器处理这些数据是花费很多时间的,我们都知道,页面是有响应时间的,一旦响应超时了,程序就终止了。所以,当我们需要处理大批的文件时,可以采用队列多线程的方式异步去处理,页面的响应时间只是上传文件的时间,一般在几秒之内吧。文件的数据一次性放到队列中,多个线程异步同时处理队列中的数据。
1. 场景:文件大小不是很大(十几兆以内),读取文件的内容。
方案:文件直接放到内存中,一行一行读取处理
在这里页面上传的我采用的是ajaxFileUpload插件,异步请求后台。当然,也可以用form表单的形式。
页面:
<input type="text"name="name"/>
<input id="fileToUpload"type="file" name="file"οnchange="selectFile(this);" style="margin-left: 50px;"/>
<button οnclick="ajaxFileUpload()">上传</button>
<script type="text/javascript">
functionajaxFileUpload() {
varfile = $("input[name='file']").val();
if(typeof(file)=="undefined"||$.trim(file).length==0) {
alert("请上传文件!");
return;
}
$.ajaxFileUpload({
url : '/upload',
type: 'post',
secureuri : false,
fileElementId : 'fileToUpload',//input 的id
dataType : 'json',
data : {
"name": name//这里随便传递了name,毕竟我们上传文件,还需要其他的一些数据
},
success : function(data) {
//这里根据后台的返回数据进行处理
},
error : function(data, status, e) {
alert("上传发生异常");
}
})
}
// 判断上传文件类型,该处设置的只允许上传txt的文件
functionselectFile(file) {
varfileName = file.value;
varmime = fileName.toLowerCase().substr(fileName.lastIndexOf("."));
if(mime!=".txt"){
alert("该文件不是txt文件,请重新选择!");
file.outerHTML= file.outerHTML;
}
}
</script>
后台:Controller
@SuppressWarnings("unchecked")
@ResponseBody
@RequestMapping(value = "/upload", method =RequestMethod.POST)
public voidfileUpload(HttpServletRequest request, HttpServletResponse response) throwsException{
Map<String,Object> result = newHashMap<String,Object>();
result.put("success", false);
//使用Apache文件上传组件处理文件上传步骤:
//1、创建一个DiskFileItemFactory工厂
DiskFileItemFactoryfactory = new DiskFileItemFactory();
//2、创建一个文件上传解析器
ServletFileUploadupload = new ServletFileUpload(factory);
upload.setSizeMax(50*1024*1024);//设置该次上传最大值为50M
List<FileItem>list = upload.parseRequest(request);
//注意:由于输入流只能读取一次,这里声明了两个文件流,inputStream用作文件读取处理,inputStreamCount用作计算文件行数,动态定义队列大小
InputStreaminputStream = null;
InputStreaminputStreamCount = null;
Stringname = null;
for(FileItemfileItem : list){
//判断某项是否是普通的表单类型,否则该表单项是file 类型的
if(fileItem.isFormField()){
if(fileItem.getFieldName().equals("name")){
name= fileItem.getString("UTF-8");
}
}else{
//用作-统计文件行数
inputStreamCount= fileItem.getInputStream();
inputStream= fileItem.getInputStream();
break;
}
}
if(inputStream== null){
log.error("文件为空!");
result.put("errorMsg","文件为空!");
ResponseUtil.writeGson(response,result);
return;
}
BufferedReader readerLineCount =new BufferedReader(newInputStreamReader(inputStreamCount));
//声明一个记录文件行数的变量
int lineCount = 0;
while(readerLineCount.readLine() !=null)//获取文件行数
{
lineCount++;
}
readerLineCount.close();
System.out.println("文件行数:"+lineCount);
//动态创建队列
int DEFAULT_IO_THREADS = 10;
BlockingQueue<Runnable> queue = newArrayBlockingQueue<Runnable>(lineCount);
ThreadPoolExecutor executor = newThreadPoolExecutor(DEFAULT_IO_THREADS,
DEFAULT_IO_THREADS*2,1, TimeUnit.HOURS, queue, new ThreadPoolExecutor.CallerRunsPolicy());
String line = null;
int i =0;
BufferedReader reader =newBufferedReader(new InputStreamReader(inputStream));
while(StringUtils.isNotEmpty(line = reader.readLine())) {
//处理每一行的数据
send(executor,line);
i++;
}
result.put("count",i);
result.put("success", true);
reader.close();
ResponseUtil.writeGson(response, result);
}
@SuppressWarnings("unused")
privatevoid send(ThreadPoolExecutor executor, String line) {
final String final_ String line=line;
executor.submit(newRunnable() {
publicvoid run() {
//在这里写读取到的每一行的处理逻辑
}
});
}
2. 场景:文件大(几十兆甚至更大),读取文件的内容。
方案:文件不能直接放到内存中,会消耗大量的内存。采用上传至服务器临时文件夹中,从文件中一行一行读取处理
页面跟例子1是一样的,主要是后台的处理方式不一样
后台:Controller
使用MultipartFilem需要配置
<!-- 处理文件上传 -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8" /> <!-- 编码 -->
<property name="maxInMemorySize" value="10240" /> <!-- 上传时占用最大内存大小 (10240) -->
<property name="uploadTempDir" value="/uploadTemp/" /> <!-- 上传临时保存目录名 ,带文件上传完成之后会自动删除保存的文件-->
<property name="maxUploadSize" value="-1" /> <!-- 最大文件大小,-1为无限止(-1) -->
</bean>
@ResponseBody
@RequestMapping(value = "/upload", method =RequestMethod.POST)
public void fileUpload(@RequestParam("file") MultipartFilemultipartFile ,HttpServletRequest request, HttpServletResponse response) throwsException{
Date startTime = new Date();
Map<String,Object> result =new HashMap<String,Object>();
result.put("success",false);
if(multipartFile.isEmpty()){
log.error("文件为空!");
result.put("errorMsg","文件为空!");
ResponseUtil.writeGson(response,result);
return;
}
String name =request.getParameter("name");//这种方式下,参数可以直接这么取
// 获取上传的文件保存的路径
String path = request.getSession().getServletContext().getRealPath("uploadTemp");
//如果目录不存在,创建文件夹
if (!new File(path).exists()){
newFile(path).mkdirs();
}
// 获取上传的文件的名称
String filename =multipartFile.getOriginalFilename();
//文件上传至指定目录下
File targetFile = new File(path,filename);
multipartFile.transferTo(targetFile);//上传
//计算文件行数(字符流 速度最快)
//声明一个BufferReader对象
BufferedReader bReader = null;
//用字符流对象实例化声明的BufferReader对象
bReader = new BufferedReader(newFileReader(targetFile));
//声明一个记录文件行数的变量
int lineCount = 0;
//声明一个保存文件每行数据的String变量
while(bReader.readLine() != null)//获取文件行数
{
lineCount++;
}
System.out.println("文件行数:"+lineCount);
bReader.close(); //进行字符串与浮点类型数据转换时,先关闭刚操作的文件
int DEFAULT_IO_THREADS = 10;
BlockingQueue<Runnable> queue= new ArrayBlockingQueue<Runnable>(lineCount);
ThreadPoolExecutor executor = newThreadPoolExecutor(DEFAULT_IO_THREADS,
DEFAULT_IO_THREADS*2,1, TimeUnit.HOURS, queue, new ThreadPoolExecutor.CallerRunsPolicy());
//通过行迭代,遍历文件的每一行,而不是把所有行都放在内存中 ,处理完之后把它扔掉。
LineIterator it =FileUtils.lineIterator(targetFile, "UTF-8");
int i=0;
try {
while (it.hasNext()) {
String line = it.nextLine();
long userId = Long.valueOf(line);
send(executor,line);
i++;
}
result.put("count", i);
result.put("success", true);
Date endTime = new Date();
System.out.println("耗时:"+(endTime.getTime()-startTime.getTime())+"毫秒");
//删除临时文件
LineIterator.closeQuietly(it);
targetFile.delete();
ResponseUtil.writeGson(response, result);
} catch (Exception e){
result.put("errorMsg",e.getMessage());
ResponseUtil.writeGson(response,result);
}
finally {
LineIterator.closeQuietly(it);
}
}
@SuppressWarnings("unused")
privatevoid send(ThreadPoolExecutor executor,String line) {
final String line = line;
executor.submit(newRunnable() {
publicvoid run() {
//在这里写处理逻辑
}
});
}
补充:像这种文件上传的,一定要防止重复提交。所以,防重复提交必须防止。
方案: 1.前端,js点击提交后,立刻移除点击事件。
2.后端,加锁。private Map<String, String> objLock = new ConcurrentHashMap<String, String>();