softmanfly的专栏

尊重个人劳动成果,转载请声明:

乱码是软件开发中的常见问题,程序员如果对码不清楚的话经常会被各种码搞得晕头转向,我在开发一个JavaWeb项目时也遇到了一些乱码的问题,百思不得其解,最后通过阅读源码和一定的猜测,对编码和乱码问题有了一定的心得体会,故记录下来(如果只想深入了解Java中的编码相关内容的话可以直接看红字下面的部分):

问题来由:在http get方法中url后面添加query string,使用中文作为参数,提交到服务器导致乱码,比如一个请求:

:8080/register?userName=小波波,最后到达服务器时调用request.getParameter("userName")就变成了乱码。

问题分析:

网上查阅了一大堆的方法,有设置charsetEncoding的,有设置URIEncoding的,有new String(params.getBytes("ISO-8859-1"), "utf-8")转化一下的;

然后我就开始各种尝试,发现有时候能扭转乱码,,有时候变成其他的乱码,虽然能够解决一时的问题,但是还是不明白本质的原因,所以我决定先从源码入手,看看getParameter函数是如何取得我们需要的参数的值得,以下是getParameter函数的源码:

public String getParameter(String name) {parseParameters();Object value = parameters.get(name);if (value == null)return (null);else if (value instanceof String[])return (((String[]) value)[0]);else if (value instanceof String)return ((String) value);elsereturn (value.toString());}发现是在parseParamters里面进行的处理:<span style="font-size:18px;">if (parsedParams) { return;//如果已经解码过,就直接返回}parameters = new HashMap<>();parameters = copyMap(getRequest().getParameterMap());mergeParameters();</span><pre name="code" class="java"><span style="font-family: Arial, Helvetica, sans-serif;"><span style="font-size:14px;">parsedParams = true;</span></span>发现应该是继续在mergeParamters里进行处理:private void mergeParameters() {if ((queryParamString == null) || (queryParamString.length() < 1))return;HashMap<String, String[]> queryParameters = new HashMap<>();String encoding = getCharacterEncoding();if (encoding == null)encoding = "ISO-8859-1";RequestUtil.parseParameters(queryParameters, queryParamString,encoding);//问题出在这Iterator<String> keys = parameters.keySet().iterator();while (keys.hasNext()) {String key = keys.next();Object value = queryParameters.get(key);if (value == null) {queryParameters.put(key, parameters.get(key));continue;}queryParameters.put(key, mergeValues(value, parameters.get(key)));}parameters = queryParameters;}大致理解以下这个函数,应该是在将通过get方法中?后面的query string携带的参数和通过addParameter方法添加的参数进行合并(merge),所以乱码问题应该来自对queryParameters的处理,也就是RequestUtil.parseParameters函数打开这个函数: public static void parseParameters(Map<String,String[]> map, String data,String encoding) {if ((data != null) && (data.length() > 0)) {// use the specified encoding to extract bytes out of the// given string so that the encoding is not lost.byte[] bytes = null;try {bytes = data.getBytes(B2CConverter.getCharset(encoding));parseParameters(map, bytes, encoding);} catch (UnsupportedEncodingException uee) {if (log.isDebugEnabled()) {log.debug(sm.getString("requestUtil.parseParameters.uee",encoding), uee);}}}}发现首先将data转化为对应编码的bytes数组(data就是query string也就是?后面的userName=小波波),然后再调用 parseParameters(map, bytes, encoding);对byte数组进行处理,那么再次进入这个parseParameters函数:public static void parseParameters(Map<String,String[]> map, byte[] data,String encoding) throws UnsupportedEncodingException {Charset charset = B2CConverter.getCharset(encoding);if (data != null && data.length > 0) {int ix = 0;int ox = 0;String key = null;String value = null;while (ix < data.length) {byte c = data[ix++];switch ((char) c) {case '&':value = new String(data, 0, ox, charset);if (key != null) {putMapEntry(map, key, value);key = null;}ox = 0;break;case '=':if (key == null) {key = new String(data, 0, ox, charset);ox = 0;} else {data[ox++] = c;}break;case '+':data[ox++] = (byte)' ';break;case '%':data[ox++] = (byte)((convertHexDigit(data[ix++]) << 4)+ convertHexDigit(data[ix++]));break;default:data[ox++] = c;}}//The last value does not end in '&'. So save it now.if (key != null) {value = new String(data, 0, ox, charset);putMapEntry(map, key, value);}}大家可以清楚的看到这里就是在进行实际的解析,分别得到参数的key(userName)和value(小波波),然后将其放入到一个Map<Key,Value>中,我们注意到这一行: value = new String(data, 0, ox, charset);这里就是真正将data数组中关于小波波的部分按照charset转化成String value,所以乱码问题的出现应该是这个charset的问题!从上面的源码中我们可以知道charset值是通过这样来设置的:String encoding = getCharacterEncoding();if (encoding == null)encoding = "ISO-8859-1";那么把charset设置成什么编码才不会导致乱码呢?要想搞清楚这个问题还真不容易,你必须得对编码有一个比较清晰的了解,以下内容才是本文的精华,能够让你对Java中的编码有一个很好的认识,以及为什么上面源码中会多次出现ISO8859-1这个编码种类,它有什么特点能够让他在众多的编码方式中脱颖而出成为Java源码中多次用来当做默认的编码:

首先我们来看看Java中的String类,String类理解了,玩转Java中的编码就不是难事了。

String类中有2个比较常用的操作:

一个是str.getBytes(String charsetName)

一个是new String(s.getBytes("GBK"), "UTF-8");

你挤进地铁时,西藏的山鹰一直盘旋云端,

softmanfly的专栏

相关文章:

你感兴趣的文章:

标签云: