Request,Response编码解码原理,文件上传下载和底层IO - 新闻资讯 - 云南小程序开发|云南软件开发|云南网站建设-昆明葵宇信息科技有限公司

159-8711-8523

云南网建设/小程序开发/软件开发

知识

不管是网站,软件还是小程序,都要直接或间接能为您产生价值,我们在追求其视觉表现的同时,更侧重于功能的便捷,营销的便利,运营的高效,让网站成为营销工具,让软件能切实提升企业内部管理水平和效率。优秀的程序为后期升级提供便捷的支持!

您当前位置>首页 » 新闻资讯 » 网站建设 >

Request,Response编码解码原理,文件上传下载和底层IO

发表时间:2016-10-25

发布人:葵宇科技

浏览次数:27

最近发现自己基础很差,自以为学的不错的东西,其实缺乏练习和复习,基础十分不扎实。恶补无从下手,只得从以前的具体例子出发,在例子中发现能力存在的问题,结合实例复习和掌握遗忘的和本来就不扎实没学透的知识:

源程序:

表单显示页面的Servlet和JSP:

package cn.xbai.servlet;

import java.io.IOException;

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

public class FileUploadServlet extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		
		//转发和重定向的区别,这里没设置编码,为什么转发到的jsp页面可以正常显示中文?
		//-转向jsp那个servlet,其编码是UTF-8,给response解码和通知浏览器编码的都是UTF-8
		request.getRequestDispatcher("/jsp/index.jsp").forward(request, response);
	}

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

		doGet(request, response);
	}

}

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    
    
    <title>My JSP 'index.jsp' starting page</title>
	
  </head>
  
  <body>
    This is my JSP page. <br>
    你好,你好<br>
    <form action="${pageContext.request.contextPath }/servlet/FileUploadSubmit" method="post">
    	姓名:<input type="text" name="name"/><br/>
    	年龄:<input type="text" name="age"/><br/>
    	证件类型:<select name="idType">
    				<option value="1">身份证</option>
    				<option value="0">其他</option>
    			</select><br/>
    	证件号码:<input type="password" name="idNum"/><br/>
    	简历资料:<input type="file" name="introduction"/><br/>
    	   <input type="submit" value="提交"/>
    </form>
  </body>
</html>

处理提交的Servlet:

package cn.xbai.servlet;

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 FileUploadSubmit extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		
		/**
		 * 码表原理-----------------------------------------
		 */
		request.setCharacterEncoding("UTF-8");//post提交解码 -因为Socket底层传输都是字节流
		//-浏览器根据页面编码码表编码成字节流
		//复习io,socket,tcp/ip,多线程深入,集合,数据结构
		/*//get:或可在过滤器中拦截,用包装类重写request的getParameter方法
		String username=request.getParameter("name");//get提交怎样都是iso-8859-1编码
		//反向获取字节码重新解码
		username=new String(username.getBytes("iso8859-1"),"UTF-8");
		*/
		/**
		 * 实验:中文,浏览器提交页面是以UTF-8提交(也就是按UTF-8编码传输过来):
		 * 1.request不设置,response按UTF-8,乱码
		 * 2.request设置UTF-8,response按UTF-8,正常
		 * 3.request,response都什么也不设置:涓浗-因request默认iso-8859-1解码,response默认iso-8859-1编码,而浏览器因为没有控制默认GBK打开
		 * 4.接着3,把request设置成UTF-8,response不设置:?? 而sysout正常:中国(io字节流,默认GBK编码解码?查!-即时查:System.out是PrintStream,PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节。
		 * 在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类。 那么平台这边解码也应该是默认字符编码,对高级语言层面的数据(此时是正常的)解码编码相同,虽然是GBK,所以不乱吗
			)-因response按iso-8859-1写入,浏览器因为没有控制,经查以GBK打开,自然乱码-每一步都有事实依据不是臆测乱说的!
		 * 5.request,response都设置UTF-8,只不控制浏览器输出:涓浗-因UTF-8编码的数据用GBK解码了:查浏览器是GBK打开,这和3情形类似
		 */
		//response.setCharacterEncoding("UTF-8");//写给response,设置response编码-将高级语言级别数据编码成字节流给response对象
		//response.setContentType("text/html;charset=UTF-8");//设置响应头控制浏览器解码
		
		
		String name=request.getParameter("name");
		System.out.println(name);
		//不明白码表原理,去看源码!底层io:看看response的getWriter返回是什么流-即时查:
		/**
		 * <pre>
		 *  getWriter
		 * 
		 * 		public java.io.PrintWriter getWriter()
		 * 		                      throws java.io.IOException
		 * 
		 * 		Returns a PrintWriter object that can send character text to the client. The PrintWriter uses the character encoding returned by getCharacterEncoding(). If the response's character encoding has not been specified as described in getCharacterEncoding (i.e., the method just returns the default value ISO-8859-1), getWriter updates it to ISO-8859-1. 
		 * 		
		 * </pre>
		 * 说明是PrintWriter类型,默认设置成iso-8859-1编码
		 */
		//response.getWriter().write(name);//设置了编码,就以这个编码写入response
		/**
		 * 终极实验:request正确解码的情况下,response什么都不设置,用getOutputStream(它返回一个ServletOutputStream,是OutputStream的直接子类)原始字节流传输:中国,这里name这个String默认GBK编码成字节流,浏览器因为没有控制也默认GBK打开,所以不乱码
		 */
		response.getOutputStream().write(name.getBytes());
		
		
	}

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

		doGet(request, response);
	}

}

我写的都是我验证过的,具体原理在代码和注释当中。

文件上传的操作:前面form表单页面设置成enctype="multipart/form-data"

处理提交的Servlet:

package cn.xbai.servlet;

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 FileUploadSubmit extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		
		
		/**
		 * 文件上传表单类型--------------------------------------------
		 * 前面用了multipart/form-data类型,这里这种方法就接收不到了
		 * 500是服务器端错误:这里因为multipart/form-data类型而无法这样获取到,反复尝试坚持调错才能明白
		 */
		//String name=request.getParameter("name");
		//System.out.println(name);
		
		/**
		 * 关于文件的接收操作-------------------------------------------
		 * 实验:String默认解码,UTF-8内容乱码,GBK的文件内容正常;UTF-8解码,UTF-8数据正常,GBK文件内容乱码
		 再实验:request不设置UTF-8,本来就无法通过request.getParameter接收,所以这里无影响
		 再实验:提交jsp页面改为GBK,这里仍然UTF-8解码,全部乱码
		 再实验:提交jsp页面改为GBK,这里GBK解码,全部正常
		 结论:jsp页面码表设置用于浏览器传递时编码非文件数据(和显示时解码?),这里的解码和编码一致才不会乱码
		 文件以字节方式存储在磁盘,存储时编码是什么就按什么编码存储,如果解码用不同码表,就会乱码,表单选择文件时以原始字节流关联,原始字节流传输,不存在码表问题,只是这里解码需要注意源文件存储时编码
		 
		 因困惑而重复实验:jsp页面UTF-8,这里GBK解码,GBK文件正常,UTF-8数据不正常
		 那么,最终结论和解决方案:UTF-8解码UTF-8数据,GBK解码GBK文件,分开判断
		 */
		InputStream in=request.getInputStream();
		int length=0;
		byte[] buffer=new byte[1024];
		while((length=in.read(buffer))>0){
			System.out.println(new String(buffer,0,length,"GBK"));
		}
		
	}

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

		doGet(request, response);
	}

}

文件上传需要注意的问题:

1.上传文件的中文乱码
1.1 解决文件的乱码
ServletFileUpload.setHeaderEncoding("UTF-8")
1.2 解决普通输入项的乱码(注意,表单类型为multipart/form-data的时候,设置request的编码是无效的)
FileItem.setString("UTF-8"); //解决乱码

2.在处理表单之前,要记得调用:
ServletFileUpload.isMultipartContent方法判断提交表单的类型,如果该方法返回true,则按上传方式处理,否则按照传统方式处理表单即可。


3.设置解析器缓冲区的大小,以及临时文件的删除
设置解析器缓冲区的大小 DiskFileItemFactory.setSizeThreshold(1024*1024);
临时文件的删除:在程序中处理完上传文件后,一定要记得调用item.delete()方法,以删除临时文件

4.在做上传系统时,千万要注意上传文件的保存目录,这个上传文件的保存目录绝对不能让外界直接访问到。

5.限制上传文件的类型
在处理上传文件时,判断上传文件的后缀名是不是允许的

6.限制上传文件的大小
调用解析器的ServletFileUpload.setFileSizeMax(1024*1024*5);就可以限制上传文件的大小,如果上传文件超出限制,则解析器会抛FileUploadBase.FileSizeLimitExceededException异常,程序员通过是否抓到这个异常,进而就可以给用户友好提示。


7.如何判断空的上传输入项
String filename = item.getName().substring(item.getName().lastIndexOf("\\")+1); //""
if(filename==null || filename.trim().equals("")){
continue;
}

8、为避免上传文件的覆盖,程序在保存上传文件时,要为每一个文件生成一个唯一的文件名
public String generateFileName(String filename){
//83434-83u483-934934
return UUID.randomUUID().toString() + "_" + filename;
}

9、为避免在一个文件夹下面保存超过1000个文件,影响文件访问性能,程序应该把上传文件打散后存储。
public String generateSavePath(String path,String filename){
int hashcode = filename.hashCode(); //121221
int dir1 = hashcode&15;
int dir2 = (hashcode>>4)&0xf;

String savepath = path + File.separator + dir1 + File.separator + dir2;
File file = new File(savepath);
if(!file.exists()){
file.mkdirs();
}
return savepath;
}

10、监听上传进度
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setProgressListener(new ProgressListener(){
public void update(long pBytesRead, long pContentLength, int pItems) {
System.out.println("当前已解析:" + pBytesRead);
}
});

11、在web页面中添加动态上传输入项

一个考虑全面的文件上传程序(提交处理,表单页面同前面):

package cn.xbai.servlet;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
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.FileUploadBase;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

public class FileUploadSubmit extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		
		
		/**
		 * 文件上传的步骤
		 */
		//如果表单类型为multipart/form-data,在servlet中注意不能采用传统方式获取数据
		//并且设置request编码无效(request把jsp提交的字节码转换过来)
		
		//创建解析工厂
		
		//创建解析器
		
		//调用解析器解析request,得到保存了所有上传数据的list
		
		//迭代list集合,拿到封装了每个输入项的fileItem
		
		//判断item类型,是普通字段则直接获取数据,是上传文件,则调用流获取数据写入本地硬盘
		
		List types=Arrays.asList("jpg","gif","avi","txt");
		
		/**
		 * 文件上传框架操作------------------------------------------------
		 * 循序渐进:分别解析普通字段和文件数据的解决方案,官方fileupload相关框架
		 */
		try {
			DiskFileItemFactory factory=new DiskFileItemFactory();
			factory.setSizeThreshold(1024*1024);
			factory.setRepository(new File(this.getServletContext().getRealPath("/temp")));
			
			ServletFileUpload upload=new ServletFileUpload(factory);
			upload.setProgressListener(new ProgressListener(){
				
				public void update(long pBytesRead, long pContentLength, int pItems) {
					// TODO Auto-generated method stub
					System.out.println("当前已解析:" + pBytesRead);
				}
				
			});
			
			upload.setFileSizeMax(1024*1024*5);
			if(!upload.isMultipartContent(request)){
				//按照传统方式获取表单数据
				request.getParameter("username");
				return;
			}
			//解决乱码:
			upload.setHeaderEncoding("UTF-8");
			List<FileItem> list=upload.parseRequest(request);
			
			for(FileItem item:list){
				if(item.isFormField()){
					//为普通输入项
					String inputName=item.getFieldName();
					String inputValue=item.getString("UTF-8");//这样解码
					//这样先从乱码还原再重新解码也可以
					//inputValue = new String(inputValue.getBytes("iso8859-1"),"UTF-8");
					System.out.println(inputName + "="  + inputValue);
				}else{
					String filename=item.getName().substring(item.getName().lastIndexOf("\\")+1);
					if(filename==null || filename.trim().equals("")){
						continue;
					}
					/*String ext = filename.substring(filename.lastIndexOf(".")+1);
					if(!types.contains(ext)){
						request.setAttribute("message", "本系统不支持" + ext + "这种类型");
						request.getRequestDispatcher("/message.jsp").forward(request, response);
						return;
					}*/
					//如果文件大小超过指定值,则存储到一个临时文件中,那么这里的流是读临时文件
					InputStream in=item.getInputStream();
					int len=0;
					byte buffer[]=new byte[1024];
					String saveFileName=generateFileName(filename);
					//!!!注意:上传文件的目录一定不能让外界直接访问到------->放到WEB-INF目录下!!
					//如果设置在应用目录下的一个目录中,用户上传一个破坏服务器的jsp,再猜路径访问这个jsp执行,会有安全问题
					String savepath=generateSavePath(this.getServletContext().getRealPath("/WEB-INF/upload"),saveFileName);
					//分隔符:windows下开发linux下部署,要用兼容的,调用File的一个方法
					FileOutputStream out=new FileOutputStream(savepath+File.separator+saveFileName);
					//读取,写入
					while((len=in.read(buffer))>0){
						out.write(buffer,0,len);
					}
					//别忘了关流:应该在try-catch-finally里的
					try {
						if(in!=null){
							
							in.close();
						}
					} catch (Exception e) {
						throw new RuntimeException(e);
					}finally{
						try {
							if(out!=null){
								
								out.close();
							}
						} catch (Exception e) {
							// TODO: handle exception
							throw new RuntimeException(e);
						}
					}
					item.delete();//删除临时文件,注意一定放在流关闭之后,不然删不掉!
				}
			}
		}catch (FileUploadBase.FileSizeLimitExceededException e) {
			request.setAttribute("message", "文件大小不能超过5m");
			request.getRequestDispatcher("/message.jsp").forward(request, response);
			return;
		}catch (Exception e) {
			throw new RuntimeException(e);
		}
		request.setAttribute("message", "上传成功!!");
		request.getRequestDispatcher("/message.jsp").forward(request, response);
		
	}
	
	public String generateSavePath(String path,String filename){
		int hashcode=filename.hashCode();
		//取低4位
		int dir1=hashcode&15;//以后这样的机巧算法要自己懂得琢磨,利用底层基础
		int dir2=(hashcode>>4)&0xf;//右移4位后取低4位
		
		String savepath=path+File.separator+dir1+File.separator+dir2;
		File file=new File(savepath);
		//别忘了创建这个目录
		if(!file.exists()){
			file.mkdirs();//创建级联目录
		}
		return savepath;
	}
	
	public String generateFileName(String filename){
		return UUID.randomUUID().toString()+"_"+filename;
	}
	
	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		doGet(request, response);
	}

}

文件下载:

列出所有文件:

package cn.xbai.servlet;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

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

public class ListFileServlet extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		String path=this.getServletContext().getRealPath("/download");
		File ddir=new File(path);
		List<File> fileList=new ArrayList<File>();//不要忘了初始化,否则空指针异常
		listAllFiles(ddir,fileList);
		Map<String,String> fileMap=new LinkedHashMap<String,String>();//没顺序是一开始这个Map无序造成的!
		for(File file:fileList){
			fileMap.put(file.getName(),file.getPath());
		}
		request.setAttribute("fileMap", fileMap);
		request.getRequestDispatcher("/WEB-INF/jsp/downloadlist.jsp").forward(request, response);
	}

	private void listAllFiles(File ddir,List<File> fileList) {//因为有递归,这里用参数传入,递归调用中传入这同一个参数,达到一个集合存储所有递归结果且有递归顺序
		// TODO Auto-generated method stub
		if(!ddir.isFile()){
			//fileList.add(ddir);
			File children[]=ddir.listFiles();
			for(File f:children){
				listAllFiles(f,fileList);
			}
		}else{
			fileList.add(ddir);//递归出口
		}
		/*
		 * 下面的思路有问题,ddir就应该判断了,而不是遍历它的子元素才开始判断,造成递归出的顺序混乱!
		//API文档引用:如果此抽象路径名不表示一个目录,那么此方法将返回 null。
		//否则返回一个 File 对象数组,每个数组元素对应目录中的每个文件或目录。
		File[] files = ddir.listFiles();//是前面list方法返回的只是文件名而不是路径,这里才递归不出来
		//递归出口:到最末端文件,list为空,执行完方法后自然不再递归,因为下面没文件了
		if(files!=null){//为了有递归出口且顺利完成递归并返回,不能用try,catch,而要有一个出口判断条件
			
			for (File file : files) {
				
				if (file.exists() && file.isFile()) {
					fileList.add(file);
				} else {
					fileList.add(file);
					listAllFiles(file, fileList);
				}
			}
		}
		*/
		
	}

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

		doGet(request, response);
	}

}

jsp:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    
    <title>Jsp</title>
    
  </head>
  
  <body>
    有以下资源供您下载:<br/>
    <c:forEach items="${requestScope.fileMap}" var="entry">
    	<!-- 下载的文件名防中文乱码,用url标签(标签知识待复习待查) -->
    	<c:url var="url" value="/servlet/DownloadServlet">
    		<c:param name="filename" value="${entry.value}"></c:param>
    	</c:url>
    	${entry.key }<a href="${url }">下载</a><br/>
    </c:forEach><!-- 去Servlet中获取文件绝对地址,设置下载相关,下载文件 -->
  </body>
</html>

下载处理Servlet:

package cn.xbai.servlet;

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

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

public class DownloadServlet extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		//要下载文件的磁盘全路径
		String filepath=request.getParameter("filename");
		//get参数,中文需要还原字节码重新解码成UTF-8
		filepath=new String(filepath.getBytes("iso8859-1"),"UTF-8");
		File file=new File(filepath);
		if(!file.exists()){
			request.setAttribute("message", "对不起,您要下载的资源已被删除");
			request.getRequestDispatcher("/message.jsp").forward(request, response);
			return;
		}
		
		String filename=filepath.substring(filepath.lastIndexOf("\\"));
		//通知浏览器以下载打开
		//文件名需要用URLEncoder编码
		response.setHeader("content-disposition", "attachment;filename="+URLEncoder.encode(filename,"UTF-8"));
		//获取文件流
		FileInputStream in=new FileInputStream(file);
		int len=0;
		byte buffer[]=new byte[1024];
		OutputStream out=response.getOutputStream();
		while((len=in.read(buffer))>0){
			out.write(buffer,0,len);
		}
		try {
			if(in!=null){
				in.close();
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		
		//String path=this.getServletContext().getRealPath("/download");
		
	}

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

		doGet(request, response);
	}

}


相关案例查看更多