乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      面經(jīng)手冊(cè) · 第11篇《StringBuilder 比 String 快?空嘴白牙的,證據(jù)呢!》

       小傅哥 2021-12-13

      作者:小傅哥
      博客:https://

      沉淀、分享、成長(zhǎng),讓自己和他人都能有所收獲!😄

      一、前言

      聊的是八股的文,干的是搬磚的活!

      面我的題開發(fā)都用不到,你為什么要問(wèn)?可能這是大部分程序員求職時(shí)的經(jīng)歷,甚至也是大家討厭和煩躁的點(diǎn)。明明給的是擰螺絲的錢、明明做的是寫CRUD的事、明明擔(dān)的是成工具的人!

      明明… 有很多,可明明公司不會(huì)招5年開發(fā)做3年經(jīng)驗(yàn)的事、明明公司也更喜歡具有附加價(jià)值的研發(fā)。有些小公司不好說(shuō),但在一些互聯(lián)網(wǎng)大廠中,我們都希望招聘到具有培養(yǎng)價(jià)值的,也更喜歡能快速打怪升級(jí)的,也更愿意讓這樣的人承擔(dān)更大的職責(zé)。

      但,你酸了! 別人看源碼你打游戲、別人學(xué)算法你刷某音、別人寫博客你浪98。所以,沒有把時(shí)間用到個(gè)人成長(zhǎng)上,就一直會(huì)被別人榨取。

      二、面試題

      謝飛機(jī),總感覺自己有技術(shù)瓶頸、有知識(shí)盲區(qū),但是又不知道在哪。所以約面試官聊天,雖然也面不過(guò)去!

      面試官:飛機(jī),你又抱著大臉,來(lái)白嫖我了啦?

      謝飛機(jī):嘿嘿,我需要知識(shí),我渴。

      面試官:好,那今天聊聊最常用的 String 吧,你怎么初始化一個(gè)字符串類型。

      謝飛機(jī)String str = "abc";

      面試官:還有嗎?

      謝飛機(jī):還有?啊,這樣 String str = new String("abc"); 😄

      面試官:還有嗎?

      謝飛機(jī):啊!?還有!不知道了!

      面試官:你不懂 String,你沒看過(guò)源碼。還可以這樣;new String(new char[]{'c', 'd'}); 回家再學(xué)學(xué)吧,下次記得給我買百事,我不喝可口。

      三、StringBuilder 比 String 快嗎?

      1. StringBuilder 比 String 快,證據(jù)呢?

      老子代碼一把梭,總有人絮叨這么搞不好,那 StringBuilder 到底那快了!

      1.1 String

      long startTime = System.currentTimeMillis();
      String str = "";
      for (int i = 0; i < 1000000; i++) {
          str += i;
      }
      System.out.println("String 耗時(shí):" + (System.currentTimeMillis() - startTime) + "毫秒");
      

      1.2 StringBuilder

      long startTime = System.currentTimeMillis();
      StringBuilder str = new StringBuilder();
      for (int i = 0; i < 1000000; i++) {
          str.append(i);
      }
      System.out.println("StringBuilder 耗時(shí)" + (System.currentTimeMillis() - startTime) + "毫秒");
      

      1.3 StringBuffer

      long startTime = System.currentTimeMillis();
      StringBuffer str = new StringBuffer();
      for (int i = 0; i < 1000000; i++) {
          str.append(i);
      }
      System.out.println("StringBuffer 耗時(shí)" + (System.currentTimeMillis() - startTime) + "毫秒");
      

      綜上,分別使用了 String、StringBuilderStringBuffer,做字符串鏈接操作(100個(gè)、1000個(gè)、1萬(wàn)個(gè)、10萬(wàn)個(gè)、100萬(wàn)個(gè)),記錄每種方式的耗時(shí)。最終匯總圖表如下;

      小傅哥 & 耗時(shí)對(duì)比

      從上圖可以得出以下結(jié)論;

      1. String 字符串鏈接是耗時(shí)的,尤其數(shù)據(jù)量大的時(shí)候,簡(jiǎn)直沒法使用了。這是做實(shí)驗(yàn),基本也不會(huì)有人這么干!
      2. StringBuilder、StringBuffer,因?yàn)闆]有發(fā)生多線程競(jìng)爭(zhēng)也就沒有🔒鎖升級(jí),所以兩個(gè)類耗時(shí)幾乎相同,當(dāng)然在單線程下更推薦使用 StringBuilder 。

      2. StringBuilder 比 String 快, 為什么?

      String str = "";
      for (int i = 0; i < 10000; i++) {
          str += i;
      }
      

      這段代碼就是三種字符串拼接方式,最慢的一種。不是說(shuō)這種+加的符號(hào),會(huì)被優(yōu)化成 StringBuilder 嗎,那怎么還慢?

      確實(shí)會(huì)被JVM編譯期優(yōu)化,但優(yōu)化成什么樣子了呢,先看下字節(jié)碼指令;javap -c ApiTest.class

      小傅哥 & 反編譯

      一看指令碼,這不是在循環(huán)里(if_icmpgt)給我 newStringBuilder 了嗎,怎么還這么慢呢?再仔細(xì)看,其實(shí)你會(huì)發(fā)現(xiàn),這new是在循環(huán)里嗎呀,我們把這段代碼寫出來(lái)再看看;

      String str = "";
      for (int i = 0; i < 10000; i++) {
          str = new StringBuilder().append(str).append(i).toString();
      }
      

      現(xiàn)在再看這段代碼就很清晰了,所有的字符串鏈接操作,都需要實(shí)例化一次StringBuilder,所以非常耗時(shí)。并且你可以驗(yàn)證,這樣寫代碼耗時(shí)與字符串直接鏈接是一樣的。 所以把StringBuilder 提到上一層 for 循環(huán)外更快。

      四、String 源碼分析

      public final class String
          implements java.io.Serializable, Comparable<String>, CharSequence {
          /** The value is used for character storage. */
          private final char value[];
      
          /** Cache the hash code for the string */
          private int hash; // Default to 0
      
          /** use serialVersionUID from JDK 1.0.2 for interoperability */
          private static final long serialVersionUID = -6849794470754667710L;
       
          ...
      }
      

      1. 初始化

      在與 謝飛機(jī) 的面試題中,我們聊到了 String 初始化的問(wèn)題,按照一般我們應(yīng)用的頻次上,能想到的只有直接賦值,String str = "abc";,但因?yàn)?String 的底層數(shù)據(jù)結(jié)構(gòu)是數(shù)組char value[],所以它的初始化方式也會(huì)有很多跟數(shù)組相關(guān)的,如下;

      String str_01 = "abc";
      System.out.println("默認(rèn)方式:" + str_01);
      
      String str_02 = new String(new char[]{'a', 'b', 'c'});
      System.out.println("char方式:" + str_02);
      
      String str_03 = new String(new int[]{0x61, 0x62, 0x63}, 0, 3);
      System.out.println("int方式:" + str_03);
      
      String str_04 = new String(new byte[]{0x61, 0x62, 0x63});
      System.out.println("byte方式:" + str_04);
      

      以上這些方式都可以初始化,并且最終的結(jié)果是一致的,abc。如果說(shuō)初始化的方式?jīng)]用讓你感受到它是數(shù)據(jù)結(jié)構(gòu),那么str_01.charAt(0);呢,只要你往源碼里一點(diǎn),就會(huì)發(fā)現(xiàn)它是 O(1) 的時(shí)間復(fù)雜度從數(shù)組中獲取元素,所以效率也是非常高,源碼如下;

      public char charAt(int index) {
          if ((index < 0) || (index >= value.length)) {
              throw new StringIndexOutOfBoundsException(index);
          }
          return value[index];
      }
      

      2. 不可變(final)

      字符串創(chuàng)建后是不可變的,你看到的+加號(hào)連接操作,都是創(chuàng)建了新的對(duì)象把數(shù)據(jù)存放過(guò)去,通過(guò)源碼就可以看到;

      小傅哥 & String 不可變

      從源碼中可以看到,String 的類和用于存放字符串的方法都用了 final 修飾,也就是創(chuàng)建了以后,這些都是不可變的。

      舉個(gè)例子

      String str_01 = "abc";
      String str_02 = "abc" + "def";
      String str_03 = str_01 + "def";
      

      不考慮其他情況,對(duì)于程序初始化。以上這些代碼 str_01str_02str_03,都會(huì)初始化幾個(gè)對(duì)象呢?其實(shí)這個(gè)初始化幾個(gè)對(duì)象從側(cè)面就是反應(yīng)對(duì)象是否可變性。

      接下來(lái)我們把上面代碼反編譯,通過(guò)指令碼看到底創(chuàng)建了幾個(gè)對(duì)象。

      反編譯下

        public void test_00();
          Code:
             0: ldc           #2                  // String abc
             2: astore_1
             3: ldc           #3                  // String abcdef
             5: astore_2
             6: new           #4                  // class java/lang/StringBuilder
             9: dup
            10: invokespecial #5                  // Method java/lang/StringBuilder."<init>":()V
            13: aload_1
            14: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            17: ldc           #7                  // String def
            19: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            22: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
            25: astore_3
            26: return
      
      • str_01 = "abc",指令碼:0: ldc,創(chuàng)建了一個(gè)對(duì)象。
      • str_02 = "abc" + "def",指令碼:3: ldc // String abcdef,得益于JVM編譯期的優(yōu)化,兩個(gè)字符串會(huì)進(jìn)行相連,創(chuàng)建一個(gè)對(duì)象存儲(chǔ)。
      • str_03 = str_01 + "def",指令碼:invokevirtual,這個(gè)就不一樣了,它需要把兩個(gè)字符串相連,會(huì)創(chuàng)建StringBuilder對(duì)象,直至最后toString:()操作,共創(chuàng)建了三個(gè)對(duì)象。

      所以,我們看到,字符串的創(chuàng)建是不能被修改的,相連操作會(huì)創(chuàng)建出新對(duì)象。

      3. intern()

      3.1 經(jīng)典題目

      String str_1 = new String("ab");
      String str_2 = new String("ab");
      String str_3 = "ab";
      
      System.out.println(str_1 == str_2);
      System.out.println(str_1 == str_2.intern());
      System.out.println(str_1.intern() == str_2.intern());
      System.out.println(str_1 == str_3);
      System.out.println(str_1.intern() == str_3);
      

      這是一道經(jīng)典的 String 字符串面試題,乍一看可能還會(huì)有點(diǎn)暈。答案如下;

      false
      false
      true
      false
      true
      

      3.2 源碼分析

      看了答案有點(diǎn)感覺了嗎,其實(shí)可能你了解方法 intern(),這里先看下它的源碼;

      /**
       * Returns a canonical representation for the string object.
       * <p>
       * A pool of strings, initially empty, is maintained privately by the
       * class {@code String}.
       * <p>
       * When the intern method is invoked, if the pool already contains a
       * string equal to this {@code String} object as determined by
       * the {@link #equals(Object)} method, then the string from the pool is
       * returned. Otherwise, this {@code String} object is added to the
       * pool and a reference to this {@code String} object is returned.
       * <p>
       * It follows that for any two strings {@code s} and {@code t},
       * {@code s.intern() == t.intern()} is {@code true}
       * if and only if {@code s.equals(t)} is {@code true}.
       * <p>
       * All literal strings and string-valued constant expressions are
       * interned. String literals are defined in section 3.10.5 of the
       * <cite>The Java&trade; Language Specification</cite>.
       *
       * @return  a string that has the same contents as this string, but is
       *          guaranteed to be from a pool of unique strings.
       */
      public native String intern();
      

      這段代碼和注釋什么意思呢?

      native,說(shuō)明 intern() 是一個(gè)本地方法,底層通過(guò)JNI調(diào)用C++語(yǔ)言編寫的功能。

      \openjdk8\jdk\src\share\native\java\lang\String.c

      Java_java_lang_String_intern(JNIEnv *env, jobject this)  
      {  
          return JVM_InternString(env, this);  
      }  
      
      oop result = StringTable::intern(string, CHECK_NULL);
      
      oop StringTable::intern(Handle string_or_null, jchar* name,  
                              int len, TRAPS) {  
        unsigned int hashValue = java_lang_String::hash_string(name, len);  
        int index = the_table()->hash_to_index(hashValue);  
        oop string = the_table()->lookup(index, name, len, hashValue);  
        if (string != NULL) return string;   
        return the_table()->basic_add(index, string_or_null, name, len,  
                                      hashValue, CHECK_NULL);  
      }  
      
      • 代碼塊有點(diǎn)長(zhǎng)這里只截取了部分內(nèi)容,源碼可以學(xué)習(xí)開源jdk代碼,連接: https://codeload.github.com/abhijangda/OpenJDK8/zip/master
      • C++這段代碼有點(diǎn)像HashMap的哈希桶+鏈表的數(shù)據(jù)結(jié)構(gòu),用來(lái)存放字符串,所以如果哈希值沖突嚴(yán)重,就會(huì)導(dǎo)致鏈表過(guò)長(zhǎng)。這在我們講解hashMap中已經(jīng)介紹,可以回看 HashMap源碼
      • StringTable 是一個(gè)固定長(zhǎng)度的數(shù)組 1009 個(gè)大小,jdk1.6不可調(diào)、jdk1.7可以設(shè)置-XX:StringTableSize,按需調(diào)整。

      3.3 問(wèn)題圖解

      小傅哥 & 圖解true/false

      看圖說(shuō)話,如下;

      1. 先說(shuō) ==,基礎(chǔ)類型比對(duì)的是值,引用類型比對(duì)的是地址。另外,equal 比對(duì)的是哈希值。
      2. 兩個(gè)new出來(lái)的對(duì)象,地址肯定不同,所以是false。
      3. intern(),直接把值推進(jìn)了常量池,所以兩個(gè)對(duì)象都做了 intern() 操作后,比對(duì)是常量池里的值。
      4. str_3 = "ab",賦值,JVM編譯器做了優(yōu)化,不會(huì)重新創(chuàng)建對(duì)象,直接引用常量池里的值。所以str_1.intern() == str_3,比對(duì)結(jié)果是true。

      理解了這個(gè)結(jié)構(gòu),根本不需要死記硬背應(yīng)對(duì)面試,讓懂了就是真的懂,大腦也會(huì)跟著愉悅。

      五、StringBuilder 源碼分析

      1. 初始化

      new StringBuilder();
      new StringBuilder(16);
      new StringBuilder("abc");
      

      這幾種方式都可以初始化,你可以傳一個(gè)初始化容量,也可以初始化一個(gè)默認(rèn)的字符串。它的源碼如下;

      public StringBuilder() {
          super(16);
      }
      
      AbstractStringBuilder(int capacity) {
          value = new char[capacity];
      }
      

      定睛一看,這就是在初始化數(shù)組呀!那是不操作起來(lái)跟使用 ArrayList 似的呀!

      2. 添加元素

      stringBuilder.append("a");
      stringBuilder.append("b");
      stringBuilder.append("c");
      

      添加元素的操作很簡(jiǎn)單,使用 append 即可,那么它是怎么往數(shù)組中存放的呢,需要擴(kuò)容嗎?

      2.1 入口方法

      public AbstractStringBuilder append(String str) {
          if (str == null)
              return appendNull();
          int len = str.length();
          ensureCapacityInternal(count + len);
          str.getChars(0, len, value, count);
          count += len;
          return this;
      }
      
      • 這個(gè)是 public final class StringBuilder extends AbstractStringBuilder,的父類與 StringBuffer 共用這個(gè)方法。
      • 這里包括了容量檢測(cè)、元素拷貝、記錄 count 數(shù)量。

      2.2 擴(kuò)容操作

      ensureCapacityInternal(count + len);

      /**
       * This method has the same contract as ensureCapacity, but is
       * never synchronized.
       */
      private void ensureCapacityInternal(int minimumCapacity) {
          // overflow-conscious code
          if (minimumCapacity - value.length > 0)
              expandCapacity(minimumCapacity);
      }
      
      /**
       * This implements the expansion semantics of ensureCapacity with no
       * size check or synchronization.
       */
      void expandCapacity(int minimumCapacity) {
          int newCapacity = value.length * 2 + 2;
          if (newCapacity - minimumCapacity < 0)
              newCapacity = minimumCapacity;
          if (newCapacity < 0) {
              if (minimumCapacity < 0) // overflow
                  throw new OutOfMemoryError();
              newCapacity = Integer.MAX_VALUE;
          }
          value = Arrays.copyOf(value, newCapacity);
      }
      

      如上,StringBuilder,就跟操作數(shù)組的原理一樣,都需要檢測(cè)容量大小,按需擴(kuò)容。擴(kuò)容的容量是 n * 2 + 2,另外把原有元素拷貝到新新數(shù)組中。

      2.3 填充元素

      str.getChars(0, len, value, count);

      public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
          // ...
          System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
      }
      

      添加元素的方式是基于 System.arraycopy 拷貝操作進(jìn)行的,這是一個(gè)本地方法。

      2.4 toString()

      既然 stringBuilder 是數(shù)組,那么它是怎么轉(zhuǎn)換成字符串的呢?

      stringBuilder.toString();

      @Override
      public String toString() {
          // Create a copy, don't share the array
          return new String(value, 0, count);
      }
      

      其實(shí)需要用到它是 String 字符串的時(shí)候,就是使用 String 的構(gòu)造函數(shù)傳遞數(shù)組進(jìn)行轉(zhuǎn)換的,這個(gè)方法在我們上面講解 String 的時(shí)候已經(jīng)介紹過(guò)。

      六、StringBuffer 源碼分析

      StringBufferStringBuilder,API的使用和底層實(shí)現(xiàn)上基本一致,維度不同的是 StringBuffer 加了 synchronized 🔒鎖,所以它是線程安全的。源碼如下;

      @Override
      public synchronized StringBuffer append(String str) {
          toStringCache = null;
          super.append(str);
          return this;
      }
      

      那么,synchronized 不是重量級(jí)鎖嗎,JVM對(duì)它有什么優(yōu)化呢?

      其實(shí)為了減少獲得鎖與釋放鎖帶來(lái)的性能損耗,從而引入了偏向鎖、輕量級(jí)鎖、重量級(jí)鎖來(lái)進(jìn)行優(yōu)化,它的進(jìn)行一個(gè)鎖升級(jí),如下圖(此圖引自互聯(lián)網(wǎng)用戶:韭韭韭韭菜,畫的非常優(yōu)秀);

      小傅哥 & 此圖引自互聯(lián)網(wǎng),畫的非常漂亮

      1. 從無(wú)鎖狀態(tài)開始,當(dāng)線程進(jìn)入 synchronized 同步代碼塊,會(huì)檢查對(duì)象頭和棧幀內(nèi)是否有當(dāng)前線下ID編號(hào),無(wú)則使用 CAS 替換。
      2. 解鎖時(shí),會(huì)使用 CASDisplaced Mark Word 替換回到對(duì)象頭,如果成功,則表示競(jìng)爭(zhēng)沒有發(fā)生,反之則表示當(dāng)前鎖存在競(jìng)爭(zhēng)鎖就會(huì)升級(jí)成重量級(jí)鎖。
      3. 另外,大多數(shù)情況下鎖🔒是不發(fā)生競(jìng)爭(zhēng)的,基本由一個(gè)線程持有。所以,為了避免獲得鎖與釋放鎖帶來(lái)的性能損耗,所以引入鎖升級(jí),升級(jí)后不能降級(jí)。

      七、常用API

      序號(hào)方法描述
      1str.concat("cde")字符串連接,替換+號(hào)
      2str.length()獲取長(zhǎng)度
      3isEmpty()判空
      4str.charAt(0)獲取指定位置元素
      5str.codePointAt(0)獲取指定位置元素,并返回ascii碼值
      6str.getBytes()獲取byte[]
      7str.equals(“abc”)比較
      8str.equalsIgnoreCase(“AbC”)忽略大小寫,比對(duì)
      9str.startsWith(“a”)開始位置值判斷
      10str.endsWith(“c”)結(jié)尾位置值判斷
      11str.indexOf(“b”)判斷元素位置,開始位置
      12str.lastIndexOf(“b”)判斷元素位置,結(jié)尾位置
      13str.substring(0, 1)截取
      14str.split(",")拆分,可以支持正則
      15str.replace(“a”,“d”)、replaceAll替換
      16str.toUpperCase()轉(zhuǎn)大寫
      17str.toLowerCase()轉(zhuǎn)小寫
      18str.toCharArray()轉(zhuǎn)數(shù)組
      19String.format(str, “”)格式化,%s、%c、%b、%d、%x、%o、%f、%a、%e、%g、%h、%%、%n、%tx
      20str.valueOf(“123”)轉(zhuǎn)字符串
      21trim()格式化,首尾去空格
      22str.hashCode()獲取哈希值

      八、總結(jié)

      • 業(yè)精于勤,荒于嬉,你學(xué)到的知識(shí)不一定只是為了面試準(zhǔn)備,還更應(yīng)該是拓展自己的技術(shù)深度和廣度。這個(gè)過(guò)程可能很痛苦,但總得需要某一個(gè)燒腦的過(guò)程,才讓其他更多的知識(shí)學(xué)起來(lái)更加容易。
      • 本文介紹了 String、StringBuilder、StringBuffer,的數(shù)據(jù)結(jié)構(gòu)和源碼分析,更加透徹的理解后,也能更加準(zhǔn)確的使用,不會(huì)被因?yàn)椴欢稿e(cuò)誤。
      • 想把代碼寫好,至少要有這四面內(nèi)容,包括;數(shù)據(jù)結(jié)構(gòu)、算法、源碼、設(shè)計(jì)模式,這四方面在加上業(yè)務(wù)經(jīng)驗(yàn)與個(gè)人視野,才能真的把一個(gè)需求、一個(gè)大項(xiàng)目寫的具備良好的擴(kuò)展性和易維護(hù)性。

      九、系列推薦

      • 握草,你竟然在代碼里下毒!
      • 一次代碼評(píng)審,差點(diǎn)過(guò)不了試用期!
      • LinkedList插入速度比ArrayList快?你確定嗎?
      • 重學(xué)Java設(shè)計(jì)模式(22個(gè)真實(shí)開發(fā)場(chǎng)景)
      • 面經(jīng)手冊(cè)(上最快的車,拿最貴的offer)

        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類似文章 更多