javaweb之文件上传,下载

 

目录

 

文件上传:允许客户将本地文件,上传到服务器端

文件上传原理:

一、文件上传编程

Apache commons-fileupload 使用

commons-fileupload 核心API 分析

上传文件注意问题

上传文件的进度监控

上传时注意事项:

二 .文件下载

下载案例:指定一个磁盘目录,通过树形结构遍历,遍历磁盘目录下及其子目录中文体 ,提供下载


 

文件上传:允许客户将本地文件,上传到服务器端

 
应用:上传照片、上传新闻图片、上传附件
 

文件上传原理:

  通过为表单元素设置Method=“post” enctype=“multipart/form-data”属性,让表单提交的数据以二进制编码的方式提交,在接收此请求的Servlet中用二进制流来获取内容,就可以取到上传文件的内容,从而实现文件的上传。

表单enctype属性  

  • application/x-www-form-urlencoded 这是默认编码方式,它只处理表单域里的value属性值,采用这种编码方式的表单会将表单域里的值处理成URL编码方式
  • multipart/form-data 这种编码方式的表单会以二进制流的方式来处理表单数据,这种编码方式会把文件域指定文件的内容也封装到请求参数里。
  • text/plain 这种方式主要适用于直接通过表单发送邮件的方式
 

一、文件上传编程

 

1、在用户页面中添加上传输入项 (客户端页面操作)
 
<input type="file" />
注意事项:
  • 1) 必须为文件上传input 提供name属性,否则文件上传内容不会被表单提交
  • 2) 表单的提交是post (get提交数据在url地址上显示,有长度限制)
  • 3) 设置enctype=multipart 使得文件上传编码 ----- MIME编码格式
 
2、在服务器端编写文件上传程序
 
通过request.getInputStream分析文件上传原理
 
常用文件上传API :
  • 1) JSP独立开发年代 jsp-smartupload ---- JSP Model1
  • jspSmartUpload是一个可免费使用的全功能的文件上传下载组件,适于嵌入执行上传下载操作的JSP文件中。
  • 2) JSP+Servlet 开发web应用 Apache commons-fileupload ---- JSP Model2
  • FileUpload 是 Apache commons下面的一个子项目,用来实现Java环境下面的文件上传功能,与常见的SmartUpload齐名。
  • 3) Servlet3.0规范中提供对文件上传的支持

Apache commons-fileupload 使用

  • 1) 去 http://commons.apache.org/fileupload/ 下载fileupload jar包同时下载 commons-fileupload 和 commons-io 两个包 -------- fileupload依赖io包
  • 2) 将jar包导入 web 工程WEB-INF/lib下
  • 3) 编程实现
  1. 步骤一:获得DiskFileItemFactory 文件项工厂
  2. 步骤二:通过工厂 获得文件上传请求核心解析类 ServletFileUpload
  3. 步骤三:使用ServletFileUpload对request进行解析 ---- 获得很多个FileItem
  4. 步骤四:对每个FileItem进行操作 判断FileItem是不是普通字段 isFormField
代表普通字段FileItem
  • getFieldName(); ---- 获得表单项name属性
  • getString(); ----- 获得表单项value
 
代表文件上传FileItem
  • getInputStream() --- 获得文件内容输入流
  • getName() ------ 获得上传文件名称
 
问题:早期IE6 浏览器提交,上传文件时,请求中存放是客户端完整路径 ----- 在服务器端保存文件时,需要切掉客户端路径,只保留文件名
int index = filename.lastIndexOf("\\");
if (index != -1) {
filename = filename.substring(index + 1);// 获得真实文件名
}

commons-fileupload 核心API 分析

 

1、DiskFileItemFactory 磁盘文件项工厂类
 
  • public DiskFileItemFactory(int sizeThreshold, java.io.File repository) 构造工厂时,指定内存缓冲区大小和临时文件存放位置
  • public void setSizeThreshold(int sizeThreshold) 设置内存缓冲区大小,默认10K
  • public void setRepository(java.io.File repository)设置临时文件存放位置,默认System.getProperty("java.io.tmpdir").
  • 内存缓冲区: 上传文件时,上传文件的内容优先保存在内存缓冲区中,当上传文件大小超过缓冲区大小,就会在服务器端产生临时文件
  • 临时文件存放位置: 保存超过了内存缓冲区大小上传文件而产生临时文件产生临时文件可以通过 FileItem的delete方法删除
 
2、ServletFileUpload 文件上传核心类
 
  • static boolean isMultipartContent(javax.servlet.http.HttpServletRequest request) 判断request的编码方式是否为multipart/form-data
  • java.util.List parseRequest(javax.servlet.http.HttpServletRequest request) 解析request,将请求体每个部分封装FileItem对象,返回List<FileItem>
  • void setFileSizeMax(long fileSizeMax) 设置单个文件上传大小 和 void setSizeMax(long sizeMax) 设置总文件上传大小
  • void setHeaderEncoding(java.lang.String encoding) 设置编码集 解决上传文件名乱码 
  • void setProgressListener(ProgressListener pListener) 设置文件上传监听器 (用来监控文件上传进度)上传时间、剩余大小、速度、剩余时间
3、FileItem 表示文件上传表单中 每个数据部分
 
  • boolean isFormField() 判断该数据项是否为文件上传项,true 不是文件上传 false 是文件上传
if(fileItem.isFormField()){
// 不是上传项
//java.lang.String getFieldName() 获得普通表单项name属性
//java.lang.String getString() / java.lang.String getString(java.lang.String encoding) 获得普通表单项value属性 传入编码集用来解决输入value乱码
}else{
// 是上传项
//java.lang.String getName() 获得上传文件名 (注意IE6存在路径)
//java.io.InputStream getInputStream() 获得上传文件内容输入流
// 上传文件
//void delete() 删除临时文件(删除时,必须要管理输入输出流)
}

注意事项:因为文件上传表单采用编码方式multipart/form-data 与传统url编码不同,所有getParameter 方法不能使用 setCharacterEncoding 无法解决输入项乱码问题

 
 

上传文件注意问题

 

1、上传文件后,在服务器端保存位置
 
  • 第一类存放位置:直接存放WebRoot目录下 和 除WEB-INF META-INF的其它子目录下 例如: WebRoot/upload客户端可以直接在浏览器上通过url访问位置(资料无需通过权限控制,而可以直接访问) ---- 对上传资源安全性要求不高、或者资源需要用户直接可见例如:购物商城商品图片
  • 第二类存放位置:放入WEB-INF及其子目录 或者 不受tomcat服务器管理目录 例如: WebRoot/WEB-INF/upload 、c:\ 、d:\abc 客户端无法通过URL直接访问,必须由服务器内部程序才能读取 (安全性较高,可以很容易添加权限控制)例如:会员制在线视频
 
2、上传文件在同一个目录重名问题
 
如果文件重名,后上传文件就会覆盖先上传文件文件名 UUID
filename = UUID.randomUUID().toString() + "_" + filename;
 
3、为了防止同一个目录下方上传文件数量过多 ---- 必须采用目录分离算法
 
  • 1) 按照上传时间进行目录分离 (周、月 )
  • 2) 按照上传用户进行目录分离 ----- 为每个用户建立单独目录
  • 3) 按照固定数量进行目录分离 ------ 假设每个目录只能存放3000个文件 ,每当一个目录存满3000个文件后,创建一个新的目录
  • 4) 按照唯一文件名的hashcode 进行目录分离
public static String generateRandomDir(String uuidFileName) {
// 获得唯一文件名的hashcode
int hashcode = uuidFileName.hashCode();
// 获得一级目录
int d1 = hashcode & 0xf;
// 获得二级目录
int d2 = (hashcode >>> 4) & 0xf;

return "/" + d2 + "/" + d1;// 共有256目录
}

4、乱码问题

  • 普通编写项 value属性乱码 ------------- fileItem.getString(编码集);
  • 上传文件项 文件名乱码 --------- fileupload.setHeaderEncoding(编码集);

上传文件的进度监控

 

  • ServletFileUpload 类 提供 public void setProgressListener(ProgressListener pListener)
  •  为文件上传程序绑定一个监听器对象,通过监听器可以监听文件上传全过程和AJAX技术结合,编写文件上传进度条
  • 设置监听器,文件上传程序会自动执行 监听器中 update方法 public void update(long pBytesRead, long pContentLength, int pItems)
在方法中可以获得 文件总大小、已经上传大小和 上传第几个元素
 
能否根据上面三个参数计算:剩余大小、传输速度、已用时间、剩余时间
  • 1) 已用时间 = 当前时间 - 开始时间
  • 2) 速度 = 已经上传大小/已用时间
  • 3) 剩余大小 = 总大小- 已经上传大小
  • 4) 剩余时间 = 剩余大小/速度

上传时注意事项:

 

  • 表单必须为post提交
  • 文件的表单必须有name属性,只有有name属性才会进行提交
  • 需要设置enctype属性值为:multipart/form-data
  • 在servlet中将上传的文件保存在服务器的硬盘中
  • 文件上传时:需要server配置以下的方式1.server右键-->open,然后在server Locations
  • 选择use Tomact installation(takes control of Tomact installtion)
  • 配置deploy path:webapps;然后就可以在tomact文件夹下就可以看到这个web应用;
  • upload temp文件夹都应放到web-INF目录下,防止上传入侵和访问其他用户资源
  • 文件名要拼接uuid保证唯一
  • 文件要分目录储存保证同一目录下不要有过多的文件要用分目录实现hash值来用
UUID:用于生成一个独一无二的数码,
String uuidfilename=UUID.randomUUID().toString()+"_"+filename;
分目录实现://设置一个独一无二的文件名
String uuidfilename=UUID.randomUUID().toString()+"_"+filename;
//转换为hash值
int hash=uuidfilename.hashCode();
//转化为hash字符串
String hashstr=Integer.toHexString(hash);
char[] hss=hashstr.toCharArray();
String path=this.getServletContext().getRealPath("upload");
for(char c:hss){
path+="/"+c;
}
new File(path).mkdirs();
InputStream inputStream=item.getInputStream();

OutputStream outputStream=new FileOutputStream(new File(path,uuidfilename));

实现文件上传监听器:上传进度;

ProgressListener progressListener = new ProgressListener() {
public void update(long pBytesRead, long pContentLength, int pItems) {

System.out.println("到现在为止, " + pBytesRead + " 字节已上传,总大小为 "
+ pContentLength);
}
};
  • upload.setProgressListener(progressListener);问件上传时的bug:
设置上传监听进度时这个监听器源码导入时出现问题,不能放入lib文件下tomact启动不起来解决问题把源码source文件删除即可。
fileUpload.setProgressListener(new ProgressListener() {
//
//已经读了,总共多少 ,读到第几个了
@Override
public void update(long pBytesRead, long pContentLength, int pItems) {
// TODO Auto-generated method stub
//转换为kb
double br=pBytesRead*1.0/1024;
double cl=pContentLength*1.0/1024;
System.out.println("已读取"+pBytesRead+"当前读到"+pItems+"总共:"+pContentLength);
}
});

 

二 .文件下载

 
将服务器端文件下载到客户端
 
常见文件下载有两种编写方式
 
  • 1、超链接直接指向下载资源:如果文件格式浏览器识别,将直接打开文件,显示在浏览器上, 如果文件格式浏览器不识别,将弹出下载窗口对于浏览器识别格式的文件,通过另存为进行下载客户端访问服务器静态资源文件时,静态资源文件是通过 缺省Servlet返回的,在tomcat配置文件conf/web.xml 找到 --- org.apache.catalina.servlets.DefaultServlet
  • 2、编写服务器程序,读取服务器端文件,完成下载必须设置两个头信息 ,来自MIME协议 Content-Type Content-Disposition,response.setContentType(getServletContext().getMimeType(filename));response.setHeader("Content-Disposition", "attachment;filename=" + filename); // 以附件形式打开,不管格式浏览器是否识别
 

下载案例:指定一个磁盘目录,通过树形结构遍历,遍历磁盘目录下及其子目录中文体 ,提供下载

 
* 遍历一个树形目录结构中所有文件
 
  • 1、广度非递归 遍历目录中所有文件
  • 2、使用get方式提交中文时
  1. <a href="/day21/downloadList?path=D:\TTPmusic\何晟铭\何晟铭 - 爱的供养.mp3">何晟铭 - 爱的供养.mp3</a><br/>
  2. 问题:IE6 提交后,服务器经过get乱码处理获得
  3. 乱码原因:IE6对中文直接进行 get提交时,进行URL编码 ---- 编码发生问题解决:手动对get提交中文进行编码 ----- URLEncoder
  • 3、如果下载文件是中文名,设置 response.setHeader("Content-Disposition", "attachment;filename=" + filename); 出现附件名乱码
不同浏览器处理下载附件名乱码 处理方式不同 ,例如 IE使用URL编码 、FF使用 BASE64编码
 
通过USER-AGENT 请求头信息字段,判断来访者浏览器类型
 
问题:火狐浏览器 在使用MimeUtility 进行Base64编码 时存在问题 ,如果字符串中没有中文,无法进行编码
 
解决:采用手动BASE64 编码
 
BASE64Encoder base64Encoder = new BASE64Encoder();
filename = "=?utf-8?B?" + base64Encoder.encode(filename.getBytes("utf-8")) + "?=";
 
上传的代码块如下:
public class FileUtil {
	//储存表单信息
	private Map parameters=null;
	//储存文件上传的信息
	private Map file=null;
	//最大的上传文件的大小
	private long max_size=30*1024*1024;
	
	public FileUtil(){
		parameters=new HashMap<>();
		file=new HashMap<>();
	}
	//问件上传;
	public int upload(HttpServletRequest request,String uploadPath) throws IOException{
		//实例化一个硬盘文件,用来配置上传的组件servletFileUpload
		//实例一个硬盘文件工厂用来配置上传组件servletFileUpload
		DiskFileItemFactory factory=new DiskFileItemFactory();
		//设置缓冲区的大小4kb
		factory.setSizeThreshold(4096);
		//实例化上传组件
		ServletFileUpload upload=new ServletFileUpload(factory);
		//设置上传的文件的尺寸
		upload.setSizeMax(max_size);
		//解决路径的 文件名的乱码问题
		upload.setHeaderEncoding("UTF-8");
		List  filelist=null;
		try {
			//获取request对象
			//把request转成FileItem的实例
			filelist=upload.parseRequest(request);
			
		} catch (FileUploadException e) {
			// TODO Auto-generated catch block
			//判断是否为这个异常的一个:你属于该类吗?或者你属于该类的派生类吗?
			if (e instanceof SizeLimitExceededException) {
				System.out.println("文件大小超过"+max_size+"字节");
				return 0;
			}
			e.printStackTrace();
		}
		Iterator fIterator=filelist.iterator();
		while(fIterator.hasNext()){
			FileItem fileItem=null;//
			String sourceFilePath=null;//文件的路径
			String sourceFileName=null;//问件的完整名字
			String fileExt=null;//文件的扩展名
			String filePath=null;
			String realPath=null;
			long size=0;
			fileItem=(FileItem)fIterator.next();
			//如果问价中是上传问价而不是表单信息
			if(!fileItem.isFormField()){
				//得到文件的完整路径
				sourceFilePath=fileItem.getName();
				size=fileItem.getSize();
				if (!sourceFilePath.equals("") && size!=0) {
					//得到去除路径的文件名
					sourceFileName=sourceFilePath.substring(sourceFilePath.lastIndexOf("\\")+1);
					//得到文件的扩展名
					fileExt=sourceFileName.substring(sourceFileName.lastIndexOf(".")+1);
					//以当前系统时间保存上传文件
					long systemTime=System.currentTimeMillis();
					//
					filePath=uploadPath+"/"+systemTime+"."+fileExt;
					//真实路径
					realPath=request.getServletContext().getRealPath(filePath);
					try{
						fileItem.write(new File(realPath));
					}catch(Exception e){
						e.printStackTrace();
						return 0;
					}
					file.put("size", String.valueOf(size));
					file.put("filePath", filePath);
					file.put("filename", sourceFileName);
					
				}
			}else{
				//如果不是上传的文件而是表单信息,则将信息保存在paramters中
				String fileldName=fileItem.getFieldName();
				String value=fileItem.getString("UTF-8");
				parameters.put(fileldName, value);
			}
			
		}
		return 1;
	}
	//下载文件的方法,参数Map储存下载文件的信息,包括文件的地址filePath和名称的fileName
	public int download(ServletContext servletContext,HttpServletResponse response,Map file) throws IOException{
		BufferedInputStream bis=null;
		BufferedOutputStream bos=null;
		try{
			String filePath=(String)file.get("filePath");
			String realPath=servletContext.getRealPath(filePath);
			long fileLength=new File(realPath).length();
			response.setHeader("Content-dispostion", "attachment;filename="+new String(((String)file.get("fileName")).getBytes("UTF-8"),"ISO8859-1"));
			bis=new BufferedInputStream(new FileInputStream(realPath));
			bos=new BufferedOutputStream(response.getOutputStream());
			byte[] buff=new byte[2048];
			int bytesRead;
			while((bytesRead=bis.read(buff,0,buff.length))!=-1){
				bos.write(buff,0,bytesRead);
			}
		}catch(Exception e){
			return 0;
		}finally {
			if (bis!=null) {
				bis.close();
			}if (bos!=null) {
				bos.close();
			}
			
		}
		return 1;
	}
	public Map getFile() {
		return file;
		
	}
	public Map getParameters(){
		return parameters;
	}
	public void setMax_size(long max_size){
		this.max_size=max_size;
	}
	
}
文件上传
//通过工厂类来实现
		DiskFileItemFactory factory=new DiskFileItemFactory();
		//设置缓冲区大小
		factory.setSizeThreshold(100*1024);
		//设置临时文件夹
		factory.setRepository(new File(this.getServletContext().getRealPath("WEB-INF/temp")));
		//2.生产文件上传核心类
		ServletFileUpload fileUpload=new ServletFileUpload(factory);
		try {
		//判断是否为真正的表单上传文件
		//if (fileUpload.isMultipartContent(request)) {
			//throw new RuntimeException("请用正确的表单上传");
		//}
		//设置文件大小的上传限制
		fileUpload.setFileSizeMax(1024*1024*10);
		fileUpload.setSizeMax(1024*1024*100);
		//设置编码
		fileUpload.setHeaderEncoding("UTF-8");
		//设置上传监听进度条:
		fileUpload.setProgressListener(new ProgressListener() {
			//
			Long beginTime = System.currentTimeMillis();
			//已经读了,总共多少 ,读到第几个了
			@Override
			public void update(long pBytesRead, long pContentLength, int pItems) {
				// TODO Auto-generated method stub
				//转换为kb
				//double br=pBytesRead*1.0/1024;
				//double cl=pContentLength*1.0/1024;
				//为了保留字节用以下方法
				BigDecimal br = new BigDecimal(pBytesRead).divide(new BigDecimal(1024),2,BigDecimal.ROUND_HALF_UP);
				BigDecimal cl = new BigDecimal(pContentLength).divide(new BigDecimal(1024),2,BigDecimal.ROUND_HALF_UP);
				System.out.print("当前读取的是第"+pItems+"个上传项,总大小"+cl+"KB,已经读取"+br+"KB");
				//剩余字节数
				BigDecimal ll = cl.subtract(br);
				System.out.print("剩余"+ll+"KB");
				//上传百分比
				BigDecimal per = br.multiply(new BigDecimal(100)).divide(cl,2,BigDecimal.ROUND_HALF_UP);
				System.out.print("已经完成"+per+"%");
				//上传用时
				Long nowTime = System.currentTimeMillis();
				Long useTime = (nowTime - beginTime)/1000;
				System.out.print("已经用时"+useTime+"秒");
				//上传速度
				BigDecimal speed = new BigDecimal(0);
				if(useTime!=0){
					//四舍五入模式向“最近邻居”转弯,除非两个邻居都是等距的,在这种情况下是圆括弧的。
					speed = br.divide(new BigDecimal(useTime),2,BigDecimal.ROUND_HALF_UP);
				}
				System.out.print("上传速度为"+speed+"KB/S");
				//大致剩余时间
				BigDecimal ltime = new BigDecimal(0);
				if(!speed.equals(new BigDecimal(0))){
					//返回一个 BigDecimal ,其值为 (this / divisor) ,其比例为指定。
					ltime = ll.divide(speed,0,BigDecimal.ROUND_HALF_UP);
				}
				System.out.print("大致剩余时间为"+ltime+"秒");
				
				System.out.println();
				}
		});
		
		//3.利用文件上传核心类来解析request
			List<FileItem> list=fileUpload.parseRequest(request);
			//循环遍历
			for(FileItem item :list){
				if (item.isFormField()) {
					//普通的字段获得的是一个表单??
					String name=item.getFieldName();
					String value=item.getString("utf-8");
					System.out.println(name+":"+value);
				}else{
					//当前一个文件上传项
					String filename=item.getName();//文件名
					//设置一个独一无二的文件名
					String uuidfilename=UUID.randomUUID().toString()+"_"+filename;
					//转换为hash值
					int hash=uuidfilename.hashCode();
					//转化为hash字符串
					String hashstr=Integer.toHexString(hash);
					char[] hss=hashstr.toCharArray();
					String path=this.getServletContext().getRealPath("upload");
					for(char c:hss){
						path+="/"+c;
					}
					new File(path).mkdirs();
					InputStream inputStream=item.getInputStream();
					
					OutputStream outputStream=new FileOutputStream(new File(path,uuidfilename)); 
					IOUtils.In2Out(inputStream, outputStream);
					IOUtils.close(inputStream, outputStream);
					//删除临时文件
					item.delete();
				}
			}
		} catch (FileUploadException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			throw new RuntimeException();
		}

OK文件的上传和下载就到这里,其实就是数据在网络层的传输。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kay三石 [Alay Kay]

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值