這是 Jerry 2022 年第二篇原創文章,也是本公眾號第 370 篇原創文章。
之前有一個朋友在知乎上向我諮詢過這個問題,我覺得很有代表性,所以專門用一篇文章來講述一些相關知識點。
先看這位朋友遇到的具體問題。
用 Postman 調用第三方接口,裏面的中文字符能夠正常顯示。
然而當用 ABAP 的 HTTP 工具類 CL_HTTP_CLIENT 的 response->get_data( ) 讀取響應之後,發現裏面的中文字符,例如 "訪問成功" 是亂碼:
首先明確一點,既然 Postman 能正確顯示響應數據中的中文內容,説明 API provider 是不存在問題的,這個亂碼問題出現在接收方,即 ABAP 代碼的編程實現需要調整。
我們只要弄清楚出現亂碼的原因,就能有的放矢進行修復了。
上個世紀 60 年代,美國製定了一套字符編碼,定義了英文字符與二進制位之間的一一映射關係,稱為 ASCII 碼。將一個符號的圖形顯示,關聯到其二進制存儲位的這種行為,就稱之為字符編碼。ASCII 就是一種最簡單的字符集和字符編碼方式。
一個字節有 8 位,2 的 8 次方為 256,因此 1 個字節只能表示 256 種符號,而漢字的總數超過了 10 萬個,顯然無法用 1 個字節來存儲。
除了大家熟悉的英文字符和漢字外,還有很多歷史更悠久的文字,比如埃及象形文字:
以及周杰倫《愛在西元前》裏提到的楔形文字:
有沒有這樣一種計算機編碼方式,能夠將這些稀奇古怪的符號都納入其中呢?有,這就是 Unicode,正如其命名暗示的,Unicode 將世界各種語言的每個字符都分配了一個唯一的編碼,以滿足跨語言、跨平台的文本信息轉換。
我們根據 Unicode 編碼表,就能查到一個字符對應的 Unicode 編碼,比如漢字 "汪"對應的 Unicode 編碼為 00006C6A.
6C6A 的二進制表示為 0110 1100 0110 1010,需要兩個字節進行存儲。表示其他的符號,可能需要三個甚至四個字節存儲。
另一方面,對於原本就存在於 ASCII 編碼表中的英文字符,僅需 1 個字節就能存儲。如果 Unicode 強制要求每個字符按照最大需要的存儲空間,即 4 字節進行存儲,顯然對於英文字符來説,意味着極大的空間浪費。
因此,Unicode 僅僅定義字符到其編碼的映射關係。而這些編碼到底採取多少個字節進行存儲,由 Unicode 具體的實現方式,比如 UTF-8,UTF16 等來決定。
UTF-8 是一種變長的編碼方式,使用 1 到 4 個字節表示一個字符,符號不同,用於存儲的字節長度也不同。比如 "汪" 的 UTF-8 碼值為 E6B1AA,需要三個字節存儲。
根據 SAP 幫助文檔,ABAP 採用 UCS-2 編碼方式,可以看成 UTF-16 的子集,因為 UCS-2 不支持 UTF-16 的 surrogates 區間內定義的一些特殊符號。
所謂 UTF-16,就是所有字符固定都用兩個字節表示。
從下面這張表格能夠看出,UTF-16 又分 UTF-16BE 和 UTF-16LE 兩種實現方式。以漢字 "汪" 的 Unicode 編碼值 6C6A 為例,如果 6C 存儲在內存低位地址,6A 存儲在內存高位地址,這就是 Big Endian 即大尾序(有時也譯作大頭,大端)存儲方式,反之則為 Little Endian 即小尾序存儲方式。
這兩個名稱來自英國諷刺寓言作家斯威夫特的《格列佛遊記》。書中的小人國爆發了內戰,戰爭起因竟然是人們爭論吃雞蛋時究竟應該從大頭(Big Endian)一端敲開,還是從小頭(Little Endian)敲開。
那麼 ABAP 的 UCS-2(UTF-16 的子集), 到底是 BE 存儲還是 LE 存儲?一試便知。
在我的系統裏,答案是 UTF-16LE.
另一種方式,直接檢查系統類 CL_ABAP_CHAR_UTILITIES 的屬性 ENDIAN. 在 Jerry 的系統裏,該屬性的值為 L,代表 Little Endian:
我們瞭解了這些知識,再來修復文章開頭描述的亂碼問題。
仔細觀察 Postman 調用 API 的返回結果,發現還有一條重要信息:charset=GB18030,意思是 API 響應數據採取 GB18030 字符集編碼。
漢字 "訪" 的 GB18030 編碼值為 B7C3,完全不等同於 UTF-16LE 中的編碼值 BF8B.
如果我們在 ABAP 代碼裏,按照默認的 UTF-16LE 的方式去讀取一個根據 GB18030 編碼的符號,當然不會得到期望的結果。這種張冠李戴的解碼方式見下圖第 55 行的 get_cdata 方法,最後就會出現亂碼。
正確的方式,採取第 57 行 get_data,返回一個 16 進制數據流,類型為 xstring:
在這個16 進制數據流裏,我們已經看到了漢字 "訪" 和 "問" 對應的 GB18030 編碼值。
剩下的事情就容易了,使用字符集 GB18030 對這段數據流進行解碼。
我們首先打開數據庫表 TCP00, 根據關鍵詞 18030 查詢表字段 CPCOMMENT:
得到 GB18030 對應的 SAP Code Page 為 8401:
在下面這段代碼中,傳入 8401,變量 lv_binary 存儲的是 16 進制數據流,變量 lv_text 存放的就是基於 GB18030 的 API 響應內容:
可以看到亂碼已經消失了,在 ABAP 程序裏顯示的內容已經和 Postman 裏觀察到的完全一致了。
希望本文介紹的這個例子,能對大家在 ABAP 裏處理中文亂碼問題有所啓發,感謝閲讀。
Jerry 的 ABAP 專集
- Jerry的ABAP, Java和JavaScript亂燉
- ABAP開發人員未來應該學些什麼
- Jerry 2017年的五一小長假:8種經典排序算法的ABAP實現
- Jerry的ABAP原創技術文章合集
- 300行ABAP代碼實現一個最簡單的區塊鏈原型
- 使用Java+SAP雲平台+SAP Cloud Connector調用ABAP On-Premise系統裏的函數
- 在SAP雲平台的CloudFoundry環境下消費ABAP On-Premise OData服務
- ABAP vs Java, 蛙泳 vs 自由泳
- 聊聊C語言和ABAP
- 動手使用ABAP Channel開發一些小工具,提升日常工作效率
- 我用ABAP做過的那些無聊的事情
- 不喜歡SAP GUI?那試試用Eclipse進行ABAP開發吧
- 使用Visual Studio Code編寫和激活ABAP代碼
- 你的ABAP程序給佛祖開過光麼?來試試Jerry這個小技巧
- 在SAP雲平台ABAP編程環境上編寫第一段ABAP程序
- SAP官方發佈的ABAP編程規範
- ABAP Code Inspector那些隱藏的功能,您都知道嗎?
- 還在用ABAP進行SAP產品的二次開發?來了解下這種全新的二次開發理念吧
- ABAP Netweaver體內的那些寄生式編程語言
- 從SAP社區上的一篇博客開始,聊聊SAP產品命名背後的那份情懷
- 雲端的ABAP Restful服務開發
- 如何在SAP雲平台ABAP編程環境裏把CDS view暴露成OData服務
- 使用abapGit在ABAP On-Premises系統和SAP雲平台ABAP環境之間進行代碼傳輸
- 30分鐘用Restful ABAP Programming模型開發一個支持增刪改查的Fiori應用
- Jerry帶您瞭解Restful ABAP Programming模型系列之二:Action和Validation的實現
- Jerry帶您瞭解Restful ABAP Programming模型系列之三:雲端ABAP應用調試
- SAP雲平台上的ABAP編程環境裏如何消費第三方服務
- ABAP開發者上雲的時候到了 - 現在大家可以免費使用SAP雲平台ABAP環境的試用版了
- 學而不思則罔 - SAP雲平台ABAP編程環境的由來和適用場景
- SAP雲平台裏的三叉戟應用
- 如何基於Restful ABAP Programming模型開發並部署一個支持增刪改查的Fiori應用
- SAP 2019 TechEd Key Note解讀:雲時代下SAP從業人員如何做二次開發?
- 有哪些ABAP關鍵字和語法,到了ABAP雲環境上就沒辦法用了?
- ABAP開發環境終於支持以駝峯命名法自動格式化ABAP變量名了
- 利用ABAP 740的新關鍵字REDUCE完成一個實際工作任務
- 一段讓人瑟瑟發抖的ABAP代碼
- 昨日萬聖節ABAP怪獸級代碼謎團,公佈答案啦
- 介紹一種在ABAP內核態進行內表高效拷貝的方法
- 使用SAP Cloud Application Programming模型開發OData的一個實際例子
- 當ABAP遇見普羅米修斯
- 使用ABAP繪製可伸縮矢量圖
- ABAP開發環境語法高亮的那些事兒
- SAP錯誤消息調試之七種武器:讓所有的錯誤消息都能被定位
- 使用ABAP操作Excel的幾種方法
- SAP GUI裏的收藏夾事務碼管理工具
- SAP GUI和Windows註冊表
- 有了Debug權限就能幹壞事?小心了,你的一舉一動盡在系統監控中
- ABAP CCDEF, CCIMP, CCMAC, CCAU, CMXXX這些東東是什麼鬼
- 實現ABAP條件斷點的三種方式
- 使用SAT跟蹤監控從瀏覽器打開的SAP應用的性能和調用棧
- 一個13年ABAP老兵的建議:瞭解這些基礎知識,對ABAP開發有百利而無一害
- SAP ABAP Netweaver容器化, 不可能完成的任務嗎?
- SAP產品增強技術回顧
- SAP API開發方法大全
- 淺談Java和SAP ABAP的靜態代理和動態代理,以及ABAP面向切面編程的嘗試
- SAP ABAP應用服務器的HTTP響應狀態碼(Status Code)
- SAP ABAP裏存在Java List這種集合工具類麼?CL_OBJECT_COLLECTION瞭解一下
- ABAP面試題系列:寫一組會出現死鎖(Deadlock)的ABAP程序
- SAP ABAP Netweaver服務器的標準登錄方式講解
- SAP ABAP關鍵字語法圖和ABAP代碼自動生成工具Code Composer
- SAP ABAP SM50的另類用途 - ABAP工作進程對數據庫表讀取操作的檢測
- 關於SAP ABAP字符變量和字符串變量字符個數的一個知識點,和一個血案
- SAP ABAP一組關鍵字 IS BOUND, IS NOT INITIAL和IS ASSIGNED的用法辨析
- SAP ABAP和Java裏的弱引用(WeakReference)和軟引用(SoftReference)
- SAP AMDP介紹 - ABAP託管的HANA數據庫過程
- 給你的ABAP對象打上標籤(Tag)
- 歷史上的今天:編程語言中null引用的十億美元錯誤
- ABAP Development Tool 代碼模板和其他一些實用技巧彙總
- SAP ABAP Development Tool 提高開發效率的十個小技巧
- 如何在 SAP BTP 平台 ABAP 編程環境裏消費基於 SOAP 的 Web Service
- ABAP 真的會過時嗎?聊聊 ABAP 的過去,現在和未來
- 基於 abapGit 和 abaplint 的 ABAP 持續集成的一個例子
- 不使用任何框架,手寫純 JavaScript 實現上傳本地文件到 ABAP 服務器
- 使用 JavaScript 上傳 PDF 和 Excel 等二進制文件到 ABAP 服務器並進行解析
- 從 ABAP Netweaver 到 ABAP Platform,我們一直在努力
更多Jerry的原創文章,盡在:"汪子熙":