<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      【原創】經驗分享:一個小小emoji盡然牽扯出來這么多東西?

      前言

      之前也分享過很多工作中踩坑的經驗:

      1. 一個線上問題的思考:Eureka注冊中心集群如何實現客戶端請求負載及故障轉移?
      2. 【原創】經驗分享:一個Content-Length引發的血案(almost....)

      今天再來分享工作中一個真實的案例:

      商品評價列表頁,顯示每條用戶的評價詳情,為了保護用戶隱私,要求顯示用戶昵稱時只能顯示第一位和最后一位,其他的用※代替。

      例如輸入:??????,輸出:??***??

      看似一個平淡無奇的需求,我也沒有太在意。服務端將用戶的評論信息存儲到db中,評價列表接口就是將數據庫中該商品的評論信息展示出來,特殊處理下評論人的昵稱就可以了。

      但是!! 測試同學發現用戶昵稱包含emoji表情時就會出問題,切割的數據會有問號顯示!!

      模擬的示例代碼如下:

      字符串截取.png

      輸出:

      輸出.png

      看到這個輸出,我真的是一臉懵逼,這完全不是我想要的結果呀!!!

      黑人問號.png

      這三個魚可算是難倒我了,難道只能給測試說 emoji太特殊 不予處理?然后撒個嬌蒙混過關?

      思考了良久,我還是決定要正視這個問題并解決掉它!(畢竟我還是那個不畏困難的小機靈鬼??)

      問題不大.png

      PS:本文很大程度是受到之前公司一位同事unicode分享的啟發,在這里向我的這位老師致敬!下面的內容會一步步分析這個問題的產生以及最終的解決方案。

      概念常識

      要解決這些問題,就必須要鋪墊一些基礎知識,大家等不及看解決方案 可以拉到文章最后的代碼示例。

      utf8mb4

      一般我們在數據庫創建表時都會默認使用這種編碼格式:

      utf8mb4編碼.png

      相信大家對這個編碼格式都不陌生吧,當我們想存儲emoji數據到數據庫中,那么數據庫的格式就需要指定為utf8mb4了,要不然存儲就會報錯了。所以在很多公司的db規范中,數據庫默認編碼必須為utf8mb4

      emoji保存報錯.png

      但是大家有沒有過這樣的疑惑,為何utf8不行而utf8mb4就行?這里面到底有什么彎彎道道

      這里面涉及到unicode相關知識,我們下面會提到,大家繼續看。

      mysql 5.5 之前,utf8編碼只支持1-3個字節,從mysql 5.5開始,可支持4個字節UTF編碼utf8mb4,一個字符最多能有4字節,所以能支持更多的字符集。

      emoji長度.png

      這個表格中包含了所有的 emoji 以及它所對應的 unicode編碼,同時也有對應的 utf-8編碼的實現。

      從圖中也可以看出 emoji 表情用 utf-8 表示時會占用 4個字節,這也就是為什么數據庫用utf8無法存儲emoji表情的原因了。

      同樣我們也可以在java代碼中看看emoji占用幾個字節長度:

      emoji長度.png

      我們也可以看到String.getBytes(),默認是utf-8編碼的:

      String.getBytes編碼格式.png

      ASCII碼

      上面介紹utf8mb4時有提過unicode,介紹它之前我們也需要先提一嘴我們的老朋友:ASCII

      ASCII(American Standard Code for Information Interchange,美國信息交換標準代碼)是基于拉丁字母的一套電腦編碼系統。它主要用于顯示現代英語。

      這樣我們就可以使用一個字節來表示現代英文,看起來非常不錯,部分數據對應關系如下:

      ASCII碼.png

      但這個只能顯示的代表拉丁文,這顯然是遠遠不夠的。

      Unicode

      顯而易見,計算機的發展并不是只支持英文一種語言的,ASCII的局限在于只能顯示26個基本拉丁字母、阿拉伯數字和英式標點符號,因此只能用于顯示現代美國英語。

      這時如果能有一種包含了世界上所有的文字的字符集,每一個地區的文字都在這個字符集中有唯一的二進制表示,這樣便不會出現亂碼問題了。所以Unicode也應運而生了。

      概念

      Unicode,中文又稱萬國碼、國際碼、統一碼、單一碼,是計算機科學領域里的一項業界標準。它對世界上大部分的文字系統進行了整理、編碼,使得電腦可以用更為簡單的方式來呈現和處理文字。

      平面

      Unicode 首先承認了 ASCII 占用 0-127 整數資源的合法性,之后又一次占用了 128-65535 的整數資源,有了這么多的整數資源,我們就可以把世界各種文字的每一種字符分配一個整數來表示了。

      之后,Unicode 聯盟發現 65536 個整數也不夠分配的,于是就索性一次性又把之后的 16 個 65536 的數字即 65536-1114111 的整數資源給占了,然后把多占的 16 個 65536 的段分別命名為 16 個平面,加上原來的 0-65535 平面Unicode 總共有 17 個平面。比如第 1 平面就是 65536-131072。當然,到目前為止,還只分配了 7 個平面出去。

      Unicode平面.png

      第0平面(Plane 0),是Unicode中的一個編碼區段。編碼從U+0000U+FFFF,這個平面里面的字符是我們最常用到的。

      65535 之后分配的字符大多數是 emoji 表情,比如 ?? 是 128570(\uD83D\uDE3A)

      這里推薦一個在線的編碼轉換網站:http://ctf.ssleye.com/cencode.html

      在線utf16轉換.png

      表示范圍

      Unicode表示范圍:U+0000 ~ U+10FFFF

      • 也就大概是:U+0000~U+110000(加上1),也就是17個FFFF(65535)
      • 差不多17*6w,大概有100w個碼點可以用來映射字符
      • 準確的值是 1114,112,差不多112w個碼點
      • 最新版本的Unicode含有136,690 個字符,離100w還很遠。
      • Unicode 官方表示目前的碼點已經夠用,以后不再擴充

      實現方式

      Unicode的實現方式不同于編碼方式。一個字符的Unicode編碼是確定的。但是在實際傳輸過程中,由于不同系統平臺的設計不一定一致,以及出于節省空間的目的,對Unicode編碼的實現方式有所不同。Unicode的實現方式稱為Unicode轉換格式(Unicode Transformation Format,簡稱為UTF)。

      對于被Unicode收錄的字符其編碼是唯一且確定的。但是Unicode的實現方式(出于傳輸、存儲、處理或向后兼容的考慮)卻有不同的幾種,其中最流行的是UTF-8UTF-16UCS2UCS4/UTF-32等,細分的話還有大小端的區別。

      對于我們Java而言,可以從char占用2字節來推斷出使用的是UTF-16編碼來存儲

      對于各種編碼問題推薦一篇好文:深入分析 Java 中的中文編碼問題

      判斷是否包含中文

      上面大概了解了Unicode的含義及用途,那么了解這個玩意有什么實際作用呢?

      我們再來看一個小的需求,比如:如何判斷一個字符串中包含中文?

      相信大家也遇到過這種需求吧,一般我們都會去百度一通,一定都能找到一個判斷是否包含中文的正則表達式,然后滿心歡喜解決了問題。

      恰巧我們系統中也有這么一個正則判斷,是架構組的同事封裝好的,一起來看下:

      是否含有中文.png

      顯然,這里是通過Unicode區間去判斷的,有沒有問題呢?

      這里的區間是用的中日韓統一表意文字,但是這個是1993年的版本,包含了大部分我們常用的中文,共有20902個字,看到后面補充的版本,還添加了很多字,由此可想像我們現在使用的判斷方式肯定會漏掉后添加的字:

      中日韓統一表意文字.png

      我們用2000年增加的中日韓統一表意文字擴展區A 來舉例測試一下:

      中日韓統一表意文字擴展區A.png

      這里加了很多生僻字,甚至都沒有我認識的,我們用第二排的數據來做一個驗證:

      驗證是否包含中文.png

      看到這里是不是很驚訝?并高呼你們這里寫了一個bug,哈哈。

      寫Bug.png

      其實這里并不能說我們的正則判斷有bug,這個需要看我們的需求是否精準到所有的生僻詞都得識別到。根據用戶的使用習慣,輸入這些生僻字的概率不是很高,所以這個正則并沒有小伙伴反饋有問題。

      解決emoji截取的問題

      言歸正傳,我們終究還是要解決開頭提出的問題,如何正確的截取含有emoji的字符串?這里從UTF-16編碼開始說起。

      UTF-16

      UTF-16 具體定義了 Unicode 字符在計算機中存取方法。UTF-16 用兩個字節來表示 Unicode 轉化格式,這個是定長的表示方法,不論什么字符都可以用兩個字節表示,兩個字節是 16 個 bit,所以叫 UTF-16。UTF-16 表示字符非常方便,每兩個字節表示一個字符,這個在字符串操作時就大大簡化了操作,這也是 Java 以 UTF-16 作為內存的字符存儲格式的一個很重要的原因。

      在基本多語言平面(碼位范圍U+0000-U+FFFF)內的碼位UTF-16編碼使用1個碼元且其值與Unicode是相等的(不需要轉換),這個就是我們正常的漢字,比如在輔助平面(碼位范圍U+10000-U+10FFFF)內的碼位在UTF-16中被編碼為一對16bit的碼元(即32bit,4字節),稱作代理對(surrogate pair)。組成代理對的兩個碼元前一個稱為 前導代理(lead surrogates) 范圍為0xD800-0xDBFF,后一個稱為 后尾代理(trail surrogates) 范圍為0xDC00-0xDFFF

      surrogate

      上面有提到surrogatesurrogate是代理的意思, 這個概念不是來自 Java 語言,而是來自 Unicode 編碼方式之一 UTF-16。具體請見:UTF-16

      簡而言之,Java 語言內部的字符信息是使用 UTF-16 編碼。因為char 這個類型是 16-bit 的。它可以有65536種取值,即65536個編號,每個編號可以代表1種字符。但是,Unicode 包含的字符已經遠遠超過65536個。那么編號大于65536的,還要用 16-bit 編碼,該怎么辦?于是Unicode 標準制定組想出的辦法就是,從這65536個編號里,拿出2048個,規定它們是「Surrogates」,讓它們兩個為一組,來代表編號大于65536的那些字符。

      更具體地,編號為 U+D800U+DBFF 的規定為「High Surrogates」,共1024個。編號為 U+DC00 U+DFFF 的規定為「Low Surrogates」,也是1024個。它們兩兩組合出現,就又可以多表示1048576種字符。

      emoji截取異常原因

      上面都是一些概念性的知識,如果硬看確實容易懵,我們還是回過頭看一下吧,從代碼入手:

      昵稱.png

      我們可以把emoji分離出來,如下:

      ?? -> \uD83D\uDC33

      ?? -> \uD83D\uDC33

      ?? -> \uD83D\uDC20

      emoji肯定是大于65536的,所以這里就用「High Surrogates」「Low Surrogates」兩兩組合的方式來呈現的。

      由上面的UTF-16編碼知識可以推斷出,我們的emoji表情截取一個char后出現亂碼的原因,是因為它是屬于UTF-16編碼輔助平面內的代理對,而我們如果截取時將代理對拆分開 就會出現異常的問題。

      對于這種情況,我們可以通過Character類的靜態方法isHighSurrogateisLowSurrogate來判斷,單個emoji的組合就是高位+低位,所以對于輔助平面內的代理對,做到整個移除或保留即可。

      isHighSurrogate方法的源碼如下:

      public static final char MIN_HIGH_SURROGATE = '\uD800';
      
      public static final char MAX_HIGH_SURROGATE = '\uDBFF';
      
      public static boolean isHighSurrogate(char ch) {
          return ch >= MIN_HIGH_SURROGATE && ch < (MAX_HIGH_SURROGATE + 1);
      }
      

      這個判斷其實就是上面說的「High Surrogates」的判定方式,我們可以轉換一下:

      U+D800 <= ch <= U+DBFF

      同理,isLowSurrogate方法的判定方式也是一樣的:

      U+DC00 <= ch <= U+DFFF

      問題解決

      還是先運行一下代碼,看看效果:

      問題解決.png

      具體實現代碼如下:

      public static void main(String[] args) {
          // 用戶昵稱為:??????,正常結果應該為:??***??
          String context = "\uD83D\uDC33\uD83D\uDC33\uD83D\uDC20";
          int realNameLength = realStringLength(context);
          String namePrefix = subString(context, 1, 0);
          String nameSuffix = subString(context, realNameLength - 1, 1);
          context = String.format("%s%s%s", namePrefix, "***", nameSuffix);
          System.out.println(context);
      }
      
      /**
       * 包含emoji表情的subString方法
       *
       * @param str 原有的str
       * @param len str長度
       * @param type type = 0 代表prefix,其他代表suffix
       */
      private static String subString(String str, int len, int type) {
          if (len < 0) {
              return str;
          }
      
          int count = 0;
          for (int i = 0; i < str.length(); i++) {
              if (count == len) {
                  // type = 0 代表prefix,其他代表suffix
                  if (type == 0) {
                      return str.substring(0, i);
                  }
                  return str.substring(i);
              }
      
              char c = str.charAt(i);
              if (Character.isHighSurrogate(c) || Character.isLowSurrogate(c)) {
                  i++;
              }
              count++;
          }
      
          return str;
      }
      
      
      /**
       * 包含emoji表情的字符串實際長度
       *
       * @param str 原有str
       * @return str實際長度
       */
      private static int realStringLength(String str) {
          int count = 0;
          for (int i = 0; i < str.length(); i++) {
              char c = str.charAt(i);
              if (Character.isHighSurrogate(c) || Character.isLowSurrogate(c)) {
                  i++;
              }
              count++;
          }
      
          return count;
      }
      

      彩蛋:認領屬于你的emoji

      emoji遠遠不止于此,unicode旗下還可以支持對emoji進行捐贈的,當然這個emoji會以捐贈者的名義去命名的。如下是現有的捐贈列表

      捐助列表.png

      捐助列表2.png

      看到第一個就是elastic.co捐贈的,而且點擊鏈接可以直接進入他們官網。第二個捐贈列表中還有一個是我同事捐贈的,哈哈,很有意思。

      如果想自己捐贈也可以直接進入到emoji捐贈網站去填寫個人信息,一共有三個檔位,捐贈后這個列表就會顯示由你定義的emoji信息了,簡直太酷了??:

      一枝花.png

      總結

      一個小小的emoji真是學問無窮,由于篇幅的問題我這里還省略了很多東西,比如UTF-8UTF-16兩種編碼形式并沒有深入講解,這里面又會牽扯到很多內容。

      我希望這篇文章能夠做到一個拋磚引玉的作用,激發小伙伴們一起去探究更多的奧秘。

      參考

      1. 維基百科 Unicodehttps://zh.wikipedia.org/wiki/Unicode
      2. 維基百科 Unicode字符平面映射https://zh.wikipedia.org/wiki/Unicode字符平面映射
      3. 不要小看小小的 emoji 表情https://juejin.im/post/6844903938878078990
      4. 談談字符編碼:Unicode、UTF-8 和 char[]https://luan.ma/post/character-encoding/
      5. 字符截斷引發的emoji表情亂碼問題https://superxlcr.github.io/2018/06/19/字符截斷引發的emoji表情亂碼問題/
      6. emoji捐贈列表https://www.unicode.org/consortium/adopted-characters.html

      歡迎關注我的公眾號,一起交流學習:

      歡迎關注

      posted @ 2020-10-09 06:38  一枝花算不算浪漫  閱讀(2424)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 国语自产拍精品香蕉在线播放| 欧美日韩欧美| 国产午夜福利视频在线| 亚洲国产精品自产在线播放| 激情的视频一区二区三区| 麻豆国产传媒精品视频| 2021国产成人精品久久| 亚洲丰满熟女一区二区蜜桃| 国产一区二区视频啪啪视频 | 无码精品人妻一区二区三区中| 在线播放深夜精品三级| 精品国产伦理国产无遮挡| 性欧美videofree高清精品| 人人妻人人狠人人爽| 少妇激情av一区二区三区| 久久婷婷大香萑太香蕉AV人| 在线天堂中文新版www| 亚洲精品中文综合第一页| 熟女一区| 一日本道伊人久久综合影| 人妻中文字幕亚洲精品| 性色高清xxxxx厕所偷窥| 国产精品白浆免费视频| 在线精品视频一区二区| 亚洲线精品一区二区三区| 国产精品乱码久久久久久小说| 欧洲无码一区二区三区在线观看| 欧美日韩国产综合草草| 曲靖市| 自拍日韩亚洲一区在线| 湟中县| 国产亚洲精品aaaa片app| 人妻系列无码专区无码中出| 久久精品国产99久久美女| 国产一区二区一卡二卡| 中文字幕人妻日韩精品| 亚洲欧美日韩综合一区在线| 亚洲伊人久久综合成人| 日韩人妻精品中文字幕专区| 免费人成视频在线| 毛片av在线尤物一区二区 |