Javaweb中文件的上传和下载
文件上传
文件上传指的是用户通过浏览器向服务器上传某个文件,服务器接收到该文件后会将该文件存储在服务器的硬盘中,通常不会存储在数据库中,这样可以减轻数据库的压力并且在文件的操作上更加灵活,常见的功能是上传头像图片。
文件上传的原理
所谓的文件上传就是服务器端通过request对象获取输入流,将浏览器端上传的数据读取出来,保存到服务器端。
文件上传的要求
- 提供form表单,表单的提交方式必须是post【get请求装不下那么多】
- form表单中的enctype属性必须是 multipart/form-data 【照着做就行】
- 表单中提供input type=”file”上传输入域 【文件那个表单】
先来个表单:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <figure> <img src=""> </figure> <form action="#" method="post" accept-charset="utf-8" enctype="multipart/form-data"> <!--# 提交地址记得改!--> <input type="file" name="photo" ><br> <input type="submit" value="上传头像"> </form> </body> </html>
来个Servlet来接收一下这个图片:
package upload; import java.io.IOException; import java.io.InputStream; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 文件上传例子 */ public class file extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { }
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //获取请求的输入流 InputStream is = request.getInputStream(); //读取输入流中的数据 int leng = 0; byte[] bytes = new byte[1024]; while ((leng = is.read(bytes)) != -1) { //先打印控制台看看 System.out.println(new String(bytes,0,leng)); } } }
打印出来的数据:
------WebKitFormBoundarypM4ZEsxzVdl0NfZV Content-Disposition: form-data; name="photo"; filename="4-2 鍥剧墖鍒囨崲鏁堟灉[20210508-164643].jpg" Content-Type: image/jpeg ???
反正一堆乱码 但是头部我们是看的懂的 就是一些标签的属性 和 上传的照片名字!和 文件类型!
如何解决?请看:
FileUpload工具的使用
在实际开发中通常会借助第三方工具来实现上传功能,应用较多的是apache旗下的Commons-fileupload。【第三方插件都要导jar包! 学了那么久 应该知道吧】
Apache 开源组织提供了一个用来处理表单文件上传的一个开源组件( Commons-fileupload ),该组件性能优异,并且其API使用极其简单,可以让开发人员轻松实现web文件上传功能,因此在web开发中实现文件上传功能,通常使用Commons-fileupload组件实现。
在使用该工具实现上传功能时,首先需要导入Commons-fileupload和commons-io两个jar包。【这两个包可以处理表单 和 处理上传文件】
去百度阿帕奇官网下载即可:
然后我们接着上面的例子 ,直接上手:
先提示一下 如果不能在Eclipse中服务器不能创建文件夹,那么就在换成本地的Tomcat的即可。
file.jsp:
package upload; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.List; import javax.management.RuntimeErrorException; import javax.print.attribute.standard.Severity; 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.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; /** * 文件上传例子 */ public class file extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //先来个编码 防止文件名乱码 推荐在过滤器写 request.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=utf-8"); //1.判断表单是否支持文件上传。即:enctype="multipart/form-data" boolean ismultipart = ServletFileUpload.isMultipartContent(request); //别导错包了 是你jar那个包哦 if(!ismultipart) { //不支持文件上传的话那就报异常 throw new RuntimeException("err:还请求不支持文件上传!"); } //2.创建一个DiskFileItemfactory 对象 DiskFileItemFactory dff = new DiskFileItemFactory(); //3.创建一个 ServletFileUpload对象,这个是上传对象的核心。 ServletFileUpload sfu = new ServletFileUpload(dff); //4.解析request对象 并得到一个表单项的集合 //sfu.parseRequest 解析 参数是request List<FileItem> 类型: List<FileItem> fileitme; try { fileitme = sfu.parseRequest(request); //需要处理异常 for(FileItem t : fileitme) { if(t.isFormField()) { //普通表单【直接打印】: String fileName = t.getFieldName(); //字段名 String fileValue = t.getString("UTF-8"); //字段值 System.out.println(fileName + " : " + fileValue); }else { //文件表单【保存到服务器的硬盘中】: //获取文件名 String fileName = t.getName(); //获取输入流 InputStream is = t.getInputStream(); //创建输出流 获取地址 -> 文件对象 -> 输出流 -> 输出 String path = this.getServletContext().getRealPath("/head_img"); //getRealPath:动态的得到WebRoot中文件在磁盘中的位置 System.out.println("*调试:存放文件路径:" + path); //console File file = new File(path,fileName); //这个看不懂你可能得倒回去学JavaSe 了.... FileOutputStream fos = new FileOutputStream(file); //开始输出 byte[] bytes = new byte[1024]; int len = -1; while((len = is.read(bytes)) != -1) { fos.write(bytes, 0, len);; } /* 临时文件一旦用完,就可将其删除了,否则占用服务器的硬盘空间。 需要注意的是,对于临时文件的删除,需要在 IO 流关闭后,否则,无法删除。 */ is.close(); fos.close(); t.delete(); //删除刚上传的临时文件 } } } catch (Exception e) { e.printStackTrace(); } } }
主要还是 步骤要熟悉 还有就是几个方法要记住 特别是路径要分清楚
允许后你会发现你的目录下 xxxx.head_img 里面就是你放的图片【头像】了
!!! xxxx.head_img 这个目录一定要存在!!!服务器根部目下! 【报错的时候会给出 你可以复制然路劲跟进去 粘贴即可】!!!
!!! 经过我完全测试 终于发现了 FileItem【 fileitme 类的返回值【List包裹的那个】 】 的 几个常用方法: 【要熟悉!!!】
所以说 用这两个jar包很方便 直接处理了文件的 还有 表单的。
1. boolean isFormField() 【这个ServletFileUpload 也有 一开始就是用ServletFileUpload的!】 用于判断FileItem类对象封装的数据是一个普通文本表单字段,还是一个文件表单字段,如果是普通表单字段则返回true,否则返回false。 2. String getName() 用于获得文件上传字段中的文件名,注意IE或FireFox中获取的文件名是不一样的,IE中是绝对路径,FireFox中只是文件名。 3.String getFieldName() 用于返回表单标签name属性的值。 4.void write(File file) 将FileItem对象中保存的主体内容保存到某个指定的文件中。如果FileItem对象中的主体内容是保存在某个临时文件中,该方法顺利完成后,临时文件有可能会被清除。该方法也可将普通表单字段内容写入到一个文件中,但它主要用途是将上传的文件内容保存在本地文件系统中。 5.String getString() 将FileItem对象中保存的数据流内容以一个字符串返回,它有两个重载的定义形式: public Java.lang.String getString() public java.lang.String getString(java.lang.String encoding) 即一个不指定编码 一个指定编码 6.String getContentType() 用于获得上传文件的类型,即表单字段元素描述头属性“Content-Type”的值,如“image/jpeg”。如果FileItem类对象对应的是普通表单字段,该方法将返回null 7.boolean isInMemory() 用来判断FileItem对象封装的数据内容是存储在内存中,还是存储在临时文件中,如果存储在内存中则返回true,否则返回false。 8. delete() delete方法用来清空FileItem类对象中存放的主体内容,如果主体内容被保存在临时文件中,delete方法将删除该临时文件。 9.InputStream getInputStream() 以流的形式返回上传文件的数据内容。 10.long getSize() 返回该上传文件的大小(以字节为单位)。
关于优化: 上面用到了上传 下面进行优化说明:
1.文件名乱码 你可以在过滤器过滤编码 或者post头过滤
2.文件夹不存在 文件夹不存在你可以加判断语句即可
3. 这个jar包不是可以判断文件表单普通表单吗 那你可以直接用来获取即可【具体功能自己发挥】
4.你可以拓展完成网页显示该图片,例如更换头像.
5.加判断是否没选择文件就点击了上传【会抛异常】
注意:文件上传时在服务器中所存放的路径最好是用户不能直接访问的路径,这样可以保证文件的安全。
用Commons-fileupload和commons-io 上传文件的原理:
临时目录:
临时目录
文件由浏览器通过网络上传到服务器,并不是直接通过一条网络线路将所有请求数据发送到了服务器的。而是将这些数据分为了很多个数据包,这些数据包分别被编号后,经由不同的网络线路最终发送到了服务器中。这些数据包到达服务器的时间会根据不同的网络线路的情况不同,分别先后到达服务器,顺序是不定的。因此服务器会在其临时目录中,创建一个临时文件,将这些数据包进行拼接组装。
Tomcat 默认情况下的临时目录是 Tomcat 服务器安装目录的 temp 子目录。我们也可以修改临时目录的默认位置。Apache 的 FileUpload 支持设置创建临时文件的最小临界值【Tomcat的默认边界是10kb】,即只有上传的文件大小超出这个值,才会创建临时文件。通过 DiskFileItemFactory 的 setSizeThreshold()方法可以设置临界值,单位为字节。通过 DiskFileItemFactory 的 setRepository()方法可以指定临时目录。
设置了临时文件的 Servlet:
package upload; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.List; import javax.management.RuntimeErrorException; import javax.print.attribute.standard.Severity; 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.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; /** * 文件上传例子 */ public class file extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //先来个编码 防止文件名乱码 推荐在过滤器写 request.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=utf-8"); //设置好上传的图片需要放的位置 String img_path = this.getServletContext().getRealPath("/head_img"); //getRealPath:动态的得到WebRoot中文件在磁盘中的位置 System.out.println("*调试:存放文件路径:" + img_path); //console //1.判断表单是否支持文件上传。即:enctype="multipart/form-data" boolean ismultipart = ServletFileUpload.isMultipartContent(request); //别导错包了 是你jar那个包哦 if(!ismultipart) { //不支持文件上传的话那就报异常 throw new RuntimeException("err:还请求不支持文件上传!"); } // *********************************// //2.创建一个DiskFileItemfactory 对象 DiskFileItemFactory dff = new DiskFileItemFactory(); //设置临时文件的目录 String temp_path = this.getServletContext().getRealPath("/temp"); //确保你temp存在啊 dff.setRepository(new File(temp_path)); //设置指定临时文件的路径 默认是tomcat的temp dff.setSizeThreshold(1024*1024*3); //设置临时文件触发大小【单位:字节】 这里设置3MB // *********************************// //3.创建一个 ServletFileUpload对象,这个是上传对象的核心。 ServletFileUpload sfu = new ServletFileUpload(dff); //4.解析request对象 并得到一个表单项的集合 //sfu.parseRequest 解析 参数是request List<FileItem> 类型: List<FileItem> fileitme; try { fileitme = sfu.parseRequest(request); //需要处理异常 for(FileItem t : fileitme) { if(t.isFormField()) { //普通表单【直接打印】: String fileName = t.getFieldName(); //字段名 String fileValue = t.getString("UTF-8"); //字段值 System.out.println(fileName + " : " + fileValue); }else { //文件表单【保存到服务器的硬盘中】: //获取文件名 String fileName = t.getName(); //获取输入流 InputStream is = t.getInputStream(); //创建输出流 获取地址 -> 文件对象 -> 输出流 -> 输出 File file = new File(img_path,fileName); //这个看不懂你可能得倒回去学JavaSe 了.... FileOutputStream fos = new FileOutputStream(file); //开始输出 byte[] bytes = new byte[1024]; int len = -1; while((len = is.read(bytes)) != -1) { fos.write(bytes, 0, len);; } is.close(); fos.close(); // t.delete(); //清空临时文件 执行前请先关闭流 【记得清除掉啊!】 } } } catch (Exception e) { e.printStackTrace(); } } }
上传文件名重名的问题
在服务器中已有文件名叫做s.jpg的文件,当用户再次上传同名的文件时,会将之前的文件名覆盖,这样会出现问题。为了解决该问题,可以将上传的文件重命名,有两种方式:
必须二选一!
- 在文件名中添加系统时间戳。
- 在文件名中添加uuid。【常用】
uuid是Universally Unique Identifier的缩写,中文是通用统一识别码,uuid具有唯一性,uuid的生成跟系统的时间、mac地址、时间序列、随机数有关,所以通常所生成的uuid是不会重复的,两个相同的uuid出现的概率非常低(比陨石撞击地球的概率还要低)。
部分代码示例:
//文件表单【保存到服务器的硬盘中】: //获取文件名 String fileName = t.getName(); //获取输入流 InputStream is = t.getInputStream(); //创建输出流 获取地址 -> 文件对象 -> 输出流 -> 输出
二选一!
fileName = System.currentTimeMillis() + "_" + fileName; //加入时间戳防止文件名冲突【比较少用】 fileName = UUID.randomUUID() + "_" + fileName; //加入UUID 【常用】 System.out.println(fileName); File file = new File(img_path,fileName); //这个看不懂你可能得倒回去学JavaSe 了.... FileOutputStream fos = new FileOutputStream(file); //开始输出 byte[] bytes = new byte[1024]; int len = -1; while((len = is.read(bytes)) != -1) { fos.write(bytes, 0, len);; } is.close(); fos.close(); t.delete(); //清空临时文件 执行前请先关闭流 } }
上传文件的大小
对于上传文件的大小,可以通过 ServletFileUpload 的 setFileSizeMax()与 setSizeMax()方法进行控制。 setFileSizeMax()用于设置单个文件上传的最大值,而 setSizeMax()用于设置单次【包含多个】上传的最大值。即若一次上传多个文件,每个文件的大小边界值与所有文件加起来的最大小值。
部分代码演示:
.....
//3.创建一个 ServletFileUpload对象,这个是上传对象的核心。 ServletFileUpload sfu = new ServletFileUpload(dff); //设置上传文件的大小限制: sfu.setFileSizeMax(1024*1024*2); //设置上传单个文件限制2M sfu.setSizeMax(1024*1024*5); //设置上传文件总大小限制5M //PS: 超过会报异常 且不会上传! //4.解析request对象 并得到一个表单项的集合
.....
因为用到 ServletFileUpload 所以推荐在他后面设置
创建目录
无论是 Windows 系统、Linux 系统,还是其它系统,其目录中所包含的文件数量是有上
限的。所以对于上传的文件,应该分目录进行管理。若文件较多,则可按照年、月、日创建多级子目
录。这样,即方便管理,又不会超出目录的文件数量上限。
部分演示代码:
//文件表单【保存到服务器的硬盘中】: //获取文件名 String fileName = t.getName(); //获取输入流 InputStream is = t.getInputStream(); //创建输出流 获取地址 -> 文件对象 -> 输出流 -> 输出 //下面开始创建多层文件夹【按日期创】 LocalDate date_now = LocalDate.now(); int Year = date_now.getYear(); int Month = date_now.getMonthValue(); int Day = date_now.getDayOfMonth(); String day_path = "/" + Year + "/" + Month + "/" + Day; //创建父目录【年/月/日】 File parentPath = new File(img_path + day_path); //【上面已定义img_path】 //如果不存在 则创建: if (!parentPath.exists()) { parentPath.mkdirs(); } fileName += "_" + System.currentTimeMillis(); //加入时间戳防止文件名冲突【比较少用】 fileName += "_" + UUID.randomUUID(); //加入UUID 【常用】 System.out.println(fileName); File file = new File(parentPath,fileName); //这个看不懂你可能得倒回去学JavaSe 了.... FileOutputStream fos = new FileOutputStream(file); //开始输出 byte[] bytes = new byte[1024]; int len = -1; while((len = is.read(bytes)) != -1) { fos.write(bytes, 0, len);; } is.close(); fos.close(); t.delete(); //清空临时文件 执行前请先关闭流 }
此处分块简介功能,但是实战开发的时候 一定要组合好代码 不要东一块西一块 实例化的放一块 然后要创建目录的放一块 要合并目录名的放一块..........总而言之 :
看着代码全部功能来做一个整体的排版【最佳 最简洁高效】
关于路径 主要还是 这个方法:
//getRealPath:动态的得到WebRoot中文件在磁盘中的位置