一、文件上传概述
实现web开发中的文件上传功能,需要两步操作:
1、在web页面中添加上传输入项
<form action="#" method="post" enctype="multipart/form-data">
<input type="file" name="filename1"/><br>
<input type="file" name="filename2"/><br>
<input type="submit" value="上传"/>
<form>
<!-- 1、表单方式必须是post
2、必须设置enctype属性为 multipart/form-data.设置该值后,浏览器在上传文件时,将会把文件数据附带在http请求消息体中,
并使用mime协议对上传的文件进行描述,以方便接收方对上传数据进行解析和处理。
3、必须要设置input的name属性,否则浏览器将不会发送上传文件的数据。
-->
2、在servlet中读取文件上传数据,并保存到服务器硬盘
request对象提供了一个getinputstream方法,通过这个方法可以读取到客户端提交过来的数据。但由于用户可能会同时上传多个文件,在servlet端编程直接读取上传数据,并分别解析出相应的文件数据是一项非常麻烦的工作。
比如下面是截取的浏览器上传文件时发送的请求的http协议中的部分内容:
accept-language: zh-hans-cn,zh-hans;q=0.5
content-type: multipart/form-data; boundary=---------------------------7dfa01d1908a4
ua-cpu: amd64
accept-encoding: gzip, deflate
user-agent: mozilla/5.0 (windows nt 6.2; win64; x64; trident/7.0; rv:11.0) like gecko
content-length: 653
host: localhost:8080
connection: keep-alive
pragma: no-cache
cookie: jsessionid=11ceff8e271ab62ce676b5a87b746b5f
-----------------------------7dfa01d1908a4
content-disposition: form-data; name="username"
zhangsan
-----------------------------7dfa01d1908a4
content-disposition: form-data; name="userpass"
1234
-----------------------------7dfa01d1908a4
content-disposition: form-data; name="filename1"; filename="c:\users\asus\desktop\upload.txt"
content-type: text/plain
this is first file content!
-----------------------------7dfa01d1908a4
content-disposition: form-data; name="filename1"; filename="c:\users\asus\desktop\upload2.txt"
content-type: text/plain
this is second file content!
hello
-----------------------------7dfa01d1908a4--
从上面的数据中也可以看出,如果自己手工的去分割读取数据很难写出健壮稳定的程序。所以,为方便用户处理上传数据,apache开源组织提供了一个用来处理表单文件上传的一个开源组件(commons-fileupload),该组件性能优异,并且其api使用极其简单,可以让开发人员轻松实现web文件上传功能,因此在web开发中实现文件上传功能,通常使用commons-fileupload组件实现。
需要导入两个jar包:commons-fileupload、commons-io
response.setcontenttype("text/html;charset=utf-8");//设置响应编码
request.setcharacterencoding("utf-8");
printwriter writer = response.getwriter();//获取响应输出流
servletinputstream inputstream = request.getinputstream();//获取请求输入流
/*
* 1、创建diskfileitemfactory对象,设置缓冲区大小和临时文件目录
* 该类有两个构造方法一个是无参的构造方法,
* 另一个是带两个参数的构造方法
* @param int sizethreshold,该参数设置内存缓冲区的大小,默认值为10k。当上传文件大于缓冲区大小时,fileupload组件将使用临时文件缓存上传文件
* @param java.io.file repository,该参数指定临时文件目录,默认值为system.getproperty("java.io.tmpdir");
*
* 如果使用了无参的构造方法,则使用setsizethreshold(int sizethreshold),setrepository(java.io.file repository)
* 方法手动进行设置
*/
diskfileitemfactory factory = new diskfileitemfactory();
int sizethreshold=1024*1024;
factory.setsizethreshold(sizethreshold);
file repository = new file(request.getsession().getservletcontext().getrealpath("temp"));
// system.out.println(request.getsession().getservletcontext().getrealpath("temp"));
// system.out.println(request.getrealpath("temp"));
factory.setrepository(repository);
/*
* 2、使用diskfileitemfactory对象创建servletfileupload对象,并设置上传文件的大小
*
* servletfileupload对象负责处理上传的文件数据,并将表单中每个输入项封装成一个fileitem
* 该对象的常用方法有:
* boolean ismultipartcontent(request);判断上传表单是否为multipart/form-data类型
* list parserequest(request);解析request对象,并把表单中的每一个输入项包装成一个fileitem 对象,并返回一个保存了所有fileitem的list集合
* void setfilesizemax(long filesizemax);设置单个上传文件的最大值
* void setsizemax(long sizemax);设置上传温江总量的最大值
* void setheaderencoding();设置编码格式,解决上传文件名乱码问题
*/
servletfileupload upload = new servletfileupload(factory);
upload.setheaderencoding("utf-8");//设置编码格式,解决上传文件名乱码问题
/*
* 3、调用servletfileupload.parserequest方法解析request对象,得到一个保存了所有上传内容的list对象
*/
list<fileitem> parserequest=null;
try {
parserequest = upload.parserequest(request);
} catch (fileuploadexception e) {
e.printstacktrace();
}
/*
* 4、对list进行迭代,每迭代一个fileitem对象,调用其isformfield方法判断是否是文件上传
* true表示是普通表单字段,则调用getfieldname、getstring方法得到字段名和字段值
* false为上传文件,则调用getinputstream方法得到数据输入流,从而读取上传数据
*
* fileitem用来表示文件上传表单中的一个上传文件对象或者普通的表单对象
* 该对象常用方法有:
* boolean isformfield();判断fileitem是一个文件上传对象还是普通表单对象
* true表示是普通表单字段,
* 则调用getfieldname、getstring方法得到字段名和字段值
* false为上传文件,
* 则调用getname()获得上传文件的文件名,注意:有些浏览器会携带客户端路径,需要自己减除
* 调用getinputstream()方法得到数据输入流,从而读取上传数据
* delete(); 表示在关闭fileitem输入流后,删除临时文件。
*/
for (fileitem fileitem : parserequest) {
if (fileitem.isformfield()) {//表示普通字段
if ("username".equals(fileitem.getfieldname())) {
string username = fileitem.getstring();
writer.write("您的用户名:"+username+"<br>");
}
if ("userpass".equals(fileitem.getfieldname())) {
string userpass = fileitem.getstring();
writer.write("您的密码:"+userpass+"<br>");
}
}else {//表示是上传的文件
//不同浏览器上传的文件可能带有路径名,需要自己切割
string clientname = fileitem.getname();
string filename = "";
if (clientname.contains("\\")) {//如果包含"\"表示是一个带路径的名字,则截取最后的文件名
filename = clientname.substring(clientname.lastindexof("\\")).substring(1);
}else {
filename = clientname;
}
uuid randomuuid = uuid.randomuuid();//生成一个128位长的全球唯一标识
filename = randomuuid.tostring()+filename;
/*
* 设计一个目录生成算法,如果所用用户上传的文件总数是亿数量级的或更多,放在同一个目录下回导致文件索引非常慢,
* 所以,设计一个目录结构来分散存放文件是非常有必要,且合理的
* 将uuid取哈希算法,散列到更小的范围,
* 将uuid的hashcode转换为一个8位的8进制字符串,
* 从这个字符串的第一位开始,每一个字符代表一级目录,这样就构建了一个八级目录,每一级目录中最多有16个子目录
* 这无论对于服务器还是操作系统都是非常高效的目录结构
*/
int hashuuid =randomuuid.hashcode();
string hexuuid = integer.tohexstring(hashuuid);
//system.out.println(hexuuid);
//获取将上传的文件存存储在哪个文件夹下的绝对路径
string filepath=request.getsession().getservletcontext().getrealpath("upload");
for (char c : hexuuid.tochararray()) {
filepath = filepath+"/"+c;
}
//如果目录不存在就生成八级目录
file filepathfile = new file(filepath);
if (!filepathfile.exists()) {
filepathfile.mkdirs();
}
//从request输入流中读取文件,并写入到服务器
inputstream inputstream2 = fileitem.getinputstream();
//在服务器端创建文件
file file = new file(filepath+"/"+filename);
bufferedoutputstream bos = new bufferedoutputstream(new fileoutputstream(file));
byte[] buffer = new byte[10*1024];
int len = 0;
while ((len= inputstream2.read(buffer, 0, 10*1024))!=-1) {
bos.write(buffer, 0, len);
}
writer.write("您上传文件"+clientname+"成功<br>");
//关闭资源
bos.close();
inputstream2.close();
}
}
//注意eclipse的上传的文件是保存在项目的运行目录,而不是workspace中的工程目录里。
二、文件上传需要特别注意的问题: (这些问题在上面的代码中都提供了简单的解决)
1、文件存放的位置
为保证服务器的安全,上传文件应保存在应用程序的web-inf目录下,或者不受web服务器管理的目录,如果用户上传一个带有可执行代码的文件,如jsp文件,根据拼接访问路径去访问的话,可以在服务器端做任何事情。
2、为防止多用户上传形同文件名的文件,而导致文件覆盖的情况发生,文件上传程序应保证上传文件具有唯一文件名。
使用uuid + 用户上传文件名的方式重命名
关于uuid:
uuid(universally unique identifier)全局唯一标识符,是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。按照开放软件基金会(osf)制定的标准计算,用到了以太网卡地址、纳秒级时间、芯片id码和许多可能的数字。由以下几部分的组合:当前日期和时间(uuid的第一个部分与时间有关,如果你在生成一个uuid之后,过几秒又生成一个uuid,则第一个部分不同,其余相同),时钟序列,全局唯一的ieee机器识别号(如果有网卡,从网卡获得,没有网卡以其他方式获得),uuid的唯一缺陷在于生成的结果串会比较长。
是一个128位长的数字,一般用16进制表示。算法的核心思想是结合机器的网卡、当地时间、一个随即数来生成guid。从理论上讲,如果一台机器每秒产生10000000个guid,则可以保证(概率意义上)3240年不重复。
从jdk1.5开始,生成uuid变成了一件简单的事,以为jdk实现了uuid:
java.util.uuid,直接调用即可.
uuid uuid = uuid.randomuuid();
string s = uuid.randomuuid().tostring();//用来生成数据库的主键id非常不错。。
uuid是由一个十六位的数字组成,表现出来的形式例如
550e8400-e29b-11d4-a716-446655440000
3、为防止单个目录下文件过多,影响文件读写速度,处理上传文件的程序应该应根据可能的上传总量,选择合适的目录结构生成算法,将上传文件分散存储。如使用hashcode方法构建多级目录。
4、如果不同用户都上传了相同的文件,那么在服务器端没有必要存储同一个文件的很多分拷贝,这样很浪费资源,应该设计算法解决这种重复文件的问题。
5、jsp技术原理自动实现了多线程。所以开发者不需要考虑上传文件的多线程操作
三、文件下载
<%
arraylist<string> filenames = new arraylist<string>();
filenames.add("file/aa.txt");
filenames.add("file/bb.jpg");
for(string filename : filenames) {
%>
<form action="downloadservlet" method="get">
<input type="hidden" name="filename" value="<%=filename %>" />
<input type="submit" value="下载:<%=filename %>" />
</form>
<%
}
%>
request.setcharacterencoding("utf-8");
string filename = request.getparameter("filename");
string urlname = urlencoder.encode(filename, "utf-8");//防止文件名中有中文乱码
response.setheader("content-disposition","attachment;filename="+urlname);
fileinputstream fis = new fileinputstream(new file(request.getsession().getservletcontext().getrealpath(filename)));
bufferedinputstream bis = new bufferedinputstream(fis);
servletoutputstream sos = response.getoutputstream();
byte[] buffer = new byte[1024];
int len=0;
while((len=bis.read(buffer, 0, 1024))!=-1){
sos.write(buffer, 0, len);
}
bis.close();
fis.close();
四、在ssh中使用smartupload组件简化文件上传下载
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。