精华内容
下载资源
问答
  • Android 编码解码的原理解析一.字符集1.字符集的由来2.字符集的演变(1)ISO-8859-1字符集(2)GB2312/GBK字符集(3)Unicode字符集(4)UTF-8编码方式二.编码解码1.编码和解码2.Java中的编码解码3.URL的解码编码三.android...

    一.字符集

    1.字符集的由来

    1. 计算机识别、处理、传递、存储数据,都是基于一个个的字节,一个字节有8为,每位都是0或1,即计算机是通过01字节流来处理数据的,而我们使用的各种字符,包括数字、符号、字母、中文等,都是需要通过字节来编码的,也就是用一些特定的字节标示一个唯一的字符。

    2. 计算机最早是按英语单字节字符设计的,即一个字节(8位能标示256个字符)就可以表示英文中所有的字符(包括字符、大小写字母、各种英文字符),也就是我们常说的ASCII字符集,该集合包括了上述的所有字符,并为每个字符指定唯一的号码,如:

      i.数字0:十进制号码是48,对应的二进制编码就是0011 0000

      ii.大写字母A:十进制号码是65,对应的二进制编码就是0100 0001

      iii.字符+:十进制号码是43,对应的二进制编码就是0010 1011

      这样一来,每一个英文字符,都会有唯一对应的二进制编码,无论数据(二进制流)传递到哪个系统上,它们都以ASCII字符集的编码来解码字节流,得到的就是正确的原始字符串数据;这也就是字符集的作用—包括一组字符,并为每个字符指定唯一的编码

    3. 如果全世界只有ASCII这一种字符集的话,其实编码解码这个概念就很简单了,但是全世界有很多很多语言,相应的就有很多很多的字符(远远大于256个),显然,这么多的字符,是一个字节无法表示全的,也就是说ASCII字符集不能涵盖全世界的字符,那么怎么办呢?扩展字符集呗,于是,就诞生了多种多样的字符集。。。

    2.字符集的演变

    (1)ISO-8859-1字符集

    ISO-8859-1字符集采用单字节,能够表示256个字符,兼容ASCII,可以支持大多数国家语言

    (2)GB2312/GBK字符集

    这些字符集是为了支持中文而产生的,使用单双字节变长编码对字符进行编码,英文字符使用单字节编码,兼容ASCII编码,对于中文使用双字节编码

    GBK是兼容GB2312的字符集,GB2312只支持简体中文,而GBK支持繁体中文

    (3)Unicode字符集

    ISO-8859-1不支持中文字符,GBK也不支持其他一些国家的字符,这就导致了使用GBK编码的程序到了其他一些国家系统上就出现乱码的情况,所以,世界需要一个通用完整的字符集来避免这些情况

    Unicode为目前最统一最全的字符集,它收录了世界上所有的字符,并为其每个字符指定了一个编码,其编码与上述字符集不兼容,是自己的一套编码系统,该字符集也是目前使用最广泛的一个字符集

    对于Unicode字符集来说,最重要的是它的编码实现方式:Unicode字符集只是容纳了所有字符并为其提供唯一的编码,但是不与其他编码兼容,而且对于英文也会占用多字节,对内存不友好,所以,Unicode有许多不同编码实现方式,所谓编码实现方式,就是将一些自定义的编码,能够映射到唯一的Unicode字符编码上,从而可以知道唯一的字符,而这些自定义的编码,可以想办法做成省内存的,兼容其他编码的;其中最常用的编码方式就是UTF-8了

    (4)UTF-8编码方式

    UTF(Unicode Transformation Format),顾名思义,UTF就是将Unicode编码转换成另一种编码的编码方式,常见的有UTF-8、UTF-7、UTF-16等,最常用的就是UTF-8了

    UTF-8是一种可变长字节的编码方式,它使用1-6个字节来对所有Unicode字符进行编码:因为对于ASCII英文字符,只需要单字节即可,如果使用Unicode的话就会使用两个字节,对内存不太友好,所以UTF-8相比Unicode的编码方式来说,可以大大的节省内存开销;对于其他字符比如中文,UTF-8通常是3个字节就可以表示一个中文字符

    UTF-8可变长字节的实现原理简介:如果只有一个字节则其最高二进制位为0;如果是多字节,其第一个字节从最高位开始,连续的二进制位值为1的个数决定了其编码的字节数,其余各字节均以10开头。
    在这里插入图片描述

    二.编码解码

    1.编码和解码

    上面介绍了字符集的概念,知道字符集是用来唯一标识每个字符的二进制字节的, 那么为什么要这么做呢?其实上面也已经说过了,因为计算机处理的数据实质上都是01bit流,8个bits就是一个字节,即计算机处理的的数据本质上都是字节流,也就是说,无论是字符还是字符串还是,最终都会以二进制字节流的形式进行处理,了解了这个,也就明白为什么要编码解码了吧?因为每个字符最终都是一串字节(一串01bits),也就是说我们传递数据前,应该将数据转换成一个一个的字节,这就叫编码;而接收方接收到的数据也就是一个一个的字节,也需要将这一串字节,转换成相应的字符(毕竟字符才是我们真正想要的数据),这就叫解码;再结合上面说的字符集:不同的程序可能使用不同的字符集,不同的字符集对于字符的编码方式不同,所以,我们不光要编码解码,还要指定编码解码所用的字符集,还要保证其编码解码用的字符集一样,否则,不同解析字节的规则解析相同的字节流,结果当然就可能出错了呗,变成了我们未知的字符了,这也就是乱码的形成原因!

    2.Java中的编码解码

    说了这么多,有些人可能会想:"我写程序使用、传递数据的时候,并没有过多的涉及到编码解码这块的处理,好像也并没有提示过我要编码解码。"下面我们来看看java中,我们通常在哪些地方使用到了编码解码,以及如何使用的:
    在这里插入图片描述
    来看输出:
    在这里插入图片描述
    由代码可知,字符串数据最终传递时,都需要转换成byte字节数组,而转换时,需要指定字符集编码方式(否则使用系统默认的),我们的网络传输等等,最终在底层传递是都是这样的

    1. 从输出可以看出,“我是test"这6个字符被转化成了10个字节的字节数组,由UTF-8编码规则可知,中文字符通常被表示为三个字节,所以[-26,-130,-111]代表"我”,[-26,-104,-81]代表"是",而英文字符符ASCII编码,所以116代表"t",101代表"e",115代表"t"

    2. 从输出可以看出,解码时也是需要指定字符集编码方式的,即utf-8根据自己的规则(上面说过),确定前三个字节为一个字符,并通过计算规则映射到Unicode中"我"的编码,于是前三个字节就解码为"我"字符,后面同理

    3. 是不是有人要问,Java也可以使用字符流读取,即InputStreamReader,可以一次性读取n个字符,那它是怎么知道几个字节为一个字符的呢?我们来看一看
      在这里插入图片描述
      再来看输出:
      在这里插入图片描述
      可见,我们构造Reader时,会指定编码方式的,而每个编码方式可以通过自己的编码规则来确定哪几个字节作为一个字符,比如ISO-8859-1为定长单字节编码,那么它解码时每次获取一个字节进行解码即可;而UTF-8为可变长编码,但是有规则(上面有介绍):每个字符的第一个字节连续前n位为1,那么这个字符就占n个字节。据此规则也可以顺利解码

    4. 以上就是我们日常使用java时容易忽视的编码解码,有人会问:"那解码编码规则使用不一样时,真的会导致乱码么?"这里就举一个例子,来解释一下:

      还是原来的输入"我是test"
      在这里插入图片描述
      再来看输出:
      在这里插入图片描述
      这结果是为什么呢?其实很简单,以utf-8进行编码,解码时,以iso-8859-1进行解码,那么iso-8859-1是定长单字节编码,所以取每一个字节进行解码,这些由utf-8生成的码在iso-8859-1的字符集编码中找到的字符肯定和utf-8不一样啊;而对于英文字符来说,这些字符集都满足ASCII编码规则,所以即使是不同的字符集对于这些字符的编码都是一致的;所以最后的"test"四个字符是没有问题的,而前面的两个中文字符(6个字节)就被解码成iso-8859-1字符集里对应的6个字符了

    3.URL的解码编码

    对于这个概念,其实很多人都不是很清楚,认为URL的编码解码和字符的编码解码是一回事,或者说是字符集编码方式之一,其实并不是这样的,字符集编码是计算机对于数据传递的一种统一方式,而Url的编码解码是针对于Url这种特殊数据传递所定义的一种规则而已。

    Url(统一资源定位符),大家都再熟悉不过了,是URI(统一资源标识符)的一种,有形如此类的格式:scheme://host:port/path?key1=value1&key2=value2,是用于干什么的呢?当然是定位到某个设备某个路径上的标识,再说通用点吧,就是我们请求后端的地址,并可以传递多个键值对参数的字符串,那么问题来了,它也是一个字符串,最终还是要编码成字节流传递到远端的,而这种格式中有些起着特殊作用的字符呢?比如/、?、=、&等等,这些字符也属于ASCII没问题,被编码成字节也没问题,但是,他们的作用是起分隔作用的,比如&分隔了每个键值对,=分隔了键和值,那如果我们的正常数据中(实际要传递的数据)有这些特殊字符呢,比如value1是“a=b”,value2是"a&b",那么这个Url的后半部分就变成了?key1=“a=b”&key2=“a&b”,如果按照原样进行编码解码,那么后端拿到的url字符串还是这样的,在取出参数和分隔键值对的时候就会有错了:按&分隔成的键值对就为key1=“a=b”,key2=“a,b”;再按=分隔成的键值就为<key1,"a>,<key2,"a>,这显然就酿成大祸了。。。

    所以,为了使Url访问时,能够保留这些特殊字符,并且Url希望使用安全字符(即不会造成混乱的字符,也就是使用通用的ASCII字符集的符号),就为Url设置了一套"编码解码"规则来达到这些要求:

    1. 对于字母、数字、一些ASCII中的字符不做处理

    2. 对于空格,容易造成歧义,转换为+(+会做(3)处理,不会影响)

    3. 对于其他字符,都使用&hex1hex2来转义,hex1hex2是该字符在对应的字符集编码方式中的编码对应的16进制数

    这样一来,整个Url就都是ASCII字符集里的安全字符,并且实际数据中的那些特殊字符都被转义了,我们来举个例子看的更明白些:

    原始Url:?kw="a=b"&exp="a&b"
    
    Url编码后的Url:?kw=%22a%3Db%22&exp=%22a%26b%22
    

    服务端在拿到Url是,就是编码后的Url,此时可以通过=&来取出键值对(因为特殊字符没有被编码),取出后,将每个key-value再进行Url的解码,就生成这样的键值对:

    <kw,"a=b">,<exp,"a&b">
    

    所以,Url的编码解码只是因为Url的一些可能造成歧义和错误的字符,而定义的一套转义规则,并不是和utf-8一样的一种编码方式,只是将字符转换成另一种字符而已。

    三.android中的编码解码

    (1)在Android中,数据的编码解码和java中其实一样(毕竟用的java。。。),如果相对Url应用编码解码,可以使用提供的URLEncoder和URLDecoder的相关方法,流程就是和上述介绍的URL解码编码规则类似

    URLEncoder

    private void appendEncoded(StringBuilder builder, String s, Charset charset, boolean isPartiallyEncoded) { 
            int escapeStart = -1; 
            for (int i = 0; i < s.length(); i++) { 
                char c = s.charAt(i); 
                if ((c >= 'a' && c <= 'z') 
                        || (c >= 'A' && c <= 'Z') 
                        || (c >= '0' && c <= '9') 
                        || isRetained(c) 
                        || (c == '%' && isPartiallyEncoded)) { 
                    if (escapeStart != -1) {
                        appendHex(builder, s.substring(escapeStart, i), charset);  //转义
                        escapeStart = -1; 
                    } 
                    if (c == '%' && isPartiallyEncoded) { 
                        // this is an encoded 3-character sequence like "%20" 
                        builder.append(s, i, i + 3); 
                        i += 2; 
                    } else if (c == ' ') { //空格换成+
                        builder.append('+'); 
                    } else { //原样保留
                        builder.append(c); 
                    } 
                } else if (escapeStart == -1) { 
                    escapeStart = i; 
                } 
            }
    }
    private static void appendHex(StringBuilder builder, String s, Charset charset) {         
    		for (byte b : s.getBytes(charset)) { //指定字符集编码
                appendHex(builder, b); 
            } 
        } 
     
    private static void appendHex(StringBuilder sb, byte b) { 
            sb.append('%'); //转换为%hex1hex2
            sb.append(Byte.toHexString(b, true)); 
    } 
    

    URLDecoder

    public static String decode(String s, boolean convertPlus, Charset charset, boolean throwOnFailure) { 
            if (s.indexOf('%') == -1 && (!convertPlus || s.indexOf('+') == -1)) { 
                return s; 
            } 
            StringBuilder result = new StringBuilder(s.length()); 
            ByteArrayOutputStream out = new ByteArrayOutputStream(); 
            for (int i = 0; i < s.length();) { 
                char c = s.charAt(i); 
                if (c == '%') { 
                    do { 
                        int d1, d2; 
                        if (i + 2 < s.length() 
                                && (d1 = hexToInt(s.charAt(i + 1))) != -1 
                                && (d2 = hexToInt(s.charAt(i + 2))) != -1) { 
                            out.write((byte) ((d1 << 4) + d2)); //%连同后两位16进制数字转换为字节写入字节数组(恢复)
                        } else if (throwOnFailure) { 
                            throw new IllegalArgumentException("Invalid % sequence at " + i + ": " + s); 
                        } else { 
                            byte[] replacement = "\ufffd".getBytes(charset); 
                            out.write(replacement, 0, replacement.length); 
                        } 
                        i += 3; 
                    } while (i < s.length() && s.charAt(i) == '%'); 
                    result.append(new String(out.toByteArray(), charset)); //按指定字符集解码为字符
                    out.reset(); 
                } else { 
                    if (convertPlus && c == '+') { //+恢复为空格
                        c = ' '; 
                    } 
                    result.append(c); 
                    i++; 
                } 
            } 
            return result.toString(); 
        } 
    

    (2)Android中另一种处理Url的方式就是使用Uri类,这也是重点要说的类
    在这里插入图片描述
    这是构建Uri的Builder类,我们可以看到,可以设置Uri的scheme,authority,path和query,而且这些属性大多都提供了两种方法,一种是encodedXxx,一种是xxx,顾名思义,前者是设置已经编码过得值,后者就是未编码过的值,而appendQueryParameter方法最常用,它设置的就是未编码过得值,那么为什么要区分呢?是基于Uri的实现机制的,Uri内部几乎所有的值都有编码未编码状态,即encoded和decoded状态,在使用时,也有两种方法,一种是getXxx,一种是getEncodedXxx,前者就是获取到decoded的相应属性,后者是获取encoded的相应属性
    在这里插入图片描述
    也就是说,Uri内部的属性,要么是编码过的,要么是未编码过的,取出时,也会进行相应的编码和解码,清楚这个很重要,否则可能会导致编码过的再编码,解码过的再解码的情况

    (3)下面就举一个容易掉进坑里的例子吧:

    i.隐式Uri跳转到一个页面,url为:mypro://www.mypro.com/mainAct?name=“鲜果100%店”,只需要使用Uri.parse(url)即可构建Uri

    public static Intent buildIntent(Context context, String url) {
        Intent intent = new Intent();
        Uri uri = Uri.parse(url);
        ...
        return intent;//后续startActivity(intent)即可
    }
    

    ii.我们来看Uri.parse方法

    public static Uri parse(String uriString) {
        return new StringUri(uriString);
    }
    private StringUri(String uriString) {
        this.uriString = uriString;//直接保存在stringUri变量中
    }
    

    iii.再来看我们如果使用uri.getQueryParameter(key)方法会发生什么

    public String getQueryParameter(String key) {
        ...
        final String query = getEncodedQuery();//获取的是encodedQuery
        ...
        final String encodedKey = encode(key, null);
        final int length = query.length();
        int start = 0;
        do {
            int nextAmpersand = query.indexOf('&', start);
            int end = nextAmpersand != -1 ? nextAmpersand : length;
    
            int separator = query.indexOf('=', start);
            if (separator > end || separator == -1) {
                separator = end;
            }
    
            if (separator - start == encodedKey.length()
                    && query.regionMatches(start, encodedKey, 0, encodedKey.length())) {
                if (separator == end) {
                    return "";
                } else {
                    String encodedValue = query.substring(separator + 1, end);//value现在是鲜果100%店,被当做了encoded状态了
                    return UriCodec.decode(encodedValue, true, StandardCharsets.UTF_8, false);//所以要decode:%hex1hex2解码,而100%后面不是16进制数,所以错误(崩溃与否看参数)
                }
            }
    
            // Move start to end of name.
            if (nextAmpersand != -1) {
                start = nextAmpersand + 1;
            } else {
                break;
            }
        } while (true);
        return null;
    }
     
    public String getEncodedQuery() {
        return getQueryPart().getEncoded();//获取queryPart对象
    }
     
    private Part getQueryPart() {
        return query == null
                ? query = Part.fromEncoded(parseQuery()) : query;//由url直接parse构建,queryPart为null,所以根据stringUri(传入的url)创建
    }
     
    private String parseQuery() {
        int qsi = uriString.indexOf('?', findSchemeSeparator());
        if (qsi == NOT_FOUND) {
            return null;
        }
    	...
        return uriString.substring(qsi + 1, fsi);//找到?后面的返回
    }
     
    static Part fromEncoded(String encoded) {
        return from(encoded, NOT_CACHED);//!!!将此queryString当成了encoded的
    }
    

    如上所述,问题就在于Uri.parse传入的url参数,被Uri默认当成的是encoded的了,而我们通常用于构建Uri的url,是decoded的;

    当getQueryParameter()时,Uri是拿到encoded的queryString然后进行decode解码,这就造成了对"鲜果100%店"这个已经decode过的字符串再度decode了,就导致了错误,所以在使用Uri的时候,要注意这点。

    展开全文
  • Java 字符串的编码解码

    千次阅读 2017-10-29 16:08:25
    结合别人的内容和自己的理解规范地整理出,做以笔记 一、认识编码 .编码:规定每个“字符”分别用一个...二、Java中常用的字符串的编码解码 1.将字符串转换成byte数组再恢复: byte[] getBytes(String charsetNam

    结合别人的内容和自己的理解规范地整理出,做以笔记

    一、认识编码

    .编码:规定每个“字符”分别用一个字节还是多个字节存储,用哪些字节来存储,这个规定就叫做“编码” 平常我们所说的“字符集”,比如:GB2312, GBK, JIS 等;

    二、Java中常用的字符串的编码解码

    1.将字符串转换成byte数组再恢复:

    byte[] getBytes(String charsetName)

    String(byte[] bytes, String charsetName)

    或getBytes()

    String(byte[] bytes)

    2.使用String sun.misc.BASE64Encode.encode(byte[] b)

    byte[] String sun.misc.BASE64Decode.decodeBuffer(String str)

    将字符串转换成byte[],再转换成ASCII码;恢复时做逆操作。

    在字符串转换成byte[]时,尽量用byte[] getBytes(String charsetName)方法,解码时也用相同的charsetName做参数,如果都不charsetName可能会导致一些中文字符不能正确解码。

    3.使用java.net.URLEncoder类和java.net.URLDecoder类

    它有static方法将字符串转换成‘pplication/x-www-form-urlencoded'格式便于在网络中传播

    形如‘%20%35'。

    三、系统的了解Java对字符串的编码和解码

     1. getBytes(charset) 这是 java 字符串处理的一个标准函数,其作用是将字符串所表示的字符按照 charset 编码,并以字节方式表示。注意字符串在 java 内存中总是按 unicode 编码存储的。当Java程序从输入流、文件或字符文字量等途径获得字符串时,均会做字符编码的转换,例如InputStreamReader 的构造函数中就需要指定编码方式,而对于从文件和字符文字量中获得字符串时,均采用系统默认的编码方式对字符数据进行解码。考虑下面一段代码: String str=”中”;

     

    ① byte[] bytes = str.getBytes();

     

    ② bytes = str.getBytes(“ISO-8859-1”);

    ③ 语句①:将一个只含有一个字符“中”的字符串文字量赋给 String 类的一个对象 str,字符文字量“中”是按照操作系统默认编码方式进行编码,在中文 windows 系统中通常是“GBK”,“中”在GBK编码中是0xD6D0,在将该字符赋给str时,Java会对该字符串进行编码转换,即将GBK编码方式的“中”转换成Unicode编码方式的“中”,Unicode编码方式“中”的编码是0x4E2D,所以str在程序运行期间在内存中的二进制表示成16进制就是0x4E2D。语句②:获得str字符串的二进制形式。getBytes(String encoding)方法需要指定编码方式,表示获得该字符串在何种编码方式中的二进制形式。此语句中没有设置参数,表示采用操作系统默认的编码方式,即此处获得的bytes是“中”在GBK编码中的二进制形式,即bytes[0]=0xD6, bytes[1]=0xD0。语句③:该语句与语句②的区别就是指定了编码方式,此处指定的是ISO-8859-1,即通常所说的Latin-1,该编码采用8bit对字符编码,所以编码空间中只有256个字符。该编码中只包含了基本的ASCII码和一些扩展的其它西欧字符,所以该字符集中不可能包含中文的“中”字,也就是说Java虚拟机无法在ISO-8859-1编码集中找到“中”字对应的编码,针对这种情况,就只返回一个问号(?,0x3f)字符,所以此时bytes.length只有1,且bytes[0]=0x3f。

     

    2.new String(byte[] bytes, String encoding) getBytes()方法从字符串获得二进制的字节数组。如果要从二进制的字节数组获得字符串,则就需要使用new String(byte[] bytes, String encoding)方法,该方法按照encoding编码方法对字节数组bytes中的二进制数组进行解析,生成一个新的字符串对象。

     

    byte[] bytes = {(byte)0xD6, (byte)0xD0, (byte)0x31};

     

    ① String str = new String(bytes);

     

    ② str = new String(bytes,”ISO-8859-1”);

     

     ③ 语句①:定义一个字节数组。语句②:将该字节数组中的二进制数据按照默认的编码方式(GBK)编码成字符串,我们知道GBK中0xD6 0xD0表示“中”,0x31表示字符“1”(GBK兼容ASCII,但不兼容ISO-8859-1除ASCII之外的部分),所以str得到的值是“中1”。语句③:该句用ISO-8859-1编码方式对该字节数据进行编码,由于在ISO-8859-1编码方式中一个字节会被解析成一个字符,所以该字节数组会被解释成包含三个字符的字符串,但由于在ISO-8859-1编码方式中没有对应0xD6和0xD0的字符,所以前两个字符会产生两个问号,由于0x31在ISO-8859-1编码中对应字符“1”(ISO-8859-1也兼容ASCII),所以此语句得到str的值是“??1”。


     3.setCharacterEncoding() 该函数用来设置http请求或者相应的编码。对于request,是指提交内容的编码,指定后可以通过getParameter()直接获得正确的字符串,如果不指定,则默认使用iso8859-1 编码,需要进一步处理。值得注意的是在执行setCharacterEncoding()之前,不能执行任何 getParameter()。而且,该指定只对POST方法有效,对GET方法无效。分析原因,应该是在执行第一个getParameter()的时候,java将会按照编码分析所有的提交内容,而后续的getParameter()不再进行分析,所以setCharacterEncoding()无效。而对于GET方法提交表单时,提交的内容在URL中,一开始就已经按照编码分析所有的提交内容,setCharacterEncoding()自然就无效。对于response,则是指定输出内容的编码,同时,该设置会传递给浏览器,告诉浏览器输出内容所采用的编码。


     四、页面编码页面编码

    页面的编码格式可以从两方面来说明:

    • 一是页面本身的编码格式,即以什么编码方式保存;
    • 二是客户端浏览器以什么编码格式显示页面。


    1. 页面保存编码格式

     

    1). HTML 页面的编码要看你保存文件时的编码选项,多数的网页编辑软件可以让你选择编码的类型,默认为本地编码,为了使网页减少编码的问题,最好保存为 UTF-8 编码格式。

     

     2). JSP 页面使用下列标签指定 JSP 源文件的编码格式,具体来说,我们在JSP源文件头上加入下面的一句即可: %@page[/email] pageEncoding="xxx"%>,xxx可以为GB2312,GBK,UTF-8(和MySQL不同,MySQL是 UTF8)等等,其默认值为ISO-8859-1。保存文件时的编码应该与xxx 一致。


    2. 页面显示编码(通知客户端浏览器用什么字符集编码显示页面)

     

     1). 在 HTML 中设置页面显示编码方式 使用标签设置页面显示编码

     

    2). 在 Servlet 中设置页面显示编码方式 使用 response.setContentType("text/html; charset=xxx");来指定生成的页面编码。

     

     3).在 JSP 中设置页面显示编码方式 使用设置页面显示编码。字符集的默认值为ISO-8859-1。



    五、jsp 编译过程(以tomcat为例)

     

    1.Tomcat 先将整个JSP页面的代码读取出来,写到一个新的JAVA文件中。在读取JSP文件时,tomcat会先去读取JSP文件的pageEncoding属性,然后按照pageEncoding指定的编码来读取JSP文件。如果pageEncoding没有指定,tomcat会使用contentType指定的字符集编码,如果contentType也没有指定,就使用默认的ISO-8859-1编码。

     

    2.Tomcat读取完JSP文件后,会用UTF-8编码将这些内容写道一个新的文件中,然后编译。

     

    3.当JSP文件显示的时候,会使用contentType中指定的MIME类型和charset。如果charset没有指定,就使用pageEncoding中指定的编码,如果pageEncoding也没有指定,就使用默认的ISO-8859-1编码。


    六、这样使我想起之前犯的错误
    原来是各自使用的编码集不相同导致的;

















    展开全文
  • 移动端图片_编码解码调研

    千次阅读 2016-07-21 09:54:51
    图片通常是移动端流量耗费最多的部分,并且占据着重要的视觉空间。合理的图片格式选用和优化可以为你节省带宽、提升视觉效果。...静态图片的编码解码 JPEG PNG WebP BPG 动态图片的编码解码 GIF APNG

    图片通常是移动端流量耗费最多的部分,并且占据着重要的视觉空间。合理的图片格式选用和优化可以为你节省带宽、提升视觉效果。在这篇文章里我会分析一下目前主流和新兴的几种图片格式的特点、性能分析、参数调优,以及相关开源库的选择。

    Index
    几种图片格式简介
    移动端图片类型的支持情况
    静态图片的编码与解码
    JPEG
    PNG
    WebP
    BPG
    动态图片的编码与解码
    GIF
    APNG
    WebP
    BPG
    动图性能对比


    几种图片格式的简介

    首先谈一下大家耳熟能详的几种老牌的图片格式吧:

    JPEG 是目前最常见的图片格式,它诞生于 1992 年,是一个很古老的格式。它只支持有损压缩,其压缩算法可以精确控制压缩比,以图像质量换得存储空间。由于它太过常见,以至于许多移动设备的 CPU 都支持针对它的硬编码与硬解码。

    PNG 诞生在 1995 年,比 JPEG 晚几年。它本身的设计目的是替代 GIF 格式,所以它与 GIF 有更多相似的地方。PNG 只支持无损压缩,所以它的压缩比是有上限的。相对于 JPEG 和 GIF 来说,它最大的优势在于支持完整的透明通道。

    GIF 诞生于 1987 年,随着初代互联网流行开来。它有很多缺点,比如通常情况下只支持 256 种颜色、透明通道只有 1 bit、文件压缩比不高。它唯一的优势就是支持多帧动画,凭借这个特性,它得以从 Windows 1.0 时代流行至今,而且仍然大受欢迎。

    在上面这些图片格式诞生后,也有不少公司或团体尝试对他们进行改进,或者创造其他更加优秀的图片格式,比如 JPEG 小组的 JPEG 2000、微软的 JPEG-XR、Google 的 WebP、个人开发者发布的 BPG、FLIF 等。它们相对于老牌的那几个图片格式来说有了很大的进步,但出于各种各样的原因,只有少数几个格式能够流行开来。下面三种就是目前实力比较强的新兴格式了:

    APNG 是 Mozilla 在 2008 年发布的一种图片格式,旨在替换掉画质低劣的 GIF 动画。它实际上只是相当于 PNG 格式的一个扩展,所以 Mozilla 一直想把它合并到 PNG 标准里面去。然而 PNG 开发组并没有接受 APNG 这个扩展,而是一直在推进它自己的 MNG 动图格式。MNG 格式过于复杂以至于并没有什么系统或浏览器支持,而 APNG 格式由于简单容易实现,目前已经渐渐流行开来。Mozilla 自己的 Firefox 首先支持了 APNG,随后苹果的 Safari 也开始有了支持, Chrome 目前也已经尝试开始支持 ,可以说未来前景很好。

    WebP 是 Google 在 2010 年发布的图片格式,希望以更高的压缩比替代 JPEG。它用 VP8 视频帧内编码作为其算法基础,取得了不错的压缩效果。它支持有损和无损压缩、支持完整的透明通道、也支持多帧动画,并且没有版权问题,是一种非常理想的图片格式。借由 Google 在网络世界的影响力,WebP 在几年的时间内已经得到了广泛的应用。看看你手机里的 App:微博、微信、QQ、淘宝、网易新闻等等,每个 App 里都有 WebP 的身影。Facebook 则更进一步,用 WebP 来显示聊天界面的贴纸动画。

    BPG 是著名程序员 Fabrice Bellard 在去年 (2014年) 发布的一款超高压缩比的图片格式。这个程序员有些人可能感觉面生,但说起他的作品 FFmpeg、QEMU 大家想必是都知道的。BPG 使用 HEVC (即 H.265) 帧内编码作为其算法基础,就这点而言,它毋庸置疑是当下最为先进的图片压缩格式。相对于 JP2、JPEG-XR、WebP 来说,同等体积下 BPG 能提供更高的图像质量。另外,得益于它本身基于视频编码算法的特性,它能以非常小的文件体积保存多帧动画。 Fabrice Bellard 聪明的地方在于,他知道自己一个人无法得到各大浏览器厂商的支持,所以他还特地开发了 Javascript 版的解码器,任何浏览器只要加载了这个 76KB 大小的 JS 文件,就可以直接显示 BPG 格式的图片了。目前阻碍它流行的原因就是 HEVC 的版权问题和它较长的编码解码时间。尽管这个图片格式才刚刚发布一年,但已经有不少厂子开始试用了,比如阿里腾讯

    移动端图片类型的支持情况

    目前主流的移动端对图片格式的支持情况如何呢?我们分别来看一下 Android 和 iOS 目前的图片编解码架构吧:

    mobile_image_arch

    Android 的图片编码解码是由 Skia 图形库负责的,Skia 通过挂接第三方开源库实现了常见的图片格式的编解码支持。目前来说,Android 原生支持的格式只有 JPEG、PNG、GIF、BMP 和 WebP (Android 4.0 加入),在上层能直接调用的编码方式也只有 JPEG、PNG、WebP 这三种。目前来说 Android 还不支持直接的动图编解码。

    iOS 底层是用 ImageIO.framework 实现的图片编解码。目前 iOS 原生支持的格式有:JPEG、JPEG2000、PNG、GIF、BMP、ICO、TIFF、PICT,自 iOS 8.0 起,ImageIO 又加入了 APNG、SVG、RAW 格式的支持。在上层,开发者可以直接调用 ImageIO 对上面这些图片格式进行编码和解码。对于动图来说,开发者可以解码动画 GIF 和 APNG、可以编码动画 GIF。

    两个平台在导入第三方编解码库时,都多少对他们进行了一些修改,比如 Android 对 libjpeg 等进行的调整以更好的控制内存,iOS 对 libpng 进行了修改以支持 APNG,并增加了多线程编解码的特性。除此之外,iOS 专门针对 JPEG 的编解码开发了 AppleJPEG.framework,实现了性能更高的硬编码和硬解码,只有当硬编码解码失败时,libjpeg 才会被用到。

    静态图片的编码与解码

    由于我目前主要是做 iOS 开发,所以下面的性能评测都是基于 iPhone 的,主要测试代码可以在这里看到测试素材很少,只有两个:

    dribbble512_pngcrushlena512_weibo

    第一张是Dribbble 的 Logo,包含 Alpha 通道,用于测试简单的、图形类的图像。
    第二张经典的 Lena 图,用于测试照片类的、具有丰富细节的图像。
    每个图像都有 64x64、128x128、256x256、512x512 四种分辨率。
    测试素材过少可能导致某些测试不够准确,但作为参考大致是没问题的。

    JPEG

    目前比较知名的 JPEG 库有以下三个:

        libjpeg开发时间最早,使用最广泛的 JPEG 库。由于 JPEG 标准过于复杂和模糊,并没有其他人去实现,所以这个库是 JPEG 的事实标准。

        libjpeg-turbo一个致力于提升编解码速度的 JPEG 库。它基于 libjpeg 进行了改造,用 SIMD 指令集 (MMX、SSE2、NEON) 重写了部分代码,官网称相对于 libjpeg 有 24 倍的性能提升。

        MozJPEG 一个致力于提升压缩比的 JPEG 库。它是 Mozilla 在 2014 年发布的基于 libjpeg-turbo 进行改造的库,相对于 libjpeg 有 5% ~ 15%  的压缩比提升,但相应的其编码速度也慢了很多。

    除了上面这三个库,苹果自己也开发了一个 AppleJPEG,但并没有开源。其调用了芯片提供的 DSP 硬编码和硬解码的功能。虽然它不如上面这三个库功能完善,但其性能非常高。在我的测试中,其编解码速度通常是 libjpeg-turbo 的 1~2 倍。可惜的是,目前开发者并不能直接访问这个库。

    下面是 ImageIO (AppleJPEG/libpng) 在 iPhone 6 上的编解码性能:

    jpeg_bench_dribbble jpeg_bench_lena

    可以看到,JPEG 编码中 quality 越小,图片体积就越小,质量越也差,编码时间也越短。解码时间并没有很大的差距,可能是其大部分时间消耗在了函数调用、硬件调用上。苹果在自己的相册 Demo 中提供的 quality 的默认值是 0.9,在这个值附近,图像质量和体积、编码解码时间之间都能取得不错的平衡。

    PNG

    相对于 JPEG 来说,PNG 标准更为清晰和简单,因此有很多公司或个人都有自己的 PNG 编码解码实现。但目前使用最广的还是 PNG 官方发布的 libpng 库。iOS 和 Android 底层都是调用这个库实现的 PNG 编解码。

    下面是 PNG 在 iPhone 6 上的编解码性能:

    jpeg_png_bench

    可以看到,在编解码图形类型(颜色少、细节少)的图片时,PNG 和 JPEG 差距并不大;但是对于照片类型(颜色和细节丰富)的图片来说,PNG 在文件体积、编解码速度上都差 JPEG 不少了。

    和 JPEG 不同,PNG 是无损压缩,其并不能提供压缩比的选项,其压缩比是有上限的。目前网上有很多针对 PNG 进行优化的工具和服务,旨在提升 PNG 的压缩比。下面是常见的几个 PNG 压缩工具的性能对比:

    png_tools_bench

    pngcrush 是 Xcode 自带的 PNG 压缩工具,相对于设计师用 Photoshop 生成的图片来说,它能取得不错的压缩效果。ImageOptim 则更进一步,对每张图用多种缩算法进行比对,选择压缩比更高的结果,进一步缩小了文件体积。TinyPNG.com 相对于其他工具来说,压缩比高得不像话。它启用了类似 GIF 那样的颜色索引表对 PNG 进行压缩,所以会导致颜色丰富的图片丢失掉一部分细节。如果使用 TinyPNG 的话,最好在压缩完成后让设计师看一下颜色效果是否可以接受。

    WebP

    WebP 标准是 Google 定制的,迄今为止也只有 Google 发布的 libwebp 实现了该的编解码 。 所以这个库也是该格式的事实标准。

    WebP 编码主要有几个参数:

        lossless: YES:有损编码 NO:无损编码。WebP 主要优势在于有损编码,其无损编码的性能和压缩比表现一般。

        quality: [0~100] 图像质量,0表示最差质量,文件体积最小,细节损失严重,100表示最高图像质量,文件体积较大。该参数只针对有损压缩有明显效果。Google 官方的建议是 75,腾讯在对 WebP 评测给出的建议也是 75。在这个值附近,WebP 能在压缩比、图像质量上取得较好的平衡。

        method: [0~6] 压缩比,0表示快速压缩,耗时短,压缩质量一般,6表示极限压缩,耗时长,压缩质量好。该参数也只针对有损压缩有明显效果。调节该参数最高能带来 20% ~ 40% 的更高压缩比,但相应的编码时间会增加 520 倍。Google 推荐的值是 4

    对于编码无损图片来说,quality=0, method=0~3 是相对来说比较合适的参数,能够节省编码时间,同时也有不错的压缩比。无损编码图片,quality=75, method=2~4 是比较合适的参数,能在编码时间、图片质量、文件体积之间有着不错的平衡。

    WebP 解码有三个参数:

        use_threads: 是否启用 pthread 多线程解码。该参数只对宽度大于 512 的有损图片起作用。开启后内部会用多线程解码,CPU 占用会更高,解码时间平均能缩短 10%~20%。

        bypass_filtering: 是否禁用滤波。该参数只对有损图片起作用,开启后大约能缩短 5%~10% 的解码时间,但会造成一些颜色过渡平滑的区域产生色带(banding)。

        no_fancy_upsampling: 是否禁用上采样。该参数只对有损图片起作用。在我的测试中,开启该参数后,解码时间反而会增加 525%,同时会造成一些图像细节的丢失,线条边缘会增加杂色,显得不自然。

    通常情况下,这三个参数都设为 NO 即可,如果要追求更高的解码速度,则可以尝试开启 use_threads 和 bypass_filtering 这两个参数。而 no_fancy_upsampling 在任何情况下都没必要开启。

    由于 WebP 测试数据较多,这里只贴一下 512x512 大小的一部分测试结果,感兴趣的可以看文章结尾处的 Excel 附件。

    webp_bench

    对于简单的图形类型的图像(比如 App 内的各种 UI 素材),WebP 无损压缩的文件体积和解码速度某些情况下已经比 PNG 还要理想了,如果你想要对 App 安装包体积进行优化,可以尝试一下 WebP。

    对于复杂的图像(比如照片)来说,WebP 无损编码表现并不好,但有损编码表现却非常棒。相近质量的图片解码速度 WebP 相距 JPEG 也已经相差不大了,而文件压缩比却能提升不少。

    BPG

    BPG 是目前已知最优秀的有损压缩格式了,它能在相同质量下比 JPEG 减少 50% 的体积。下面是经典的 Lena 图的对比,你也可以在这里看到大量其他图片的 BPG、JPEG、JPEG2000、JPEG-XR、WebP 压缩效果的在线对比,效果非常明显。

    bpg_demo

    BPG 目前只有作者发布的 libbpg 可用。但作者基于 libbpg 编译出了一个 Javascript 解码器,很大的扩展了可用范围。bpg 可以以无损和有损压缩两种方式进行编码,有损压缩时可以用 quality 参数控制压缩比,可选范围为 0~51,数值越大压缩比越高。通常来说,25 附近是一个不错的选择,BPG 官方工具默认值是 28。

    libbpg 目前并没有针对 ARM NEON 做优化,所以其在移动端的性能表现一般。下面是 iPhone 6 上的性能测试:

    bpg_bench

    由于 bpg 编码时间太长,我并没有将数据放到表格里。可以看到相同质量下,BPG 的解码速度还是差 JPEG 太多,大约慢了 35 倍。目前来说,BPG 适用于那些对流量非常敏感,但对解码时间不敏感的地方。从网上的新闻来看,手机淘宝和手机QQ都已经有所尝试,但不清楚他们是否对 BPG 解码进行了优化。

    动态图片的编码与解码

    动图在网络上非常受欢迎,它近似视频,但通常实现简单、文件体积小,应用范围非常广泛。动图的始祖是 GIF,它自 Windows 1.0 时代就在互联网上流行开来,直到今天仍然难以被其他格式取代。尽管它非常古老,但其所用的原理和今天几种新兴格式几乎一样。

    下面是一张 GIF 格式的 QQ 大表情:

    bench_gif_demo

    这张表情由 6 幅静态图构成,每幅图片有一定的存活时间,连贯播放就形成了动画:

    bench_gif_demo1

    这几张图中,大部分内容是相近的,为了压缩文件体积,通常动图格式都支持一些特殊的方式对相似图片进行裁剪,只保留前后帧不同的部分:

    bench_gif_demo2

    在解码动图时,解码器通常采用所谓"画布模式"进行渲染。想象一下:播放的区域是一张画布,第一帧播放前先把画布清空,然后完整的绘制上第一帧图;播放第二帧时,不再清空画布,而是只把和第一帧不同的区域覆盖到画布上,就像油画的创作那样。

    像这样的第一帧就被称为关键帧(即 I 帧,帧内编码帧),而后续的那些通过补偿计算得到的帧被称为预测编码帧(P帧)。一个压缩的比较好的动图内,通常只有少量的关键帧,而其余都是预测编码帧;一个较差的压缩工具制作的动图内,则基本都是关键帧。不同的动图压缩工具通常能得到不同的结果。

    除此之外,动图格式通常有更为详细的参数控制每一帧的绘制过程,下面是 GIF/APNG/WebP 通用的几个参数:

    Disposal Method (清除方式) 
       Do Not Dispose:把当前帧增量绘制到画布上,不清空画布。
       Restore to Background:绘制当前帧之前,先把画布清空为默认背景色。
       Restore to Previous:绘制下一帧前,把先把画布恢复为当前帧的前一帧

    Blend Mode (混合模式) 
       Blend None: 绘制时,全部通道(包含Alpha通道)都会覆盖到画布,相当于绘制前先清空画布的指定区域。
       Blend over:绘制时,Alpha 通道会被合成到画布,即通常情况下两张图片重叠的效果。

    上面这些技术,就是常见动图格式的基础了,下面分别介绍一下不同动图格式的特点。

    GIF

    GIF 缺陷非常明显:它通常只支持 256 色索引颜色,这导致它只能通过抖动、差值等方式模拟较多丰富的颜色;它的 Alpha 通道只有 1 bit,这意味着一个像素只能是完全透明或者完全不透明。

    gif_apng_demo

    上面这是腾讯博客里的一张演示图,可以看到 GIF 由于 Alpha 通道的问题,产生了严重的"毛边"现象。目前通常的解决方案是在图片的边缘加一圈白边,以减轻这种视觉效果:

    gif_wrong_demo

    可以仔细观察一下 QQ、微信等 App 里面的动画表情,几乎每个表情都被一圈白边所环绕,不得不说是一种很无奈的解决方案。

    GIF 的制作工具有很多,但效果好、压缩比高的工具非常少。对于已经制作好的 GIF 来说,用 imagemagick 处理一下可以把文件体积压缩不少。如果需要将视频转为 GIF,Cinemagraph Pro 是个不错的傻瓜化工具。这里有一篇文章介绍如何用 ffmpeg 压缩 GIF,虽然参数调节有点麻烦,但效果非常理想。

    下面是没有经过优化的 GIF 和经过 ffmpeg 优化编码的 GIF,可以看到差距非常大。

    bbb-trans bbb-nodither

    APNG

    APNG 目前并没有被 PNG 官方所接受,所以 libpng 并不能直接解码 APNG。但由于 APNG 只是基于 PNG 的一个简单扩展,所以在已经支持 PNG 的平台上,可以很轻松的用少量代码实现 APNG 的编解码。Chromium 为了支持 APNG 播放,只增加了不到 600 行代码我自己也用大概 500 行 C 代码实现了一个简单的 APNG 编解码工具。另外,在支持 canvas 的浏览器上,可以用 apng-canvas 直接显示 APNG 动画。APNG 压缩最好的工具目前是 apngasm,大部分图形化工具比如腾讯的 iSparta 都是基于这个工具开发的。

    就目前而言, APNG 是 GIF 最好的替代了:实现简单,可用范围广,压缩比不错,显示效果好。

    WebP

    WebP 在 2010 年 发布时并没有支持动图。2012 年 libwebp v0.2 的时候,Google 才开始尝试支持动画,但其实现有很多问题,性能也非常差,以至于 Chrome 团队一直都没有接受。直到 2013 年,libwebp v0.4 时,动画格式才稳定下来才被 Chrome 所接受。

    WebP 动图实际上是把多个单帧 WebP 数据简单打包到一个文件内,而并不是由单帧 WebP 扩展而来,以至于动图格式并不能向上兼容静态图。如果要支持动图,首先在编译 libwebp 时需要加上 demux 模块,解码 WebP 时需要先用 WebPDemuxer 尝试拆包,之后再把拆出来的单帧用 WebPDecode 解码。为了方便编译,我写了个脚本用于打包 iOS 的静态库,加入了 mux 和 demux 模块。

    Google 提供了两个简单的命令行工具用于制作动图:gif2webp 能把 GIF 转换为 WebP, webpmux 能把多个 WebP 图片打包为动态图,并且有着很多参数可以调节。这两个工具对相近帧的压缩并不太理想,以至于有的情况下压缩比还不如 APNG,但除此以外也没有其他什么更好的工具可以用了。

    BPG

    BPG 本身是基于 HEVC (H.265) 视频编码的,其最开始设计时就考虑到了动图的实现。由于它充分利用了 HEVC 的高压缩比和视频编码的特性,其动图压缩比远超其他格式。这里这里有几张 BPG 动图示例,可以看到相同质量下 BPG 动图只有 APNG/WebP/GIF 几十分之一的大小。

    我在这里写了个简单的利用 libbpg 解码动图的方法,如有需要可以参考下。

    动图性能对比

    我把下面这张 GIF 分别转为 WebP、APNG、BPG 动图,并在 iPhone 6 上对其所有帧进行解码。

    gif_ermilio

    评测结果如下:

    anim_bench

    APNG 在文件体积上比 GIF 略有优势,解码时间相差不多。WebP 在体积和解码时间上都具有较大的优势。BPG 在体积上优势最大,但解码时间也最长。这么看来,APNG 和 WebP 都是不错的选择,而 BPG 还有待性能优化。

    最后做一个小广告:如果你是 iOS 平台的开发者,可以试试我开发的 YYWebImage,它支持 APNG、WebP、GIF 动图的异步加载与播放、编码与解码,支持渐进式图像加载,可以替代 SDWebImage、PINRemoteImage、FLAnimatedImage 等开源库。

    评测数据

    上面提到的所有评测数据表格:image_benchmark.xlsx 推荐用 Excel 打开查看。
    展开全文
  • python之字符编码解码

    千次阅读 2014-01-08 23:21:29
     字符编码就是计算机中用二进制表示的内容在屏幕上显示的字符的一种映射,不同的编码提供了不同的映射关系,不同的字符编码需要用对应的解码方式,当你给出一段序列,文本,你就要给出相应的密钥来破解这段编码,...
            用VS做C++开发的人,肯定遇到过将工程的字符集在Multibyte与Unicode之间互相转换的经历,CString,char*的转换也是家常便饭,L,_T这些宏的使用,MultiByteToWideChar()和WideCharToMultiByte(),W2A()和A2W(),虽然用起来很丑陋,但是都可以解决问题。前段时间使用python写一些脚本的时候,总是遇到UnicodeEncodeError的错误,故想了解python对于字符的处理。

    字符编码

            字符编码就是计算机中用二进制表示的内容在屏幕上显示的字符的一种映射,不同的编码提供了不同的映射关系,不同的字符编码需要用对应的解码方式,当你给出一段序列,文本,你就要给出相应的密钥来破解这段编码,否则就会出现我们经常看到的字符乱码,无法解析等一些问题。常见的字符集有ASCII字符集、GB2312字符集、BIG5字符集、GB18030字符集、Unicode字符集等。一开始源于西文字符集及其编码形式的ASCII字符集,当计算机大行其道时,人们逐渐发现,ASCII字符集里可怜的127个字符已经不能再满足他们的需求了,于是人们开始打一个字节的后面的128个字符的主意了,于是欧美大陆扩展出了256字符的编码,这对于拉丁语系的文字来讲够用,但是对于亚洲的一些文字,却差太远了,于是我们想出了新的方法,扩展字节为多字节字符集,于是出现了各自为战的局面,大家都扩展字符集,GB2312字符集、BIG5字符集、GB18030字符集等等,于是在不同的语言上面就不能正常翻译了,同一个多字节码在不同的标准下表示的是不同的字符,大一统的时代终于来临了,Unicode编码横空出世。

    对于字符编码的历史,发展,以及标准,引用《字符编码详解及由来(UNICODE,UTF-8,GBK)》一段通俗的讲解,文章来源已找不到出处:
    “一直对字符的各种编码方式懵懵懂懂,什么ANSI、UNICODE、UTF-8、GB2312、GBK、DBCS、UCS……是不是看的很晕,假如您细细的阅读本文你一定可以清晰的理解他们。Let's go!         
        很久很久以前,有一群人,他们决定用8个可以开合的晶体管来组合成不同的状态,以表示世界上的万物。他们看到8个开关状态是好的,于是他们把这称为"字节"。
        再后来,他们又做了一些可以处理这些字节的机器,机器开动了,可以用字节来组合出很多状态,状态开始变来变去。他们看到这样是好的,于是它们就这机器称为"计算机"。
        开始计算机只在美国用。八位的字节一共可以组合出256(2的8次方)种不同的状态。 
        他们把其中的编号从0开始的32种状态分别规定了特殊的用途,一但终端、打印机遇上约定好的这些字节被传过来时,就要做一些约定的动作。遇上00x10, 终端就换行,遇上0x07, 终端就向人们嘟嘟叫,例如遇上0x1b, 打印机就打印反白的字,或者终端就用彩色显示字母。他们看到这样很好,于是就把这些0x20以下的字节状态称为"控制码"。 
        他们又把所有的空格、标点符号、数字、大小写字母分别用连续的字节状态表示,一直编到了第127号,这样计算机就可以用不同字节来存储英语的文字了。大家 看到这样,都感觉很好,于是大家都把这个方案叫做 ANSI 的"Ascii"编码(American Standard Code for Information Interchange,美国信息互换标准代码)。当时世界上所有的计算机都用同样的ASCII方案来保存英文文字。           
        后来,就像建造巴比伦塔一样,世界各地的都开始使用计算机,但是很多国家用的不是英文,他们的字母里有许多是ASCII里没有的,为了可以在计算机保存他 们的文字,他们决定采用127号之后的空位来表示这些新的字母、符号,还加入了很多画表格时需要用下到的横线、竖线、交叉等形状,一直把序号编到了最后一 个状态255。从128到255这一页的字符集被称"扩展字符集"。从此之后,贪婪的人类再没有新的状态可以用了,美帝国主义可能没有想到还有第三世界国 家的人们也希望可以用到计算机吧!
        等中国人们得到计算机时,已经没有可以利用的字节状态来表示汉字,况且有6000多个常用汉字需要保存呢。但是这难不倒智慧的中国人民,我们不客气地把那些127号之后的奇异符号们直接取消掉, 
        规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到 0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,我们还把数学符号、罗马希腊的 字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符了。 
          中国人民看到这样很不错,于是就把这种汉字方案叫做 "GB2312"。GB2312 是对 ASCII 的中文扩展。 
          但是中国的汉字太多了,我们很快就就发现有许多人的人名没有办法在这里打出来,特别是某些很会麻烦别人的国家领导人。于是我们不得不继续把 GB2312 没有用到的码位找出来老实不客气地用上。 
          后来还是不够用,于是干脆不再要求低字节一定是127号之后的内码,只要第一个字节是大于127就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字 符集里的内容。结果扩展之后的编码方案被称为 GBK 标准,GBK 包括了 GB2312 的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号。 
          后来少数民族也要用电脑了,于是我们再扩展,又加了几千个新的少数民族的字,GBK 扩成了GB18030。从此之后,中华民族的文化就可以在计算机时代中传承了。 
          中国的程序员们看到这一系列汉字编码的标准是好的,于是通称他们叫做 "DBCS"(Double Byte Charecter Set 双字节字符集)。在DBCS系列标准里,最大的特点是两字节长的汉字字符和一字节长的英文字符并存于同一套编码方案里,因此他们写的程序为了支持中文处 理,必须要注意字串里的每一个字节的值,如果这个值是大于127的,那么就认为一个双字节字符集里的字符出现了。那时候凡是受过加持,会编程的计算机僧侣 们都要每天念下面这个咒语数百遍:
          "一个汉字算两个英文字符!一个汉字算两个英文字符……"
          因为当时各个国家都像中国这样搞出一套自己的编码标准,结果互相之间谁也不懂谁的编码,谁也不支持别人的编码,连大陆和台湾这样只相隔了150海里,使用 着同一种语言的兄弟地区,也分别采用了不同的 DBCS 编码方案——当时的中国人想让电脑显示汉字,就必须装上一个"汉字系统",专门用来处理汉字的显示、输入的问题,但是那个台湾的愚昧封建人士写的算命程序 就必须加装另一套支持 BIG5  编码的什么"倚天汉字系统"才可以用,装错了字符系统,显示就会乱了套!这怎么办?而且世界民族之林中还有那些一时用不上电脑的穷苦人民,他们的文字又怎 么办?
          真是计算机的巴比伦塔命题啊! 
          正在这时,大天使加百列及时出现了——一个叫 ISO(国际标谁化组织)的国际组织决定着手解决这个问题。他们采用的方法很简单:废了所有的地区性编码方案,重新搞一个包括了地球上所有文化、所有字母 和符号的编码!他们打算叫它"Universal Multiple-Octet Coded Character Set",简称 UCS, 俗称 "UNICODE"。 
          UNICODE 开始制订时,计算机的存储器容量极大地发展了,空间再也不成为问题了。于是 ISO 就直接规定必须用两个字节,也就是16位来统一表示所有的字符,对于ascii里的那些“半角”字符,UNICODE 包持其原编码不变,只是将其长度由原来的8位扩展为16位,而其他文化和语言的字符则全部重新统一编码。由于"半角"英文符号只需要用到低8位,所以其高 8位永远是0,因此这种大气的方案在保存英文文本时会多浪费一倍的空间。
          这时候,从旧社会里走过来的程序员开始发现一个奇怪的现象:他们的strlen函数靠不住了,一个汉字不再是相当于两个字符了,而是一个!是的,从 UNICODE 开始,无论是半角的英文字母,还是全角的汉字,它们都是统一的"一个字符"!同时,也都是统一的"两个字节",请注意"字符"和"字节"两个术语的不 同,“字节”是一个8位的物理存贮单元,而“字符”则是一个文化相关的符号。在UNICODE 中,一个字符就是两个字节。一个汉字算两个英文字符的时代已经快过去了。    
          从前多种字符集存在时,那些做多语言软件的公司遇上过很大麻烦,他们为了在不同的国家销售同一套软件,就不得不在区域化软件时也加持那个双字节字符集咒 语,不仅要处处小心不要搞错,还要把软件中的文字在不同的字符集中转来转去。UNICODE 对于他们来说是一个很好的一揽子解决方案,于是从 Windows NT 开始,MS 趁机把它们的操作系统改了一遍,把所有的核心代码都改成了用 UNICODE 方式工作的版本,从这时开始,WINDOWS 系统终于无需要加装各种本土语言系统,就可以显示全世界上所有文化的字符了。 
        但是,UNICODE 在制订时没有考虑与任何一种现有的编码方案保持兼容,这使得 GBK 与UNICODE 在汉字的内码编排上完全是不一样的,没有一种简单的算术方法可以把文本内容从UNICODE编码和另一种编码进行转换,这种转换必须通过查表来进行。 
        如前所述,UNICODE 是用两个字节来表示为一个字符,他总共可以组合出65535不同的字符,这大概已经可以覆盖世界上所有文化的符号。如果还不够也没有关系,ISO已经准备 了UCS-4方案,说简单了就是四个字节来表示一个字符,这样我们就可以组合出21亿个不同的字符出来(最高位有其他用途),这大概可以用到银河联邦成立 那一天吧!
        UNICODE 来到时,一起到来的还有计算机网络的兴起,UNICODE 如何在网络上传输也是一个必须考虑的问题,于是面向传输的众多 UTF(UCS Transfer Format)标准出现了,顾名思义,UTF8就是每次8个位传输数据,而UTF16就是每次16个位,只不过为了传输时的可靠性,从UNICODE到 UTF时并不是直接的对应,而是要过一些算法和规则来转换。
        受到过网络编程加持的计算机僧侣们都知道,在网络里传递信息时有一个很重要的问题,就是对于数据高低位的解读方式,一些计算机是采用低位先发送的方法,例 如我们PC机采用的 INTEL 架构,而另一些是采用高位先发送的方式,在网络中交换数据时,为了核对双方对于高低位的认识是否是一致的,采用了一种很简便的方法,就是在文本流的开始时 向对方发送一个标志符——如果之后的文本是高位在位,那就发送"FEFF",反之,则发送"FFFE"。不信你可以用二进制方式打开一个UTF-X格式的 文件,看看开头两个字节是不是这两个字节?
        讲到这里,我们再顺便说说一个很著名的奇怪现象:当你在 windows 的记事本里新建一个文件,输入"联通"两个字之后,保存,关闭,然后再次打开,你会发现这两个字已经消失了,代之的是几个乱码!呵呵,有人说这就是联通之所以拼不过移动的原因。
        其实这是因为GB2312编码与UTF8编码产生了编码冲撞的原因。 
        从网上引来一段从UNICODE到UTF8的转换规则:
            Unicode 
            UTF-8
            0000 - 007F 
            0xxxxxxx
            0080 - 07FF 
            110xxxxx 10xxxxxx
            0800 - FFFF 
            1110xxxx 10xxxxxx 10xxxxxx
        例如"汉"字的Unicode编码是6C49。6C49在0800-FFFF之间,所以要用3字节模板:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 1100 0100 1001,将这个比特流按三字节模板的分段方法分为0110 110001 001001,依次代替模板中的x,得到:1110-0110 10-110001 10-001001,即E6 B1 89,这就是其UTF8的编码。 
        而当你新建一个文本文件时,记事本的编码默认是ANSI,如果你在ANSI的编码输入汉字,那么他实际就是GB系列的编码方式,在这种编码下,"联通"的内码是: 
                c1 1100 0001 
                aa 1010 1010 
                cd 1100 1101 
                a8 1010 1000       
        注意到了吗?第一二个字节、第三四个字节的起始部分的都是"110"和"10",正好与UTF8规则里的两字节模板是一致的,于是再次打开记事本时,记事 本就误认为这是一个UTF8编码的文件,让我们把第一个字节的110和第二个字节的10去掉,我们就得到了"00001 101010",再把各位对齐,补上前导的0,就得到了"0000 0000 0110 1010",不好意思,这是UNICODE的006A,也就是小写的字母"j",而之后的两字节用UTF8解码之后是0368,这个字符什么也不是。这就 是只有"联通"两个字的文件没有办法在记事本里正常显示的原因。
        而如果你在"联通"之后多输入几个字,其他的字的编码不见得又恰好是110和10开始的字节,这样再次打开时,记事本就不会坚持这是一个utf8编码的文件,而会用ANSI的方式解读之,这时乱码又不出现了。  
        好了,终于可以回答NICO的问题了,在数据库里,有n前缀的字串类型就是UNICODE类型,这种类型中,固定用两个字节来表示一个字符,无论这个字符是汉字还是英文字母,或是别的什么。
        如果你要测试"abc汉字"这个串的长度,在没有n前缀的数据类型里,这个字串是7个字符的长度,因为一个汉字相当于两个字符。而在有n前缀的数据类型里,同样的测试串长度的函数将会告诉你是5个字符,因为一个汉字就是一个字符。”

    对于各种字符编码的详细介绍,可以参见字符集和字符编码(Charset & Encoding)和《Dive into Python》中第四章字符串的内容

    python中的str和Unicode


    在python中,指定编码格式的字符按照按照指定格式解析,未指定按照默认编码格式解析,在Python2里默认的编码方式为ascii,在Python 3中默认编码方式为utf-8,可以通过sys.getdefaultencoding()而获得编码格式
    import sys
    print sys.getdefaultencoding()

    通常意义的字符串(str)和Unicode字符串都是basestring的子类,字符编码得到的都是字节流,
    在每个源码文件声明编码(encoding declaration)
    #!/usr/bin/python3
    # -*- coding: utf-8 -*-


    python的encode和decode方法


    Python中,想要将某字符串解码为对应的Unicode,但是所使用的编码类型和字符串本身的编码不匹配
    Python中,打印字符串时,字符串本身的编码,与输出终端中所用编码不匹配
    Python中,打印含某些特殊字符的Unicode类型字符串,但是输出终端中字符编码集中不包含这些特殊字符

    utf-8的字符串解析出来的字节流如下,输出时,用系统默认的格式进行解析输出,得到如下的字符串
    >>> str1 = u"中文"
    >>> str2 = "中文"
    >>> str1.encode('utf-8')
    '\xc3\x96\xc3\x90\xc3\x8e\xc3\x84'
    >>> print str1.encode('utf-8')
    脰脨脦脛
    >>> str1.encode('gb2312')
    >>> str1.encode('gbk')
    
    Traceback (most recent call last):
      File "<pyshell#13>", line 1, in <module>
        str1.encode('gbk')
    UnicodeEncodeError: 'gbk' codec can't encode character u'\xd6' in position 0: illegal multibyte sequence
    >>> str2.encode('utf-8')
    
    Traceback (most recent call last):
      File "<pyshell#14>", line 1, in <module>
        str2.encode('utf-8')
    UnicodeDecodeError: 'ascii' codec can't decode byte 0xd6 in position 0: ordinal not in range(128)

    Python对于Unicode有内建的encode()和decode()方法,decode()和encode()内建函数接收一个字符串对应的解码/编码后的字符串,decode()和encode()都可以应用于常规字符串和Unicode()字符串,字符串在Python内部的表示是unicode编码,因此,在做编码转换时,通常需要以unicode作为中间编码,即先将其他编码的字符串解码(decode)成unicode,再从unicode编码(encode)成另一种编码。decode的作用是将其他编码的字符串转换成unicode编码,如str1.decode('gb2312'),表示将gb2312编码的字符串str1转换成unicode编码。 encode的作用是将unicode编码转换成其他编码的字符串,如str2.encode('gb2312'),表示将unicode编码的字符串str2转换成gb2312编码。

    因此,转码的时候一定要先搞明白,字符串str是什么编码,然后decode成unicode,然后再encode成其他编码。代码中字符串的默认编码与代码文件本身的编码一致。

    如:s='中文' 如果是在utf8的文件中,该字符串就是utf8编码,如果是在gb2312的文件中,则其编码为gb2312。这 种情况下,要进行编码转换,都需要先用decode方法将其转换成unicode编码,再使用encode方法将 其转换成其他编码。通常,在没有指定特定的编码方式时,都是使用的系统默认编码创建的代码文件
    展开全文
  • if self.clones == nil then self:createClones() end --参考子函数createClones,作用是复制LSTM网络 assert(seq:size(1) == self.seq_length + 1) local img = self.img_proj:forward(input[1]) --构建了图像...
  • 数据结构哈夫曼编码解码课程设计

    千次阅读 多人点赞 2018-12-17 20:13:07
    数据结构课程设计 课题名称:哈夫曼编码/译码器 专业:班级: 姓名:(组长)学号:成绩: 姓名:(组员)学号:成绩: 姓名:(组员)学号:成绩: 指导教师: ...
  • 在上篇博客中LZ介绍了前面两种场景(IO、内存)中的java编码解码操作,其实在这两种场景中我们只需要在编码解码过程中设置正确的编码解码方式一般而言是不会出现乱码的。对于我们从事java开发的人而言,其实最容易也...
  • 常用的加密解密及编码解码算法

    万次阅读 2013-01-04 13:41:17
    常用的加密解密及编码解码算法 http://iamcaihuafeng.blog.sohu.com/144299832.html 今天从Google Reader上发现了一篇文章,关于URL编码,写得还算不错,对自己也有一些启示。顺便把常用的加密及...
  • 2.假如文件头 没有#coding: utf-8那么文件当前的编码是什么? 3.print是相当于读入么?print出来的结果的编码跟什么有关?这几个问题会在看完以下知识后进行解答一、基本概念:1.字节:字节是计算机中数据存储的基本单位...
  • 在java中主要有四个场景需要进行编码解码操作:  (1):I/O操作  (2):内存  (3):数据库  (4):javaWeb I/O操作 在前面LZ就提过乱码问题无非就是转码过程中编码格式的不统一产生的,比如编码时采用UTF...
  • 很显然,压缩编码处理是必须的。一段刚刚捕获的60分钟原始视屏可能达到2G,经过压缩处理可以减至500MB左右,一张单反照片可能有5MB,经过压缩之后只有400KB,而质量不会发生明显的损失。 hadoop面临的情况也是一样...
  • java中字符串的编码解码问题

    千次阅读 2014-03-21 13:22:08
    均会做字符编码的转换,例如InputStreamReader 的构造函数中就需要指定编码方式,而对于从文件和字符文字量中获得字符串时,均采用系统默认的编码方式对字符数据进行解码。考虑下面一段代码: String str=”中”; ...
  • JSP编码解码格式(杂)

    千次阅读 2010-08-28 16:18:00
    因为生成的页面内容还必须经过网络传输才能到达用户的浏览器,而这个过程中也存在着编码的转换问题。如果不对response进行设置,容器会使用默认的编码方式进行传送,而客户端浏览器也可能获取不到编码信息,最终用户...
  • PYTHON编码解码

    千次阅读 2018-10-21 14:04:40
    从第一天接触Python就对解码编码的问题很困惑,最近在学习网络数据爬虫,又遇到了一系列的解码编码问题,处理中文编码问题真的好麻烦,这次真的需要好好填坑了。 详细内容下面的文章: Python字符串的编码解码...
  • Huffman编码解码_C语言实现

    万次阅读 多人点赞 2017-04-16 10:22:22
    哈夫曼编码(Huffman Coding),是一种编码方式,哈夫曼编码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,该方法完全依据字符出现概率来构造异...本次实验用C语言实现了Huffman编码器、Huffman解码器。
  • Faster RCNN网络解码过程和编码过程 1、解码过程(预测过程) 解码过程分为两部分: 对先验框进行调整的解码过程 对建议框进行调整的解码过程 1.对先验框进行调整 RPN网络的输出结果进行解码,对先验框进行调整...
  • web请求编码解码

    2014-11-15 00:36:21
    UTF-8英文1字节中文3字节,在编码效率和编码安全性之间做了平衡,适合网络传输,是理想的中文编码方式 ISO-8859-1只能只能表示256个西欧字符,中文变为?可能是错误使用了ISO-8859-1而丢失信息导致的。 GBK英文1...
  • tensorflow实现自编码网络

    千次阅读 2018-09-01 22:58:17
    一、自编码网络编码,又称自编码器(autoencoder),也是神经...自编码器是一个3层或大于3层的神经网络,它的作用是将输入样本压缩到隐藏层,然后解压,在输出端还原输入样本。最终输出层神经元数量等于输入层神经...
  • Js中进行utf-8的编码解码

    千次阅读 2018-07-19 16:09:30
    JavaScript本身可通过charCodeAt方法得到一个字符的Unicode编码,并...在网络上传输一般采用UTF-8编码,JavaScript本身没有提供此类方法。不过有一个简便的办法来实现UTF-8的编码解码。 Web要求URL的查询字符串采...
  • 基本概念有信息交换就会产生编码、传输、解码三个过程。编码是信息从一种形式转变成另一种形式的过程,正如人类的语言通过声带编码,转换成声波。解码编码的逆函数,耳膜接收声波,通过脑神经解码成人类文化所能...
  • 视频压缩编码解码

    千次阅读 2007-11-14 14:00:00
    引言 视频具有一系列的优点,如直观性、确切性、高效性、广泛性等等,但是信息量太大,要是视频得到有效的应用,必须首先解决视频压缩编码问题,其次解决压缩后视频质量的保证问题。这两者是相互矛盾的两个方面,...
  • 编码网络

    千次阅读 2018-11-13 11:40:33
    编码器是一个3层或大于3层的神经网络,将输入表达式x编码为一个新的表达式y,然后再将y解码回x。这是一个非监督学习算法,使用反向传播算法来训练网络使输出等于输入。 图中,虚线蓝色框内就是一个自编码器模型...
  • 人脸关键点检测-递归编解码网络

    千次阅读 2016-09-05 11:44:34
    下图是文章中使用到的编解码网络,是一个全卷积网络:前半部分卷积表示编码,后半部分反卷积表示解码。作者提到使用全卷积,是因为可以得到同样尺寸的输出,这样便于进行递归操作。 由图可以看出,input是3...
  • Base64编码及其作用

    千次阅读 2018-02-23 16:28:01
    最重要的是 1.便于网络传输。 2.不可见性。...编码是 公开的,比如下面要介绍的Base 64编码,任何人都可以解码;而加密则相反,你只希望自己或者特定的人才可以对内容进行解密。 (二)Base 64编码...
  • 一、详解编码问题 1.为什么要编码 我们知道,计算机内部,所有信息最终都是一个二进制值。每一个二进制位(bit)有0和1两种状态,限定位数的二进制可以有多种不同的状态,每一个状态可以对应特定的信息。例如一个...
  • 本文描述灵派编码编码器如何把网络解码并通过HDMI, VGA, SDI口输出
  • 编码器-解码器LSTM是一个循环神经网络,旨在解决序列到序列问题,有时称为seq2seq。 序列到序列的预测问题具有挑战性,因为输入和输出序列中的项数可能会发生变化。 例如,文本翻译和学习执行程序就是seq2seq问题...
  • 关于视频编码器的作用详细介绍

    千次阅读 2017-07-10 10:25:00
    视频编码器在监控系统中的作用是:将前端摄像机、拾音器等设备采集到的音视频信号经过压缩编码,通过网络传输到后端的监控中心,并由PC机上运行的相应的解码软件或硬件解码设备进行解码播放。 比如:通过放置...
  •     在这项工作中,我们提出了一种利用图卷积网络和深集(deep sets),基于分子结构的药物药物相互作用检测方法。与传统的GCN相比,我们提出了一个更具鉴别性的卷积层,在不丧失捕获复杂相互作用的能力的情况下...
  • 用Keras LSTM构建编码器-解码器模型

    千次阅读 2020-11-01 11:22:39
    基础知识:了解本文之前最好拥有关于循环神经网络(RNN)和编解码器的知识。 本文是关于如何使用Python和Keras开发一个编解码器模型的实用教程,更精确地说是一个序列到序列(Seq2Seq)。在上一个教程中,我们开发了...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 52,845
精华内容 21,138
关键字:

编码解码网络作用