文件的上传与下载

一.实现文件上传功能

1,当我看到这句话的时候,想到的最简单的实现方式就是通过一个servlet,在里面再通过request对象提供getInputStream方法,读取客户端提交过来的数据,但是在这个实现过程中,如果有多个用户同时上传多个文件,在这个servlet端编程直接读取上传的数据,并分别解析出相应的文件的数据是一项非常麻烦的事情,所以这种实现方式不可取.

2,为了方便处理客户端上传的数据,Apache提供了一个处理表单文件上传的一个开源组件(Commons-fileupload ),

使用Commons-fileupload组件实现文件上传,需要导入该组件相应的支撑jar包:Commons-fileupload和commons-io。

3,fileUpload工作的流程图:
4,在编写客户端用于上传文件的表单的时候注意的事项:
1) 表单的提交是post (get提交数据在url地址上显示,有长度限制) 
2)表单标签中: enctype="multipart/form-data" 使得文件上传编码 ----- MIME编码格式
3)表单中intput标签中type属性,和必须有name属性:<input type="file" name="file1">否则文件上传内容不会被表单提交 
综上所述:有如下实例:
body>
	<form action="${pageContext.request.contextPath }/servlet/FileUpLoadDemo1" enctype="multipart/form-data" method="post">
		用户名:<input type="text" name="username"><br>
		上传文件:<input type="file" name="file1"><br>
		<input type="submit" value="提交">
	</form>
</body>
5,在服务器端编写文件上传程序
1)Apache commons-fileupload的使用
1>将jar包导入 web 工程WEB-INF/lib下
2>编程实现:
步骤一:获得DiskFileItemFactory 文件项工厂--->
DiskFileItemFactory factory=new DiskFileItemFactory();
步骤二:通过工厂获得文件上传请求核心解析类ServletFileUpload--->
ServletFileUpload fileupload=new ServletFileUpload(factory);
步骤三:使用ServletFileUpload对request进行解析  ---- 获得很多个FileItem ---->
List<FileItem> list = fileupload.parseRequest(request); --->(是表单中分割的各个部分)
步骤四:对每个FileItem进行操作 判断FileItem是不是普通字段 isFormField ---->
1)如果FileItem为普通字段:
getFieldName();  ---- 获得表单项name属性
getString(); ----- 获得表单项value
2)如果FileItem为文件上传字段:
getInputStream() --- 获得文件内容输入流
getName() ------ 获得上传文件名称
2)commons-fileupload 核心API 分析
1)DiskFileItemFactory 磁盘文件项工厂类 
1>public DiskFileItemFactory(int sizeThreshold, java.io.File repository)  构造工厂时,指定内存缓冲区大小和临时文件存放位置
2>public void setSizeThreshold(int sizeThreshold) 设置内存缓冲区大小,默认10K
3>public void setRepository(java.io.File repository)设置临时文件存放位置,默认System.getProperty("java.io.tmpdir"). 
内存缓冲区: 上传文件时,上传文件的内容优先保存在内存缓冲区中,当上传文件大小超过缓冲区大小,就会在服务器端产生临时文件临时文件存放位置: 保存超过了内存缓冲区大小上传文件而产生临时文件 
* 产生临时文件可以通过 FileItem的delete方法删除 

2)ServletFileUpload 文件上传核心类 
1>static boolean  isMultipartContent(javax.servlet.http.HttpServletRequest request)  判断request的编码方式是否为multipart/form-data
2>java.util.List parseRequest(javax.servlet.http.HttpServletRequest request) 解析request,将请求体每个部分封装FileItem对象,返回List<FileItem>
3>void setFileSizeMax(long fileSizeMax) 设置单个文件上传大小  和 void  setSizeMax(long sizeMax)设置总文件上传大小 
4>void setHeaderEncoding(java.lang.String encoding)  设置编码集 解决上传文件名乱码 *****
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编码不同,所有getPara meter 方
法不能使用 setCharacterEncoding 无法解决输入项乱码问题 
3) 上传文件在同一个目录重名问题  如果文件重名,后上传文件就会覆盖先上传文件 

文件名 UUID 
filename = UUID.randomUUID().toString() + "_" + filename;//保证了文件名的唯一性
4)为了防止同一个目录下方上传文件数量过多 ---- 必须采用目录分离算法 
1> 按照上传时间进行目录分离 (周、月 )
2>按照上传用户进行目录分离 ----- 为每个用户建立单独目录 
3>按照固定数量进行目录分离 ------ 假设每个目录只能存放3000个文件 ,每当一个目录存满3000个文件后,创建一个新的目录
4>通过URL对上传的文件进行目录分类:
String directory=this.getServletContext().getRealPath("/upload")+"/"+request.getRemoteAddr();
5) 乱码问题 
普通编写项 value属性乱码 ------------- fileItem.getString(编码集);
上传文件项 文件名乱码 --------- fileupload.setHeaderEncoding(编码集);
6)五、上传文件的进度监控 
ServletFileUpload 类 提供 public void setProgressListener(ProgressListener pListener) 
注意:此监听器必须在List<FileItem> list = fileupload.parseRequest(request); 方法之前就的设置
* 为文件上传程序绑定一个监听器对象,通过监听器可以监听文件上传全过程 
* 和AJAX技术结合,编写文件上传进度条 
设置监听器,文件上传程序会自动执行监听器中update方法 
public void update(long pBytesRead, long pContentLength, int pItems)

在方法中可以获得 文件总大小、已经上传大小和 上传第几个元素 
根据上面三个参数计算:剩余大小、传输速度、已用时间、剩余时间(在这里需要用到JDK中的
BigDecimal )
1) 已用时间 = 当前时间 - 开始时间
2) 速度 = 已经上传大小/已用时间
3) 剩余大小 = 总大小- 已经上传大小
4) 剩余时间 = 剩余大小/速度 
7)综上知识点总结的示例代码:
package cn.guoqing.fileupload;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

public class FileUpLoadDemo1 extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		try {
			DiskFileItemFactory factory=new DiskFileItemFactory();
			//由工厂获取文件上传核心处理类
			ServletFileUpload fileupload=new ServletFileUpload(factory);
			//设置单个上传文件的上限
			factory.setSizeThreshold(1024*100);
			//假如上传文件的大小超过了指定的上限,就把文件临时存储到指定的文件夹中
			factory.setRepository(new File(this.getServletContext().getRealPath("/temp")));
			if(!fileupload.isMultipartContent(request)){
				throw new RuntimeException("请用正确的文件上传表单上传数据!!!");
			}

			fileupload.setHeaderEncoding("utf-8");//????

			final long time1 = System.currentTimeMillis();
			//为上传处理类设置一个监听器
			fileupload.setProgressListener(new ProgressListener() {

				@Override
				public void update(long pBytesRead, long pContentLength, int pItems) {
					//1,当前上传的item
					System.out.println("当前上传的文件项为:"+pItems);
					System.out.println("当前上传的文件的字节书为:"+pContentLength);
					System.out.println("当前一共读取的字节数位:"+pBytesRead);
					//当前上传的进度
					System.out.println("当前的上传进度为:"+new BigDecimal(pBytesRead).divide(new BigDecimal(pContentLength), 4, BigDecimal.ROUND_HALF_UP).movePointRight(2)+"%");
					//文件上传速速
					long time2 = System.currentTimeMillis();
					Long time=time2-time1;
					double kbps=0;
					if(time>0)
						kbps=new BigDecimal(pBytesRead).divide(new BigDecimal(time), 2,BigDecimal.ROUND_HALF_UP).doubleValue();
					System.out.println("文件上传的速度为:"+kbps+"KB/S");
				}
			});

			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{
					InputStream is = item.getInputStream();
					String name=item.getName();
					System.out.println(name);
					name=UUID.randomUUID()+"_"+name;

					//IE9 bug
					if(name.contains("\\")){
						name = name.substring(name.lastIndexOf("\\")+1);
					}
					//将上传的文件按照上传的IP来分类存储
					String directory=this.getServletContext().getRealPath("/upload")+"/"+request.getRemoteAddr();

					File file=new File(directory);
					if(!file.exists()){
						file.mkdir();
					}
					File path=new File(file,name);
					FileOutputStream fos=new FileOutputStream(path);

					byte[] buf=new byte[1024];
					int len=0;
					while((len=is.read(buf))!=-1){
						fos.write(buf, 0, len);
					}
					fos.close();
					//删除临时存储文件
					item.delete();
				}
			}
		} catch (FileUploadException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		doGet(request, response);
	}

}

二.文件的下载

1.将服务器端的文件下载带客户端,常见的文件下载方式有两种编写方式;

1)超链接直接指向下载资源 
如果文件格式浏览器识别,将直接打开文件,显示在浏览器上,如果文件格式浏览器不识别,将弹出下载窗口对于浏览器识别格式的文件,通过另存为进行下载 .

2)编写服务器程序,读取服务器端文件,完成下载 
必须设置两个头信息 ,来自MIME协议  Content-Type  Content-Disposition 
response.setContentType(getServletContext().getMimeType(filename));
response.setHeader("Content-Disposition", "attachment;filename=" + filename); // 以附件形式打开,不管格式浏览器是否识别 .

2.使用get方式提交中文时 
<a href="/day21/downloadList?path=D:\TTPmusic\何晟铭\何晟铭 - 爱的供养.mp3">何晟铭 - 爱养.mp3</a><br/> 
问题:IE6 提交后,服务器经过get乱码处理获得 乱码 
原因:IE6对中文直接进行 get提交时,进行URL编码 ---- 编码发生问题 
解决:手动对get提交中文进行编码  ----- URLEncoder 

3.在浏览器页面中对一个已知目录进行遍历,如果遍历到的文件一个资源文件那么就将它显示在页面上:

代码:

<body>
	<%
		Queue<File> queue=new LinkedList<File>();
		queue.offer(new File("D:/music"));
		File file =null;
		while((file=queue.poll())!=null){
			File[] files=file.listFiles();
			for(int i=0;i<files.length;i++){
				if(files[i].isDirectory())
					queue.offer(files[i]);
				else{
				%>
					<a href="${pageContext.request.contextPath}/servlet/DownLoadServlet2?path=<%=URLEncoder.encode(files[i].getAbsolutePath(),"utf-8")%>">
						<%=files[i].getName() %></a><br>
				<% }
			}
			
		}
	 %>
</body>
3.一个服务器端的下载文件的servlet

package cn.guoqing.filedownload;

import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLEncoder;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class DownLoadServlet2 extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		String path = request.getParameter("path");
		System.out.println(path);
		//将上传的文件路径进行编码
		path = new String(path.getBytes("iso8859-1"),"utf-8");
		System.out.println(path);
		//获取文件的名字
		String filename = path.substring(path.lastIndexOf("\\")+1);
		System.out.println(filename);
		response.setHeader("Content-Disposition", "attachment;filename="+URLEncoder.encode(filename,"utf-8"));
		response.setHeader("Content-Type", this.getServletContext().getMimeType(filename));
		//定义一个跟资源相关联的流对象,用于读取资源
		FileInputStream fis=new FileInputStream(path);
		ServletOutputStream os = response.getOutputStream();
		//将文件读取到response缓存区中
		byte[] buf=new byte[1024];
		int len=0;
		while((len=fis.read(buf))!=-1){
			os.write(buf, 0, len);
		}
		fis.close();

	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		doGet(request, response);
	}

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值