java中的编码基础

一:编码和解码

计算机按照字节存储,一个字节8位(8bit).一个字节的表示范围是0-255.但是我们需要表示的符号远远多于255,那么就需要新的数据结构,在Java中就是字符类型char(2个字节表示一个字符).从字符到字节需要编码,反之需要解码.在java中编码解码使用的是String类,Charset类

  1. 常用的编码格式
    ASCII,GBK,GB2312,UTF-8,UTF-16,ISO8859-1

  2. 需要编码转换的场景
    一般而言在字节和字符之间的转换往往就需要编码和解码
    Io(磁盘io,网络io)
    内存中字符字节转换

    中文编码可以选择GBK,但是最佳方案是UTF-8可变字节节约空间,效率在网络传输方面也很好.UTF-16是jvm的内存编码效率高,但固定双字节会牺牲一些空间.综合而言UTF-8很适合作为编码方案

  3. 在文件中输入文字,存储的实际上是对应编码的二进制

  4. java内存中一个字符char,使用两个字节16bit存储,并且是唯一的(UTF-16的编码)

  5. 浏览器发出请求到达服务器,服务器处理完成并响应,这个过程涉及的编码
    浏览器发出请求,url,请求参数,cookie等涉及编码问题,达到服务端,服务端处理过程中可能会读取本地文件,远程数据库等涉及编码问题.处理完成需要编码然后返回结果,浏览器解码展示

一:JVM系统所使用的编码

通过System.out.println(System.getProperty(“file.encoding”));可以查看,这个编码方式是jvm的系统编码,再eclipse中可以通过以下方式设置
在这里插入图片描述
也可以通过使用java指令启动运行时指定:
在这里插入图片描述
在String.getBytes();方法中默认使用的编码格式就是utf-8,如果指定了file.encoding,那么使用file.encoding
String类的getBytes()源码如下:

 public byte[] getBytes() {
      return StringCoding.encode(value, 0, value.length);
  }

继续往下跟:

 static byte[] encode(char[] ca, int off, int len) {
      String csn = Charset.defaultCharset().name();
      try {
          // use charset name encode() variant which provides caching.
          return encode(csn, ca, off, len);
      } catch (UnsupportedEncodingException x) {
          warnUnsupportedCharset(csn);
      }
      try {
          return encode("ISO-8859-1", ca, off, len);
      } catch (UnsupportedEncodingException x) {
          // If this code is hit during VM initialization, MessageUtils is
          // the only way we will be able to get any kind of error message.
          MessageUtils.err("ISO-8859-1 charset not available: "
                           + x.toString());
          // If we can not find ISO-8859-1 (a required encoding) then things
          // are seriously wrong with the installation.
          System.exit(1);
          return null;
      }
  }

其中第一行代码: String csn = Charset.defaultCharset().name();如果file.encoding不为空那么使用其作为charset,否则默认使用UTF-8

 public static Charset defaultCharset() {
      if (defaultCharset == null) {
          synchronized (Charset.class) {
              String csn = AccessController.doPrivileged(
                  new GetPropertyAction("file.encoding"));
              Charset cs = lookup(csn);
              if (cs != null)
                  defaultCharset = cs;
              else
                  defaultCharset = forName("UTF-8");
          }
      }
      return defaultCharset;
  }

getBytes(charset)得到的就是字符串在指定的charset下的字节数组

二:jvm的内存中字符以unicode作为字符集,优化的utf-16进行编码存储

三:java.util.Properties解析

从Properties的load方法:可以看出主要用来解析xml,解析properties.在解析properties文件时可以使用字节流和字符流
在这里插入图片描述

1:解析peoperties文件

首先有几点说明:

  1. eclipse的Properties File Editor编辑器默认把所有输入转义为unicode的字符码对应的16进制,所以在这里输入一个中文实际上输入的是16进制unicode码
  2. 在iso8859-1,gbk,UTF-8中兼容ascii码,即数字和字母使用单字节,而utf-16中字母是双字节
  3. 以下是java api中关于Properties类的说明中的一段话解释了第1点:
    在这里插入图片描述
    1>字节流

        用法如下:从2.properties文件中读取test属性

        File file2 = new File("file/2.properties");
		FileInputStream fileInputStream = new FileInputStream(file2);
		Properties properties = new Properties();
		properties.load(fileInputStream);
		System.out.println(properties.getProperty("test"));

        看如下源码:
load方法:

public synchronized void load(InputStream inStream) throws IOException {
        load0(new LineReader(inStream));
    }

new LineReader(inStream)创建读取文件的内部类,持有流的引用具体结构如下:
在这里插入图片描述
接着跟进:在以下代码中主要做了以下事情:从配置文件每次读取一行数据,通过&0xff保证扩展为int类型时(32位)后8位不变,强制转换成char,相当于在原有字节左边补8个零.然后默认按照unicode字符码的方式解析,最后将key-value存入hanshtable中(实际内部是map)
关于&0xff的解释参考:
https://blog.csdn.net/leonwang_fly/article/details/47422235
https://blog.csdn.net/cherry609195946/article/details/41957783
https://www.cnblogs.com/think-in-java/p/5527389.html
https://blog.csdn.net/qq_26459233/article/details/83749028

 private void load0 (LineReader lr) throws IOException {
        char[] convtBuf = new char[1024];
        int limit;
        int keyLen;
        int valueStart;
        char c;
        boolean hasSep;
        boolean precedingBackslash;

        while ((limit = lr.readLine()) >= 0) {
            c = 0;
            keyLen = 0;
            valueStart = limit;
            hasSep = false;

            //System.out.println("line=<" + new String(lineBuf, 0, limit) + ">");
            precedingBackslash = false;
            while (keyLen < limit) {
                c = lr.lineBuf[keyLen];
                //need check if escaped.
                if ((c == '=' ||  c == ':') && !precedingBackslash) {
                    valueStart = keyLen + 1;
                    hasSep = true;
                    break;
                } else if ((c == ' ' || c == '\t' ||  c == '\f') && !precedingBackslash) {
                    valueStart = keyLen + 1;
                    break;
                }
                if (c == '\\') {
                    precedingBackslash = !precedingBackslash;
                } else {
                    precedingBackslash = false;
                }
                keyLen++;
            }
            while (valueStart < limit) {
                c = lr.lineBuf[valueStart];
                if (c != ' ' && c != '\t' &&  c != '\f') {
                    if (!hasSep && (c == '=' ||  c == ':')) {
                        hasSep = true;
                    } else {
                        break;
                    }
                }
                valueStart++;
            }
            String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf);
            String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf);
            put(key, value);
        }
    }

关键代码1:          lr.readLine()从文件读取一行,最重要的代码是 c = (char) (0xff & inByteBuf[inOff++]);读取一个字节然后在高位补齐8个零转成char这样不会改变字符的值

 int readLine() throws IOException {
            int len = 0;
            char c = 0;
            //  跳过空行
            boolean skipWhiteSpace = true;
            // 注释行
            boolean isCommentLine = false;
            // 换行
            boolean isNewLine = true;
            boolean appendedLineBegin = false;
            boolean precedingBackslash = false;
            boolean skipLF = false;

            while (true) {
                // 读取一行到字节码,读取到空行或者注释行直接返回
                if (inOff >= inLimit) {
                    inLimit = (inStream==null)?reader.read(inCharBuf)
                                              :inStream.read(inByteBuf);
                    inOff = 0;
                    if (inLimit <= 0) {
                        if (len == 0 || isCommentLine) {
                            return -1;
                        }
                        return len;
                    }
                }
                
                // 这个地方将原来一个字节左边补8个零存入char中
                if (inStream != null) {
                    //The line below is equivalent to calling a
                    //ISO8859-1 decoder.
                    c = (char) (0xff & inByteBuf[inOff++]);
                } else {
                    c = inCharBuf[inOff++];
                }
                if (skipLF) {
                    skipLF = false;
                    if (c == '\n') {
                        continue;
                    }
                }
                // 跳过空行
                if (skipWhiteSpace) {
                    if (c == ' ' || c == '\t' || c == '\f') {
                        continue;
                    }
                    if (!appendedLineBegin && (c == '\r' || c == '\n')) {
                        continue;
                    }
                    skipWhiteSpace = false;
                    appendedLineBegin = false;
                }
                if (isNewLine) {
                    isNewLine = false;
                    if (c == '#' || c == '!') {
                        isCommentLine = true;
                        continue;
                    }
                }

                if (c != '\n' && c != '\r') {
                    lineBuf[len++] = c;
                    if (len == lineBuf.length) {
                        int newLength = lineBuf.length * 2;
                        if (newLength < 0) {
                            newLength = Integer.MAX_VALUE;
                        }
                        char[] buf = new char[newLength];
                        System.arraycopy(lineBuf, 0, buf, 0, lineBuf.length);
                        lineBuf = buf;
                    }
                    //flip the preceding backslash flag
                    if (c == '\\') {
                        precedingBackslash = !precedingBackslash;
                    } else {
                        precedingBackslash = false;
                    }
                }
                else {
                    // reached EOL
                    if (isCommentLine || len == 0) {
                        isCommentLine = false;
                        isNewLine = true;
                        skipWhiteSpace = true;
                        len = 0;
                        continue;
                    }
                    if (inOff >= inLimit) {
                        inLimit = (inStream==null)
                                  ?reader.read(inCharBuf)
                                  :inStream.read(inByteBuf);
                        inOff = 0;
                        if (inLimit <= 0) {
                            return len;
                        }
                    }
                    if (precedingBackslash) {
                        len -= 1;
                        //skip the leading whitespace characters in following line
                        skipWhiteSpace = true;
                        appendedLineBegin = true;
                        precedingBackslash = false;
                        if (c == '\r') {
                            skipLF = true;
                        }
                    } else {
                        return len;
                    }
                }
            }
        }
    }

取出一行存储到char[] lineBuf = new char[1024];中
现在遍历lineBuf找到=的位置,左边是key的结束位置,右边是value的开始位置
在下面的代码中,解析key,value

 String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf);
 String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf);
  private String loadConvert (char[] in, int off, int len, char[] convtBuf) {
        if (convtBuf.length < len) {
            int newLen = len * 2;
            if (newLen < 0) {
                newLen = Integer.MAX_VALUE;
            }
            convtBuf = new char[newLen];
        }
        char aChar;
        char[] out = convtBuf;
        int outLen = 0;
        int end = off + len;

        while (off < end) {
            aChar = in[off++];
            if (aChar == '\\') {
                aChar = in[off++];
                if(aChar == 'u') {
                    // Read the xxxx
                    int value=0;
                    for (int i=0; i<4; i++) {
                        aChar = in[off++];
                        switch (aChar) {
                          case '0': case '1': case '2': case '3': case '4':
                          case '5': case '6': case '7': case '8': case '9':
                             value = (value << 4) + aChar - '0';
                             break;
                          case 'a': case 'b': case 'c':
                          case 'd': case 'e': case 'f':
                             value = (value << 4) + 10 + aChar - 'a';
                             break;
                          case 'A': case 'B': case 'C':
                          case 'D': case 'E': case 'F':
                             value = (value << 4) + 10 + aChar - 'A';
                             break;
                          default:
                              throw new IllegalArgumentException(
                                           "Malformed \\uxxxx encoding.");
                        }
                     }
                    out[outLen++] = (char)value;
                } else {
                    if (aChar == 't') aChar = '\t';
                    else if (aChar == 'r') aChar = '\r';
                    else if (aChar == 'n') aChar = '\n';
                    else if (aChar == 'f') aChar = '\f';
                    out[outLen++] = aChar;
                }
            } else {
                out[outLen++] = aChar;
            }
        }
        return new String (out, 0, outLen);
    }

读取每一个字符,然后按照unicode字符码的规则\u+4个16进制字符,拼接成原始的unicode字符.由于jvm内部是unicode+UTF-16,所有可以直接显示成中文等字符
至此解析完成

put(key, value);
存入hashtable中

从这个解析过程可以得出一些结论:
1:在eclipse上使用properties编辑器,无论怎么编写(中英文都支持),只要文件编码是兼容ascii码,读取就不会乱码(UTF-8,GBK,ISO8859-1)
2:在本地自己编写properties文件时,使用字节流不支持中文
3:在本地自己编写时,可以把内容(中文)转义成unicode字符码,然后文件保存为(UTF-8,GBK,ISO8859-1这几个兼容ascii码)读取不会乱码

    2>字符流

这里使用字符流可以指定编码格式,和properties文件保持一致即可

2:解析xml

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章