從Java String實例來理解ANSI、Unicode、BMP、UTF等編碼概念

来源:http://www.cnblogs.com/salted-fish/archive/2016/01/17/5117590.html
-Advertisement-
Play Games

一、前言 一切的謎都解開了!在寫這篇隨筆之前,我的心情只能用金田一每次破案後的這句臺詞來表達。 其實從開始寫Java代碼以來,遇到過無數次亂碼與轉碼問題,比如從文本文件讀入到String出現亂碼,JSP獲取HTTP請求參數出現亂碼等問題,由於這些問題很常見,遇到的時候隨手百度一下一般都可以順...


一、前言

  一切的謎都解開了!在寫這篇隨筆之前,我的心情只能用金田一每次破案後的這句臺詞來表達。  

  其實從開始寫Java代碼以來,遇到過無數次亂碼與轉碼問題,比如從文本文件讀入到String出現亂碼,JSP獲取HTTP請求參數出現亂碼等問題,由於這些問題很常見,遇到的時候隨手百度一下一般都可以順利解決。也曾嘗試過去把概念理清楚,但網上眾說紛紜,內容繁雜,又不願意花精力去看標準文檔,所以問題擱置了很久。

  前兩天同學與我談起一個Java源文件的編碼問題(這問題在最後一個實例分析),從這個問題入手拉扯出了一連串的問題,然後我們一邊查資料一邊討論,直到深夜,終於在一篇博客中找到了關鍵性線索,解決了所有的疑惑,以前沒有理解的語句都能解釋清楚了。因此我決定用這篇隨筆,記錄我對一些編碼問題的理解以及實驗的結果。

  下麵有些概念是我自己結合實際的理解,如果有誤,請一定不吝指正。

二、概念總結

  早期,互聯網還沒有發展起來,電腦僅用於處理一些本地的資料,所以很多國家和地區針對本土的語言設計了編碼方案,這種與區域相關的編碼統稱為ANSI編碼(因為都是對ANSI-ASCII碼的擴展)。但是他們沒有事先商量好怎麼相互相容,而是自己搞自己的,這樣就埋下了編碼衝突的禍根,比如大陸使用的GB2312編碼與臺灣使用的Big5編碼就有衝突,同樣的兩個位元組,在兩種編碼方案里表示的是不同的字元,隨著互聯網的興起,一個文檔里經常會包含多種語言,電腦在顯示的時候就遇到麻煩了,因為它不知道這兩個位元組到底屬於哪種編碼。

  這樣的問題在世界上普遍存在,因此重新定義一個通用的字元集,為世界上所有字元進行統一編號的呼聲不斷高漲。

  由此Unicode碼應運而生,它為世界上所有字元進行了統一編號,由於它可以唯一標識一個字元,所以字體也只需要針對Unicode碼進行設計就行了。但Unicode標准定義的是一個字元集,而沒有規定編碼方案,也就是說它僅僅定義了一個個抽象的數字與其對應的字元,而沒有規定具體怎麼存儲一串Unicode數字,真正規定怎麼存儲的是UTF-8、UTF-16、UTF-32等方案,所以帶有UTF開頭的編碼,都是可以直接通過計算和Unicode數值(Code Point,代碼點)進行轉換的。顧名思義,UTF-8就是8位長度為基本單位編碼,它是變長編碼,用1~6個位元組來編碼一個字元(因為受Unicode範圍的約束,所以實際最大隻有4位元組);UTF-16是16位為基本單位編碼,也是變長編碼,要麼2個位元組要麼4個位元組;UTF-32則是定長的,固定4位元組存儲一個Unicode數。

  其實我以前一直對Unicode有點誤解,在我的印象中Unicode碼最大隻能到0xFFFF,也就是最多只能表示 2^16 個字元,在仔細看了維基百科之後才明白,早期的UCS-2編碼方案確實是這樣,UCS-2固定使用兩個位元組來編碼一個字元,因此它只能編碼BMP(基本多語言平面,即0x0000-0xFFFF,包含了世界上最常用的字元)範圍內的字元。為了要編碼Unicode大於0xFFFF的字元,人們對UCS-2編碼進行了拓展,創造了UTF-16編碼,它是變長的,在BMP範圍內,UTF-16與UCS-2完全一致,而BMP之外UTF-16則使用4個位元組來存儲。

  為了方便下麵的描述,先交代一下代碼單元(Code Unit)的概念,某種編碼的基本組成單位就叫代碼單元,比如UTF-8的代碼單元為1個位元組,UTF-16的代碼單元為2個位元組,不好解釋,但是很好理解。

  為了相容各種語言以及更好的跨平臺,Java String保存的就是字元的Unicode碼。它以前使用的是UCS-2編碼方案來存儲Unicode,後來發現BMP範圍內的字元不夠用了,但是出於記憶體消耗和相容性的考慮,並沒有升到UCS-4(即UTF-32,固定4位元組編碼),而是採用了上面所說的UTF-16,char類型可看作其代碼單元。這個做法導致了一些麻煩,如果所有字元都在BMP範圍內還沒事,若有BMP外的字元,就不再是一個代碼單元對應一個字元了,length方法返回的是代碼單元的個數,而不是字元的個數,charAt方法返回的自然也是一個代碼單元而不是一個字元,遍歷起來也變得麻煩,雖然提供了一些新的操作方法,總歸還是不方便,而且還不能隨機訪問。

  此外,我發現Java在編譯的時候還不會處理大於0xFFFF的Unicode字面量,所以如果你敲不出某個非BMP字元來,但是你知道它的Unicode碼,得用一個比較笨的方法來讓String存儲它:手動計算出該字元的UTF-16編碼(四位元組),把前兩個位元組和後兩個位元組各作為一個Unicode數,然後賦值給String,示例代碼如下所示。

public static void main(String[] args) {
        //String str = "";        //我們想賦值這樣一個字元,假設我輸入法打不出來
        
        //但我知道它的Unicode是0x1D11E
        //String str = "\u1D11E";  //這樣寫不會識別
        
        //於是通過計算得到其UTF-16編碼 D834 DD1E
        String str = "\uD834\uDD1E"; //然後這麼寫
        
        System.out.println(str);     //成功輸出了""
    }

  Windows系統自帶的記事本可以另存為Unicode編碼,實際上指的是UTF-16編碼。上面說了,主要使用的字元編碼都在BMP範圍內,而在BMP範圍內,每個字元的UTF-16編碼值與對應的Unicode數值是相等的,這大概就是微軟把它稱為Unicode的原因吧。舉個例子,我在記事本中輸入了”好a“兩個字元,然後另存為Unicode big endian(高位優先)編碼,用WinHex打開文件,內容如下圖,文件開頭兩個位元組被稱為Byte Order Mark(位元組順序標記),(FE FF)標識位元組序為高位優先,然後(59 7D)正是”好“的Unicode碼,(00 61)正是”a“的Unicode碼。

image

  有了Unicode碼,也還不能立即解決問題,因為首先世界上已經存在了大量的非Unicode標準的編碼數據,我們不可能丟棄它們,其次Unicode的編碼往往比ANSI編碼更占空間,所以從節約資源的角度來說,ANSI編碼還是有存在的必要的。所以需要建立一個轉換機制,使得ANSI編碼可以轉換到Unicode進行統一處理,也可以把Unicode轉換到ANSI編碼以適應平臺的要求。

  轉換方法說起來比較容易,對於UTF系列或者是ISO-8859-1這種被相容的編碼,可以通過計算和Unicode數值直接進行轉換(實際可能也是查表),而對於系統遺留下來的ANSI編碼,則只能通過查表的方式進行,微軟把這種映射表稱為Code Page(代碼頁),並按編碼進行分類編號,比如我們常見的cp936就是GBK的代碼頁,cp65001就是UTF-8的代碼頁。下圖是微軟官網查到的GBK->Unicode映射表(目測不全),同理還應有反向的Unicode->GBK映射表。

5XSYJX8ZCHRDRUL0OS18(}N

  有了代碼頁,就可以很方便的進行各種編碼轉換了,比如從GBK轉換到UTF-8,只需要先按照GBK的編碼規則對數據按字元劃分,用每個字元的編碼數據去查GBK代碼頁,得到其Unicode數值,再用該Unicode去查UTF-8的代碼頁(或直接計算),就可以得到對應的UTF-8編碼。反過來同理。註意:UTF-8是Unicode的標準實現,它的代碼頁中包含了所有的Unicode取值,所以任意編碼轉換到UTF-8,再轉換回去都不會有任何丟失。至此,我們可以得出一個結論就是,要完成編碼轉換工作,最重要的是第一步要成功的轉換到Unicode,所以正確選擇字元集(代碼頁)是關鍵。

  理解了轉碼丟失問題的本質後,我才突然明白JSP的框架為什麼要以ISO-8859-1去解碼HTTP請求參數,導致我們獲取中文參數的時候不得不寫這樣的語句:

String param = new String(s.getBytes("iso-8859-1"), "UTF-8");

  因為JSP框架接收到的是參數編碼的二進位位元組流,它不知道這究竟是什麼編碼(或者不關心),也就不知道該查哪個代碼頁去轉換到Unicode。然後它就選擇了一種絕對不會產生丟失的方案,它假設這是ISO-8859-1編碼的數據,然後查ISO-8859-1的代碼頁,得到Unicode序列,因為ISO-8859-1是按位元組編碼的,而且不同於ASCII的是,它對0 ~ 255空間的每一位都進行了編碼,所以任意一個位元組都能在它的代碼頁中找到對應的Unicode,若再從Unicode轉回原始位元組流的話也就不會有任何丟失。它這樣做,對於不考慮其他語言的歐美程式員來說,可以直接用JSP框架解碼好的String,而要相容其他語言的話也只需要轉回原始位元組流,再以實際的代碼頁去解碼一下就好。

  我對Unicode以及字元編碼的相關概念闡述完畢,接下來用Java實例來感受一下。

三、實例分析

1.轉換到Unicode——String構造方法

  String的構造方法就是把各種編碼數據轉換到Unicode序列(以UTF-16編碼存儲),下麵這段測試代碼,用來展示Java String構造方法的應用,實例中都不涉及非BMP字元,所以就不用codePointAt那些方法了。

public class Test {

    public static void main(String[] args) throws IOException {
        //"你好"的GBK編碼數據
        byte[] gbkData = {(byte)0xc4, (byte)0xe3, (byte)0xba, (byte)0xc3};
        //"你好"的BIG5編碼數據
        byte[] big5Data = {(byte)0xa7, (byte)0x41, (byte)0xa6, (byte)0x6e};

        //構造String,解碼為Unicode

        String strFromGBK = new String(gbkData, "GBK");
        String strFromBig5 = new String(big5Data, "BIG5");

        //分別輸出Unicode序列

        showUnicode(strFromGBK);
        showUnicode(strFromBig5);
    }

    public static void showUnicode(String str) {
        for (int i = 0; i < str.length(); i++) {
            System.out.printf("\\u%x", (int)str.charAt(i));
        }
        System.out.println();
    }
}

  運行結果如下圖

  image

  從結果可以發現,只要指定了正確的字元集(代碼頁),String就可以解碼出正確的Unicode,最後可以試試println("\u4f60\u597d"),輸出的就是“你好”。

2.Unicode轉換到各種編碼——getBytes

  String擁有了Unicode序列,想要轉換到其它編碼就易如反掌了,根據你參數指定的字元集,去相應的代碼頁查找就可以轉換過去了,當然如果該字元集不支持某字元(也就是沒有這條Unicode記錄),那就會導致編碼丟失,再也不能還原到原來的Unicode序列了。

  這裡,我們和第1節的做法相反,我們把Unicode序列轉換到其它各種編碼,如下所示。

public class Test {

    public static void main(String[] args) throws IOException {
        //字元串"你好"
        String str = "\u4f60\u597d";

        //轉換到各種編碼
        
        showBytes(str, "GBK");
        showBytes(str, "BIG5");
        showBytes(str, "UTF-8");
    }

    public static void showBytes(String str, String charset) throws IOException {
        for (byte b : str.getBytes(charset))
            System.out.printf("0x%x ", b);
        System.out.println();
    }
}

  運行結果如下圖

image

  可以發現,由於String掌握了Unicode碼,要轉換到其它編碼so easy!

3.以Unicode為橋梁,實現編碼互轉

  有了上面兩部分的基礎,要實現編碼互轉就很簡單了,只需要把他們聯合使用就可以了。先new String把原編碼數據轉換為Unicode序列,再調用getBytes轉到指定的編碼就OK。

  比如一個很簡單的GBK到Big5的轉換代碼如下

public static void main(String[] args) throws UnsupportedEncodingException {
        //假設這是以位元組流方式從文件中讀取到的數據(GBK編碼)
        byte[] gbkData = {(byte) 0xc4, (byte) 0xe3, (byte) 0xba, (byte) 0xc3};
        
        //轉換到Unicode
        String tmp = new String(gbkData, "GBK");
        
        //從Unicode轉換到Big5編碼
        byte[] big5Data = tmp.getBytes("Big5");
        
        //後續操作……
    }

4.編碼丟失問題

  上面已經解釋了,JSP框架採用ISO-8859-1字元集來解碼的原因。先用一個例子來模擬這個還原過程,代碼如下

public class Test {

    public static void main(String[] args) throws UnsupportedEncodingException {
        //JSP框架收到6個位元組的數據
        byte[] data = {(byte) 0xe4, (byte) 0xbd, (byte) 0xa0, (byte) 0xe5, (byte) 0xa5, (byte) 0xbd};
        //列印原始數據
        showBytes(data);
        //JSP框架假設它是ISO-8859-1的編碼,生成一個String對象
        String tmp = new String(data, "ISO-8859-1");
        
        //**************JSP框架部分結束********************
        
        //開發者拿到後列印它發現是6個歐洲字元,而不是預期的"你好"
        System.out.println("  ISO解碼的結果:" + tmp);
        
        //因此首先要得到原始的6個位元組的數據(反查ISO-8859-1的代碼頁)
        byte[] utfData = tmp.getBytes("ISO-8859-1");
        
        //列印還原的數據
        showBytes(utfData);
        
        //開發者知道它是UTF-8編碼的,因此用UTF-8的代碼頁,重新構造String對象
        String result = new String(utfData, "UTF-8");
        //再列印,正確了!
        System.out.println("  UTF-8解碼的結果:" + result);
    }

    public static void showBytes(byte[] data) {
        for (byte b : data)
            System.out.printf("0x%x ", b);
        System.out.println();
    }
}

  運行結果如下,第一次輸出是不正確的,因為解碼規則不對,也查錯了代碼頁,得到的是錯誤的Unicode。然後發現通過錯誤的Unicode反查ISO-8859-1代碼頁還能完美的還原數據。

image

 

  然後我們嘗試把ISO-8859-1替換為ASCII,結果就會變成這樣子

image

  這是因為,ASCII雖然也是每位元組對應一個字元,但是它只對0~127這個空間進行了編碼,也就是說每個位元組的最大值只能為0x7F,而上面的6個位元組全部都大於這個數值,因此在ASCII的代碼頁中是找不到這6個位元組的,於是Java就搞了一個預設值。我用如下的代碼測試發現,當通過編碼數據在代碼頁中查不到對應的Unicode時,就返回預設值\ufffd(對應圖中第一種問號),反過來,當通過Unicode在代碼頁中查不到對應的編碼數據時,就返回預設值0x3f(ASCII,對應圖中第二種問號)。由此,這個輸出結果也就可以解釋清楚了。

public static void main(String[] args) throws IOException {
        //輸出結果全為\ufffd
        byte[] data = {(byte) 0x80};
        showUnicode(new String(data, "UTF-8"));
        showUnicode(new String(data, "GBK"));
        showUnicode(new String(data, "Big5"));
        
        //輸出結果全為0x3f
        String str = "\uccdd";
        showBytes(str, "GBK");
        showBytes(str, "BIG5");
        showBytes(str, "ISO-8859-1");
    }

5.Java源文件的編碼問題

  這就是開頭所提到的那個問題,把問題描述一下先。就如下這麼一小段代碼,源文件使用UTF-8編碼保存。(註意別用Windows的記事本,因為它會在UTF-8文件最前面加入一個3位元組的BOM頭,而很多程式都不相容這一點)

public class Test {

    public static void main(String[] args) {
        System.out.println("中");
    }
}

  然後在Windows中使用預設參數編譯該文件(系統區域設置為簡體中文,即預設使用GBK字元集解碼),然後會得到如下錯誤

image

  這不是重點,重點如果把“中”換成“中國”,編譯就會成功,運行結果如下圖。另外進一步可發現,中文字元個數為奇數時編譯失敗,偶數時通過。這是為什麼呢?下麵詳細分析一下。

image

  因為Java String內部使用的是Unicode,所以在編譯的時候,編譯器就會對我們的字元串字面量進行轉碼,從源文件的編碼轉換到Unicode(維基百科說用的是與UTF-8稍微有點不同的編碼)。編譯的時候我們沒有指定encoding參數,所以編譯器會預設以GBK方式去解碼,對UTF-8和GBK有點瞭解的應該會知道,一般一個中文字元使用UTF-8編碼需要3個位元組,而GBK只需要2個位元組,這就能解釋為什麼字元數的奇偶性會影響結果,因為如果2個字元,UTF-8編碼占6個位元組,以GBK方式來解碼恰好能解碼為3個字元,而如果是1個字元,就會多出一個無法映射的位元組,就是圖中問號的地方。

  再具體一點的話,源文件中“中國”二字的UTF-8編碼是 e4 b8 ad e5 9b bd,編譯器以GBK方式解碼,3個位元組對分別查cp936得到3個Unicode值,分別是6d93 e15e 6d57,對應結果圖中的三個奇怪字元。如下圖所示,編譯後這3個Unicode在.class文件中實際以類UTF-8編碼存儲,運行的時候,JVM中存儲的就是Unicode,然而最終輸出時,還是會編碼之後傳遞給終端,這次約定的編碼就是系統區域設置的編碼,所以如果終端編碼設置改了,還是會亂碼。我們這裡的e15e在Unicode標準中並沒有定義相應的字元,所以在不同平臺不同字體下顯示會有所不同。

image

  可以想象,如果反過來,源文件以GBK編碼存儲,然後騙編譯器說是UTF-8,那基本上是無論輸入多少個中文字元都無法編譯通過了,因為UTF-8的編碼很有規律性,隨意組合的位元組是不會符合UTF-8編碼規則的。

  當然,要使編譯器能正確的把編碼轉換到Unicode,最直接的方法還是老老實實告訴編譯器源文件的編碼是什麼。

四、總結

  經過這次收集整理和實驗,瞭解了很多與編碼相關的概念,也熟悉了編碼轉換的具體過程,這些思想可以推廣到各種編程語言去,實現原理都類似,所以我想以後再遇到這類問題,應該不會再不知所以然了。

Reference

http://www.fmddlmyy.cn/text6.html
https://zh.wikipedia.org/wiki/Unicode
http://www.decodeunicode.org/
https://zh.wikipedia.org/wiki/UTF-8
https://en.wikipedia.org/wiki/UTF-16
https://en.wikipedia.org/wiki/Java_class_file
https://msdn.microsoft.com/en-us/library/cc194914.aspx
https://docs.oracle.com/javase/7/docs/api/java/lang/Character.html#unicode
https://zh.wikipedia.org/wiki/%E6%B1%89%E5%AD%97%E5%86%85%E7%A0%81%E6%89%A9%E5%B1%95%E8%A7%84%E8%8C%83


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.概述 Windows Script Host除了提供一個對象模型之外,還提供了一種腳本框架,這就是WSF腳本。通過WSF約定的標記元素,可以將多種腳本語言寫的代碼塊組合起來,完成任務。除此之外,還可以實現一些DOS沒有的命令功能,通過CScript xxx.wsf /?來查看幫助文檔,幫助文檔....
  • 常用命令mvn clean:清除maven的編譯結果mvn compile:編譯mvn package:編譯、打包mvn install:編譯、打包、部署–DskipTests:編譯測試用例,但不執行測試-Dmaven.test.skip:不編譯測試用例且不執行測試–Dcheckstyle.skip...
  • 一、配置在需要上傳的工程中的pom.xml文件中加入下麵的配置releaseRelease Repositoryhttp://ip/nexus/content/repositories/releasessnapshotSnapshot Repositoryhttp://ip/nexus/conten...
  • 對於一個學過C和Objective-C的程式猿來講,Swift編程語言的語法基礎非常簡單。但是Swift也是添加了很多新的語法,比如元組、可選類型等等。一、類型轉換和C、Objective-C中的類型強轉類似,只需要將常量或者變數放進 '()' 內,然後在括弧前面加上目標類型就可以了。 1 let ...
  • 面向對象編程簡單來說就是基於對類和對象的使用,所有的代碼都是通過類和對象來實現的編程就是面向對象編程!面向對象的三大特性:封裝、繼承、多態首先創建一個類#使用class創建一個School類,類中有個student方法class School: def student(self): ...
  • 登錄nexus私服後臺,按照下圖1-3的順序進行添加倉庫;其中步驟3有三種倉庫類型(Type)進行選擇1、 Hosted Repository:本地倉庫,在私服伺服器上存放用戶自行上傳的jar包;2、 Proxy Repository:遠程代理倉庫,從遠程下載jar包,並保存在私服伺服器;3、 Re...
  • 一、玩一玩playground首先要新建一個playground,創建完成之後,你會看到程式裡面有一個定義好的變數 str,在右邊的側欄中會顯示出變數的值,也就是說,我們在編程的過程中就能對變數進行實時監測。1.點擊右邊欄對應行的圓圈圖標,變數的值就會顯示在這行代碼的下麵2.點擊右邊欄對應行的眼睛圖...
  • log4jdbc是一個JDBC驅動器,能夠記錄SQL日誌和SQL執行時間等信息。log4jdbc使用SLF4J(Simple Logging Facade)作為日誌系統。特性:1.支持JDBC3和JDBC4。 2.支持現有大部分JDBC驅動。 3.易於配置(在大部分情況下,只需要改變驅動類名並...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...