<del id="nnjnj"></del><track id="nnjnj"></track>

<p id="nnjnj"></p>

<address id="nnjnj"></address>

    <pre id="nnjnj"><pre id="nnjnj"></pre></pre>

      <noframes id="nnjnj"><ruby id="nnjnj"><ruby id="nnjnj"></ruby></ruby>

      • 自動秒收錄
      • 軟件:1973
      • 資訊:57811|
      • 收錄網站:279872|

      IT精英團

      提高Java字符串編碼和解碼性能的技巧

      提高Java字符串編碼和解碼性能的技巧

      瀏覽次數:
      評論次數:
      編輯: 樂詠
      信息來源: ITPUB
      更新日期: 2022-05-18 20:56:47
      摘要

      1常見字符串編碼●常見的字符串編碼有:LATIN1只能保存ASCII字符,又稱ISO-8859-1。UTF-8變長字節編碼,一個字符需要使用1個、2個或者3個byte表示。由于中文通常需要3個字節

      • 正文開始
      • 相關閱讀
      • 推薦作品

      1

      常見字符串編碼

      常見的字符串編碼有:LATIN1只能存儲ASCII字符,也稱為ISO-8859-1。UTF-8變長字節編碼,一個字符需要用1、2或3個字節來表示。由于中文通常需要3個字節來表示,中文場景的UTF-8編碼通常需要更多的空間。備選方案為GBK/GB2312/GB18030。UTF-16有兩個字節,一個字符需要用兩個字節來表示,也稱為UCS-2 (2字節通用字符集)。根據大小頭的不同,UTF-16有兩種形式,UTF-16BE和UTF-16LE。默認的UTF-16指的是UTF-16BE。Java中的Char是UTF-16LE編碼。GB18030變長字節編碼,一個字符需要用1、2或3個字節表示。與UTF8類似,中文只需要2個字符,這意味著中文節省了字節大小。缺點是國際上不常用。

      為了計算方便,內存中的字符串通常使用等寬字符,Java語言中的char和。NET使用UTF-16。早期的Windows-NT只支持UTF-16。2

      編碼轉換性能

      UTF-16和UTF-8之間的轉換很復雜,并且性能通常很差。

      以下是將UTF-16轉換為UTF-8編碼的實現??梢钥闯鏊惴ū容^復雜,所以性能比較差,不能用vector API優化這個操作。

      static int encode utf8(char[]ut F16,int off,int len,byte[] dest,int dp) { int sl=off len,last _ offset=sl-1;while(off sl){ char c=ut F16[off];if (c0x80) { //最多有七位dest[DP]=(byte)c;} else if (c0x800) { //2 dest,11位dest[DP]=(byte)(0xc 0 |(c 6));dest[DP]=(byte)(0x 80 |(c0x3f));} else if (c='\uD800' amp

      ;& c < '\uE000') {int uc;if (c < '\uDC00') {if (off > last_offset) {dest[dp++] = (byte) '?';return dp;}
      char d = utf16[off];if (d >= '\uDC00' && d < '\uE000') {uc = (c << 10) + d + 0xfca02400;} else {throw new RuntimeException("encodeUTF8 error", new MalformedInputException(1));}} else {uc = c;}dest[dp++] = (byte) (0xf0 | ((uc >> 18)));dest[dp++] = (byte) (0x80 | ((uc >> 12) & 0x3f));dest[dp++] = (byte) (0x80 | ((uc >> 6) & 0x3f));dest[dp++] = (byte) (0x80 | (uc & 0x3f));off++; // 2 utf16} else {// 3 dest, 16 bitsdest[dp++] = (byte) (0xe0 | ((c >> 12)));dest[dp++] = (byte) (0x80 | ((c >> 6) & 0x3f));dest[dp++] = (byte) (0x80 | (c & 0x3f));}}return dp;}
      相關代碼地址[1] 。
      由于Java中char是UTF-16LE編碼,如果需要將char[]轉換為UTF-16LE編碼的byte[]時,可以使用sun.misc.Unsafe#copyMemory方法快速拷貝。比如:
      static int writeUtf16LE(char[] chars, int off, int len, byte[] dest, final int dp) {UNSAFE.copyMemory(chars, CHAR_ARRAY_BASE_OFFSET + off * 2, dest, BYTE_ARRAY_BASE_OFFSET + dp, len * 2);dp += len * 2;return dp;}


      3

      Java String的編碼


      不同版本的JDK String的實現不一樣,從而導致有不同的性能表現。char是UTF-16編碼,但String在JDK 9之后內部可以有LATIN1編碼。
      3.1. JDK 6之前的String實現
      static class String {final char[] value;final int offset;final int count;}

      在Java 6之前,String.subString方法產生的String對象和原來String對象共用一個char[] value,這會導致subString方法返回的String的char[]被引用而無法被GC回收。于是使得很多庫都會針對JDK 6及以下版本避免使用subString方法。
      3.2. JDK 7/8的String實現
      static class String {final char[] value;}

      JDK 7之后,字符串去掉了offset和count字段,value.length就是原來的count。這避免了subString引用大char[]的問題,優化也更容易,從而JDK7/8中的String操作性能比Java 6有較大提升。
      3.3. JDK 9/10/11的實現
      static class String {final byte code;final byte[] value;
         static final byte LATIN1 = ;   static final byte UTF16 = 1;}

      JDK 9之后,value類型從char[]變成byte[],增加了一個字段code,如果字符全部是ASCII字符,使用value使用LATIN編碼;如果存在任何一個非ASCII字符,則用UTF16編碼。這種混合編碼的方式,使得英文場景占更少的內存。缺點是導致Java 9的String API性能可能不如JDK 8,特別是傳入char[]構造字符串,會被做壓縮為latin編碼的byte[],有些場景會下降10%。

      4

      快速構造字符串的方法


      為了實現字符串是不可變特性,構造字符串的時候,會有拷貝的過程,如果要提升構造字符串的開銷,就要避免這樣的拷貝。

      比如如下是JDK8的String的一個構造函數的實現

      public final class String {public String(char value[]) {this.value = Arrays.copyOf(value, value.length);}}

      在JDK8中,有一個構造函數是不做拷貝的,但這個方法不是public,需要用一個技巧實現MethodHandles.Lookup & LambdaMetafactory綁定反射來調用,文章后面有介紹這個技巧的代碼。
      public final class String {String(char[] value, boolean share) {// assert share : "unshared not supported";this.value = value;}}

      快速構造字符的方法有三種:
      1. 使用MethodHandles.Lookup & LambdaMetafactory綁定反射
      2. 使用JavaLangAccess的相關方法
      3. 使用Unsafe直接構造

      這三種方法,1和2性能差不多,3比1和2略慢,但都比直接new字符串要快得多。JDK8使用JMH測試的數據如下:
      Benchmark                          Mode  Cnt       Score       Error   UnitsStringCreateBenchmark.invoke      thrpt    5  784869.350 ±  1936.754  ops/msStringCreateBenchmark.langAccess  thrpt    5  784029.186 ±  2734.300  ops/msStringCreateBenchmark.unsafe      thrpt    5  761176.319 ± 11914.549  ops/msStringCreateBenchmark.newString   thrpt    5  140883.533 ±  2217.773  ops/ms

      在JDK 9之后,對全部是ASCII字符的場景,直接構造能達到更好的效果。
      4.1 基于MethodHandles.Lookup & LambdaMetafactory綁定反射的快速構造字符串的方法。
      相關代碼地址[2]。4.1.1 JDK8快速構造字符串
      public static BiFunction<char[], Boolean, String> getStringCreatorJDK8() throws Throwable {Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);  constructor.setAccessible(true);    MethodHandles lookup = constructor.newInstance(String.class, -1 // Lookup.TRUSTED             );    MethodHandles.Lookup caller = lookup.in(String.class);MethodHandle handle = caller.findConstructor(String.class, MethodType.methodType(void.class, char[].class, boolean.class)                );
          CallSite callSite = LambdaMetafactory.metafactory(            caller            , "apply"            , MethodType.methodType(BiFunction.class)            , handle.type().generic()            , handle            , handle.type()            );
          return (BiFunction) callSite.getTarget().invokeExact();}
      4.1.2 JDK 11快速構造字符串的方法
      public static ToIntFunction<String> getStringCode11() throws Throwable {Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);constructor.setAccessible(true);MethodHandles.Lookup lookup = constructor.newInstance(            String.class            , -1 // Lookup.TRUSTED    );
      MethodHandles.Lookup caller = lookup.in(String.class);MethodHandle handle = caller.findVirtual(String.class, "coder", MethodType.methodType(byte.class)   );
      CallSite callSite = LambdaMetafactory.metafactory(caller, "applyAsInt", MethodType.methodType(ToIntFunction.class), MethodType.methodType(int.class, Object.class), handle, handle.type()    );
      return (ToIntFunction<String>) callSite.getTarget().invokeExact();}
      if (JDKUtils.JVM_VERSION == 11) {Function<byte[], String> stringCreator = JDKUtils.getStringCreatorJDK11();
      byte[] bytes = new byte[]{'a', 'b', 'c'};String apply = stringCreator.apply(bytes);assertEquals("abc", apply);}

      4.1.3 JDK 17快速構造字符串的方法

      在JDK 17中,MethodHandles.Lookup使用Reflection.registerFieldsToFilter對lookupClass和allowedModes做了保護,網上搜索到的通過修改allowedModes的辦法是不可用的。
      在JDK 17中,要通過配置JVM啟動參數才能使用MethodHandlers。如下:
      --add-opens java.base/java.lang.invoke=ALL-UNNAMED

      public static BiFunction<byte[], Charset, String> getStringCreatorJDK17() throws Throwable {Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, Class.class, int.class);constructor.setAccessible(true);MethodHandles.Lookup lookup = constructor.newInstance(String.class, null            , -1 // Lookup.TRUSTED    );
      MethodHandles.Lookup caller = lookup.in(String.class);MethodHandle handle = caller.findStatic(            String.class, "newStringNoRepl1", MethodType.methodType(String.class, byte[].class, Charset.class)    );
      CallSite callSite = LambdaMetafactory.metafactory(caller, "apply", MethodType.methodType(BiFunction.class), handle.type().generic(), handle            , handle.type()    );    return (BiFunction<byte[], Charset, String>) callSite.getTarget().invokeExact();}
      if (JDKUtils.JVM_VERSION == 17) {BiFunction<byte[], Charset, String> stringCreator = JDKUtils.getStringCreatorJDK17();
      byte[] bytes = new byte[]{'a', 'b', 'c'};String apply = stringCreator.apply(bytes, StandardCharsets.US_ASCII);assertEquals("abc", apply);}

      4.2 基于JavaLangAccess快速構造

      通過SharedSecrets提供的JavaLangAccess,也可以不拷貝構造字符串,但是這個比較麻煩,JDK 8/11/17的API都不一樣,對一套代碼兼容不同的JDK版本不方便,不建議使用。

      JavaLangAccess javaLangAccess = SharedSecrets.getJavaLangAccess();javaLangAccess.newStringNoRepl(b, StandardCharsets.US_ASCII);

      4.3 基于Unsafe實現快速構造字符串
      public static final Unsafe UNSAFE;static {Unsafe unsafe = null;try {Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");theUnsafeField.setAccessible(true);unsafe = (Unsafe) theUnsafeField.get(null);} catch (Throwable ignored) {}UNSAFE = unsafe;}
      ////////////////////////////////////////////
      Object str = UNSAFE.allocateInstance(String.class);UNSAFE.putObject(str, valueOffset, chars);

      注意:在JDK 9之后,實現是不同,比如:
      Object str = UNSAFE.allocateInstance(String.class);UNSAFE.putByte(str, coderOffset, (byte) );UNSAFE.putObject(str, valueOffset, (byte[]) bytes);

      4.4 快速構建字符串的技巧應用:


      如下的方法格式化日期為字符串,性能就會非常好。

      public String formatYYYYMMDD(Calendar calendar) throws Throwable {int year = calendar.get(Calendar.YEAR);int month = calendar.get(Calendar.MONTH) + 1;int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
      byte y0 = (byte) (year / 1000 + '0');byte y1 = (byte) ((year / 100) % 10 + '0');byte y2 = (byte) ((year / 10) % 10 + '0');byte y3 = (byte) (year % 10 + '0');byte m0 = (byte) (month / 10 + '0');byte m1 = (byte) (month % 10 + '0');byte d0 = (byte) (dayOfMonth / 10 + '0');byte d1 = (byte) (dayOfMonth % 10 + '0');
      if (JDKUtils.JVM_VERSION >= 9) {byte[] bytes = new byte[] {y0, y1, y2, y3, m0, m1, d0, d1};
      if (JDKUtils.JVM_VERSION == 17) {return JDKUtils.getStringCreatorJDK17().apply(bytes, StandardCharsets.US_ASCII);}
      if (JDKUtils.JVM_VERSION <= 11) {return JDKUtils.getStringCreatorJDK11().apply(bytes);}
      return new String(bytes, StandardCharsets.US_ASCII);}
      char[] chars = new char[]{(char) y0, (char) y1, (char) y2, (char) y3, (char) m0,(char) m1, (char) d0, (char) d1};
      if (JDKUtils.JVM_VERSION == 8) {return JDKUtils.getStringCreatorJDK8().apply(chars, true);}
      return new String(chars);}



      5

      快速遍歷字符串的辦法


      無論JDK什么版本,String.charAt都是一個較大的開銷,JIT的優化效果并不好,無法消除參數index范圍檢測的開銷,不如直接操作String里面的value數組。
      public final class String {private final char value[];public char charAt(int index) {if ((index < ) || (index >= value.length)) {throw new StringIndexOutOfBoundsException(index);}return value[index];}}

      在JDK 9之后的版本,charAt開銷更大
      public final class String {private final byte[] value;private final byte coder;public char charAt(int index) {if (isLatin1()) {return StringLatin1.charAt(value, index);} else {return StringUTF16.charAt(value, index);}}}

      5.1 獲取String.value的方法

      獲取String.value的方法有如下:
      1. 使用Field反射
      2. 使用Unsafe

      Unsafe和Field反射在JDK 8 JMH的比較數據如下:
      Benchmark                         Mode  Cnt        Score       Error   UnitsStringGetValueBenchmark.reflect  thrpt    5   438374.685 ±  1032.028  ops/msStringGetValueBenchmark.unsafe   thrpt    5  1302654.150 ± 59169.706  ops/ms

      5.1.1 使用反射獲取String.value
      static Field valueField;static {try {valueField = String.class.getDeclaredField("value");valueField.setAccessible(true);} catch (NoSuchFieldException ignored) {}}
      ////////////////////////////////////////////
      char[] chars = (char[]) valueField.get(str);

      5.1.2 使用Unsafe獲取String.value
      static long valueFieldOffset;static {try {Field valueField = String.class.getDeclaredField("value");valueFieldOffset = UNSAFE.objectFieldOffset(valueField);} catch (NoSuchFieldException ignored) {}}
      ////////////////////////////////////////////
      char[] chars = (char[]) UNSAFE.getObject(str, valueFieldOffset);
      static long valueFieldOffset;static long coderFieldOffset;static {try {Field valueField = String.class.getDeclaredField("value");valueFieldOffset = UNSAFE.objectFieldOffset(valueField);Field coderField = String.class.getDeclaredField("coder");coderFieldOffset = UNSAFE.objectFieldOffset(coderField);} catch (NoSuchFieldException ignored) {}}
      ////////////////////////////////////////////
      byte coder = UNSAFE.getObject(str, coderFieldOffset);byte[] bytes = (byte[]) UNSAFE.getObject(str, valueFieldOffset);


      6

      更快的encodeUTF8方法


      當能直接獲取到String.value時,就可以直接對其做encodeUTF8操作,會比String.getBytes(StandardCharsets.UTF_8)性能好很多。
      6.1 JDK8高性能encodeUTF8的方法
      public static int encodeUTF8(char[] src, int offset, int len, byte[] dst, int dp) {int sl = offset + len;int dlASCII = dp + Math.min(len, dst.length);
      // ASCII only optimized loopwhile (dp < dlASCII && src[offset] < '\u0080') {dst[dp++] = (byte) src[offset++];}
      while (offset < sl) {char c = src[offset++];if (c < 0x80) {// Have at most seven bitsdst[dp++] = (byte) c;} else if (c < 0x800) {// 2 bytes, 11 bitsdst[dp++] = (byte) (0xc0 | (c >> 6));dst[dp++] = (byte) (0x80 | (c & 0x3f));} else if (c >= '\uD800' && c < ('\uDFFF' + 1)) { //Character.isSurrogate(c) but 1.7final int uc;int ip = offset - 1;if (c >= '\uD800' && c < ('\uDBFF' + 1)) { // Character.isHighSurrogate(c)if (sl - ip < 2) {uc = -1;} else {char d = src[ip + 1];// d >= '\uDC00' && d < ('\uDFFF' + 1)if (d >= '\uDC00' && d < ('\uDFFF' + 1)) { // Character.isLowSurrogate(d)uc = ((c << 10) + d) + (0x010000 - ('\uD800' << 10) - '\uDC00'); // Character.toCodePoint(c, d)} else {dst[dp++] = (byte) '?';continue;}}} else {//if (c >= '\uDC00' && c < ('\uDFFF' + 1)) { // Character.isLowSurrogate(c)dst[dp++] = (byte) '?';continue;} else {uc = c;}}
      if (uc < ) {dst[dp++] = (byte) '?';} else {dst[dp++] = (byte) (0xf0 | ((uc >> 18)));dst[dp++] = (byte) (0x80 | ((uc >> 12) & 0x3f));dst[dp++] = (byte) (0x80 | ((uc >> 6) & 0x3f));dst[dp++] = (byte) (0x80 | (uc & 0x3f));offset++; // 2 chars}} else {// 3 bytes, 16 bitsdst[dp++] = (byte) (0xe0 | ((c >> 12)));dst[dp++] = (byte) (0x80 | ((c >> 6) & 0x3f));dst[dp++] = (byte) (0x80 | (c & 0x3f));}}return dp;}

      • 使用encodeUTF8方法舉例
      char[] chars = UNSAFE.getObject(str, valueFieldOffset);// ensureCapacity(chars.length * 3)byte[] bytes = ...; // int bytesLength = IOUtils.encodeUTF8(chars, , chars.length, bytes, bytesOffset);

      這樣encodeUTF8操作,不會有多余的arrayCopy操作,性能會得到提升。
      6.1.1 性能測試比較

      • 測試代碼

      public class EncodeUTF8Benchmark {static String STR = "01234567890ABCDEFGHIJKLMNOPQRSTUVWZYZabcdefghijklmnopqrstuvwzyz一二三四五六七八九十";static byte[] out;
      static long valueFieldOffset;
      static {out = new byte[STR.length() * 3];try {Field valueField = String.class.getDeclaredField("value");valueFieldOffset = UnsafeUtils.UNSAFE.objectFieldOffset(valueField);} catch (NoSuchFieldException e) {e.printStackTrace();}}
      @Benchmarkpublic void unsafeEncodeUTF8() throws Exception {char[] chars = (char[]) UnsafeUtils.UNSAFE.getObject(STR, valueFieldOffset);int len = IOUtils.encodeUTF8(chars, , chars.length, out, );}
      @Benchmarkpublic void getBytesUTF8() throws Exception {byte[] bytes = STR.getBytes(StandardCharsets.UTF_8);System.arraycopy(bytes, , out, , bytes.length);}
      public static void main(String[] args) throws RunnerException {Options options = new OptionsBuilder().include(EncodeUTF8Benchmark.class.getName()).mode(Mode.Throughput).timeUnit(TimeUnit.MILLISECONDS).forks(1).build();new Runner(options).run();}}

      • 測試結果

        EncodeUTF8Benchmark.getBytesUTF8      thrpt    5  20690.960 ± 5431.442  ops/msEncodeUTF8Benchmark.unsafeEncodeUTF8  thrpt    5  34508.606 ±   55.510  ops/ms

        從結果來看,通過unsafe + 直接調用encodeUTF8方法, 編碼的所需要開銷是newStringUTF8的58%。
        6.2 JDK9/11/17高性能encodeUTF8的方法
        public static int encodeUTF8(byte[] src, int offset, int len, byte[] dst, int dp) {int sl = offset + len;while (offset < sl) {byte b0 = src[offset++];byte b1 = src[offset++];
        if (b1 == && b >= ) {dst[dp++] = b0;} else {char c = (char)(((b0 & 0xff) << ) | ((b1 & 0xff) << 8));if (c < 0x800) {// 2 bytes, 11 bitsdst[dp++] = (byte) (0xc0 | (c >> 6));dst[dp++] = (byte) (0x80 | (c & 0x3f));} else if (c >= '\uD800' && c < ('\uDFFF' + 1)) { //Character.isSurrogate(c) but 1.7final int uc;int ip = offset - 1;if (c >= '\uD800' && c < ('\uDBFF' + 1)) { // Character.isHighSurrogate(c)if (sl - ip < 2) {uc = -1;} else {b0 = src[ip + 1];b1 = src[ip + 2];char d = (char) (((b0 & 0xff) << ) | ((b1 & 0xff) << 8));// d >= '\uDC00' && d < ('\uDFFF' + 1)if (d >= '\uDC00' && d < ('\uDFFF' + 1)) { // Character.isLowSurrogate(d)uc = ((c << 10) + d) + (0x010000 - ('\uD800' << 10) - '\uDC00'); // Character.toCodePoint(c, d)} else {return -1;}}} else {//if (c >= '\uDC00' && c < ('\uDFFF' + 1)) { // Character.isLowSurrogate(c)return -1;} else {uc = c;}}
        if (uc < ) {dst[dp++] = (byte) '?';} else {dst[dp++] = (byte) (0xf0 | ((uc >> 18)));dst[dp++] = (byte) (0x80 | ((uc >> 12) & 0x3f));dst[dp++] = (byte) (0x80 | ((uc >> 6) & 0x3f));dst[dp++] = (byte) (0x80 | (uc & 0x3f));offset++; // 2 chars}} else {// 3 bytes, 16 bitsdst[dp++] = (byte) (0xe0 | ((c >> 12)));dst[dp++] = (byte) (0x80 | ((c >> 6) & 0x3f));dst[dp++] = (byte) (0x80 | (c & 0x3f));}}}return dp;}

        • 使用encodeUTF8方法舉例

        byte coder = UNSAFE.getObject(str, coderFieldOffset);byte[] value = UNSAFE.getObject(str, coderFieldOffset);
        if (coder == ) {// ascii arraycopy} else {// ensureCapacity(chars.length * 3)byte[] bytes = ...; // int bytesLength = IOUtils.encodeUTF8(value, , value.length, bytes, bytesOffset);}

        這樣encodeUTF8操作,不會有多余的arrayCopy操作,性能會得到提升。


        7

        重要提醒


        上面這些技巧都不是給新手使用的,使用不當會容易導致BUG,如果沒徹底搞懂,請不要使用!
        參考
        Linux預定任務調度(crontab) 好實用!
        ? 上一篇 2022-05-18
        Java處理異常的9個最佳實踐 你做得對嗎?
        下一篇 ? 2022-05-18
        • 如何在Ubuntu中保留文件系統并備份當前開發板鏡像
          0閱讀 0條評論 個贊
          在Ubuntu保留文件系統或者說備份當前開發板鏡像的需求在不斷增加。比如Ubuntu文件系統需要安裝庫文件的話直接使用apt-get工具就可以下載,但由于需要下載的核心板較多,比較費時間,這時需要將安……
        • 國產核心板全志T507助力消防系統升級
          0閱讀 0條評論 個贊
          9月16日下午,位于湖南長沙市區內的中國電信大樓發生火災,建筑高度218米,現場濃煙滾滾,數十層樓體燃燒劇烈。消防救援人員趕到現場后很快將火勢控制住,目前大樓火勢已被撲滅,所幸未發現人員傷亡。湖南電信……
        • 教大家如何處理Spring Boot易流中的用戶和群體!
          0閱讀 0條評論 個贊
          1.準備工作2.用戶操作2.1添加用戶2.2修改用戶2.3刪除用戶2.4查詢用戶3.組操作3.1添加組3.2修改組3.3刪除組3.4查詢組4.查看表詳情雖然說我們在實際開發中,……
        • 從PG15開始WAL壓縮優化
          0閱讀 0條評論 個贊
          PG15傳聞中的超級令人激動的功能大多數跳票了,年初我也寫過一個關于PG15新功能跳票的文章。PG15BETA已經發出幾個月了,似乎PG15里令人激動人心的功能不多,不過從長長的新功能列表里,……
        • 深入了解美團葉子發射器開源方案
          0閱讀 0條評論 個贊
          大家好,我是樹哥。之前我們有聊過「如何設計一個分布式ID發號器」,其中有講過4種解決方案,分別是:UUID類雪花算法數據庫自增主鍵Redis原子自增美團以第2、3種解決方案為基礎,開發出……
        發表評論 共有條評論
        用戶名: 密碼:
        驗證碼: 匿名發表
        最近發布資訊
        更多
        警花高潮嗷嗷叫
        <del id="nnjnj"></del><track id="nnjnj"></track>

        <p id="nnjnj"></p>

        <address id="nnjnj"></address>

          <pre id="nnjnj"><pre id="nnjnj"></pre></pre>

            <noframes id="nnjnj"><ruby id="nnjnj"><ruby id="nnjnj"></ruby></ruby>