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

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