亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

StringBuilder比String快嗎

發布時間:2021-10-28 13:51:38 來源:億速云 閱讀:188 作者:iii 欄目:編程語言

這篇文章主要講解了“StringBuilder比String快嗎”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“StringBuilder比String快嗎”吧!

StringBuilder 比 String 快嗎? 

1. StringBuilder 比 String 快,證據呢?

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

1.1 String
long startTime = System.currentTimeMillis();
String str = "";
for (int i = 0; i < 1000000; i++) {
    str += i;
}
System.out.println("String 耗時:" + (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 耗時" + (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 耗時" + (System.currentTimeMillis() - startTime) + "毫秒");
 

「綜上」,分別使用了 StringStringBuilderStringBuffer,做字符串鏈接操作(100個、1000個、1萬個、10萬個、100萬個),記錄每種方式的耗時。最終匯總圖表如下;

StringBuilder比String快嗎

從上圖可以得出以下結論;

  1. String 字符串鏈接是耗時的,尤其數據量大的時候,簡直沒法使用了。     這是做實驗,基本也不會有人這么干!
  2. StringBuilder、     StringBuffer,因為沒有發生多線程競爭也就沒有????鎖升級,所以兩個類耗時幾乎相同,當然在單線程下更推薦使用     StringBuilder 。 

2. StringBuilder 比 String 快, 為什么?

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

這段代碼就是三種字符串拼接方式,最慢的一種。不是說這種+加的符號,會被優化成 StringBuilder 嗎,那怎么還慢?

確實會被JVM編譯期優化,但優化成什么樣子了呢,先看下字節碼指令;javap -c ApiTest.class

StringBuilder比String快嗎

一看指令碼,這不是在循環里(if_icmpgt)給我 newStringBuilder 了嗎,怎么還這么慢呢?再仔細看,其實你會發現,這new是在循環里嗎呀,我們把這段代碼寫出來再看看;

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

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

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. 初始化

在與 謝飛機 的面試題中,我們聊到了 String 初始化的問題,按照一般我們應用的頻次上,能想到的只有直接賦值,String str = "abc";,但因為 String 的底層數據結構是數組char value[],所以它的初始化方式也會有很多跟數組相關的,如下;

String str_01 = "abc";
System.out.println("默認方式:" + 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);
 

以上這些方式都可以初始化,并且最終的結果是一致的,abc。如果說初始化的方式沒用讓你感受到它是數據結構,那么str_01.charAt(0);呢,只要你往源碼里一點,就會發現它是 O(1) 的時間復雜度從數組中獲取元素,所以效率也是非常高,源碼如下;

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

2. 不可變(final)

字符串創建后是不可變的,你看到的+加號連接操作,都是創建了新的對象把數據存放過去,通過源碼就可以看到;

StringBuilder比String快嗎  
小傅哥 & String 不可變

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

「舉個例子」

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

不考慮其他情況,對于程序初始化。以上這些代碼 str_01str_02str_03,都會初始化幾個對象呢?其實這個初始化幾個對象從側面就是反應對象是否可變性。

接下來我們把上面代碼反編譯,通過指令碼看到底創建了幾個對象。

「反編譯下」

  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,創建了一個對象。
  • str_02 = "abc" + "def",指令碼:     3: ldc // String abcdef,得益于JVM編譯期的優化,兩個字符串會進行相連,創建一個對象存儲。
  • str_03 = str_01 + "def",指令碼:     invokevirtual,這個就不一樣了,它需要把兩個字符串相連,會創建     StringBuilder對象,直至最后     toString:()操作,共創建了三個對象。

「所以」,我們看到,字符串的創建是不能被修改的,相連操作會創建出新對象。 

3. intern() 

3.1 經典題目
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);
 

這是一道經典的 String 字符串面試題,乍一看可能還會有點暈。答案如下;

false
false
true
false
true
   
3.2 源碼分析

看了答案有點感覺了嗎,其實可能你了解方法 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」,說明 intern() 是一個本地方法,底層通過JNI調用C++語言編寫的功能。

「\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);  
}  
 
  • 代碼塊有點長這里只截取了部分內容,源碼可以學習開源jdk代碼,連接:https://codeload.github.com/abhijangda/OpenJDK8/zip/master
  • C++這段代碼有點像HashMap的哈希桶+鏈表的數據結構,用來存放字符串,所以如果哈希值沖突嚴重,就會導致鏈表過長。這在我們講解hashMap中已經介紹,可以回看 HashMap源碼
  • StringTable 是一個固定長度的數組     1009 個大小,jdk1.6不可調、jdk1.7可以設置     -XX:StringTableSize,按需調整。 
3.3 問題圖解
StringBuilder比String快嗎

看圖說話,如下;

  1. 先說     ==,基礎類型比對的是值,引用類型比對的是地址。另外,equal 比對的是哈希值。
  2. 兩個new出來的對象,地址肯定不同,所以是false。
  3. intern(),直接把值推進了常量池,所以兩個對象都做了     intern() 操作后,比對是常量池里的值。
  4. str_3 = "ab",賦值,JVM編譯器做了優化,不會重新創建對象,直接引用常量池里的值。所以     str_1.intern() == str_3,比對結果是true。

理解了這個結構,根本不需要死記硬背應對面試,讓懂了就是真的懂,大腦也會跟著愉悅。 

StringBuilder 源碼分析 

1. 初始化

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

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

public StringBuilder() {
    super(16);
}

AbstractStringBuilder(int capacity) {
    value = new char[capacity];
}
 

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

 2. 添加元素
stringBuilder.append("a");
stringBuilder.append("b");
stringBuilder.append("c");
 

添加元素的操作很簡單,使用 append 即可,那么它是怎么往數組中存放的呢,需要擴容嗎? 

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;
}
 
  • 這個是     public final class StringBuilder extends AbstractStringBuilder,的父類與     StringBuffer 共用這個方法。
  • 這里包括了容量檢測、元素拷貝、記錄     count 數量。 
2.2 擴容操作

「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,就跟操作數組的原理一樣,都需要檢測容量大小,按需擴容。擴容的容量是 n * 2 + 2,另外把原有元素拷貝到新新數組中。 

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 拷貝操作進行的,這是一個本地方法。 

2.4 toString()

既然 stringBuilder 是數組,那么它是怎么轉換成字符串的呢?

stringBuilder.toString();

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

其實需要用到它是 String 字符串的時候,就是使用 String 的構造函數傳遞數組進行轉換的,這個方法在我們上面講解 String 的時候已經介紹過。 

StringBuffer 源碼分析

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

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

那么,synchronized 不是重量級鎖嗎,JVM對它有什么優化呢?

其實為了減少獲得鎖與釋放鎖帶來的性能損耗,從而引入了偏向鎖、輕量級鎖、重量級鎖來進行優化,它的進行一個鎖升級,如下圖(此圖引自互聯網用戶:「韭韭韭韭菜」,畫的非常優秀);

StringBuilder比String快嗎
  1. 從無鎖狀態開始,當線程進入     synchronized 同步代碼塊,會檢查對象頭和棧幀內是否有當前線下ID編號,無則使用     CAS 替換。
  2. 解鎖時,會使用     CAS 將     Displaced Mark Word 替換回到對象頭,如果成功,則表示競爭沒有發生,反之則表示當前鎖存在競爭鎖就會升級成重量級鎖。
  3. 另外,大多數情況下鎖????是不發生競爭的,基本由一個線程持有。所以,為了避免獲得鎖與釋放鎖帶來的性能損耗,所以引入鎖升級,升級后不能降級。

常用API

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

感謝各位的閱讀,以上就是“StringBuilder比String快嗎”的內容了,經過本文的學習后,相信大家對StringBuilder比String快嗎這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

鄂托克旗| 天祝| 永春县| 开江县| 南部县| 会理县| 潞西市| 新邵县| 德令哈市| 开江县| 华容县| 商都县| 陇西县| 桐庐县| 彰化市| 嘉兴市| 贞丰县| 平利县| 方正县| 郴州市| 墨竹工卡县| 厦门市| 宜阳县| 双峰县| 柳州市| 通江县| 汉源县| 华池县| 丁青县| 高要市| 漯河市| 北宁市| 斗六市| 洪江市| 宿松县| 长海县| 乌什县| 海口市| 河北区| 凤庆县| 郯城县|