主頁 > 後端開發 > 第 13 章 StringTable詳解

第 13 章 StringTable詳解

2020-10-13 09:17:25 後端開發

目錄
  • 第 13 章 StringTable
    • 1、String 的基本特性
      • 1.1、String 概述
      • 1.2、String 的基本特征
      • 1.3、String 的底層結構
    • 2、String 的記憶體分配
      • 2.1、String 記憶體分配演程序序
      • 2.2、為什么要調整 String 位置
    • 3、String 的基本操作
    • 4、字串拼接操作
      • 4.1、符串拼接操作的結論
      • 4.2、字串拼接的底層細節
    • 5、intern() 的使用
      • 5.1、intern() 方法的說明
      • 5.2、new String() 的說明
      • 5.3、有點難的面試題
      • 5.4、intern() 方法的總結
      • 5.5、intern() 方法的練習
      • 5.6、intern() 方法效率測驗
    • 6、StringTable 的垃圾回收
    • 7、G1 中的 String 去重操作

微信搜一搜: 全堆疊小劉,獲取文章全套 pdf版

第 13 章 StringTable

1、String 的基本特性

1.1、String 概述

String 的概述

  1. String:字串,使用一對 "" 引起來表示
String s1 = "mogublog" ;
String s2 =  new String("moxi");
  1. String宣告為final的,不可被繼承
  2. String實作了Serializable介面:表示字串是支持序列化的,實作了Comparable介面:表示String可以比較大小
  3. string在jdk8及以前內部定義了final char[] value用于存盤字串資料,JDK9時改為byte[]

為什么 JDK9 改變了 String 的結構

官方檔案

http://openjdk.java.net/jeps/254

為什么改為 byte[] 存盤?

  1. String類的當前實作將字符存盤在char陣列中,每個字符使用兩個位元組(16位),
  2. 從許多不同的應用程式收集的資料表明,字串是堆使用的主要組成部分,而且大多數字串物件只包含拉丁字符,這些字符只需要一個位元組的存盤空間,因此這些字串物件的內部char陣列中有一半的空間將不會使用,
  3. 之前 String 類使用 UTF-16 的 char[] 陣列存盤,現在改為 byte[] 陣列 外加一個編碼標志位存盤,該編碼標志將指定 String 類中 byte[] 陣列的編碼方式
  4. 結論:String再也不用char[] 來存盤了,改成了byte [] 加上編碼標記,節約了一些空間
  5. 同時基于String的資料結構,例如StringBuffer和StringBuilder也同樣做了修改

private final char value[];

private final byte[] value

1.2、String 的基本特征

String 的基本特征

String:代表不可變的字符序列,簡稱:不可變性,

  1. 當對字串重新賦值時,需要重寫指定記憶體區域賦值,不能使用原有的value進行賦值,
  2. 當對現有的字串進行連接操作時,也需要重新指定記憶體區域賦值,不能使用原有的value進行賦值,
  3. 當呼叫String的replace()方法修改指定字符或字串時,也需要重新指定記憶體區域賦值,不能使用原有的value進行賦值,

通過字面量的方式(區別于new)給一個字串賦值,此時的字串值宣告在字串常量池中,

當對字串重新賦值時,需要重寫指定記憶體區域賦值,不能使用原有的value進行賦值

  • 代碼
@Test
public void test1() {
    String s1 = "abc";
    String s2 = "abc";
    s1 = "hello";

    System.out.println(s1 == s2);

    System.out.println(s1);
    System.out.println(s2);
}
  • 位元組碼指令
    • 取字串 "abc" 時,使用的是同一個符號參考:#2
    • 取字串 "hello" 時,使用的是另一個符號參考:#3
 0 ldc #2 <abc>
 2 astore_1
 3 ldc #2 <abc>
 5 astore_2
 6 ldc #3 <hello>
 8 astore_1
 9 getstatic #4 <java lang system.out>
12 aload_1
13 aload_2
14 if_acmpne 21 (+7)
17 iconst_1
18 goto 22 (+4)
21 iconst_0
22 invokevirtual #5 <java io printstream.println>
25 getstatic #4 <java lang system.out>
28 aload_1
29 invokevirtual #6 <java io printstream.println>
32 getstatic #4 <java lang system.out>
35 aload_2
36 invokevirtual #6 <java io printstream.println>
39 return
</java></java></java></java></java></java></hello></abc></abc>

當對現有的字串進行連接操作時,也需要重新指定記憶體區域賦值,不能使用原有的value進行賦值

  • 代碼
@Test
public void test2() {
    String s1 = "abc";
    String s2 = "abc";
    s2 += "def";
    System.out.println(s2);
    System.out.println(s1);
}
  • 位元組碼指令:拼接操作通過 StringBuilder 的 append() 方法完成
 0 ldc #2 <abc>
 2 astore_1
 3 ldc #2 <abc>
 5 astore_2
 6 new #7 <java lang stringbuilder>
 9 dup
10 invokespecial #8 <java lang stringbuilder.<init>>
13 aload_2
14 invokevirtual #9 <java lang stringbuilder.append>
17 ldc #10 <def>
19 invokevirtual #9 <java lang stringbuilder.append>
22 invokevirtual #11 <java lang stringbuilder.tostring>
25 astore_2
26 getstatic #4 <java lang system.out>
29 aload_2
30 invokevirtual #6 <java io printstream.println>
33 getstatic #4 <java lang system.out>
36 aload_1
37 invokevirtual #6 <java io printstream.println>
40 return
</java></java></java></java></java></java></def></java></java></java></abc></abc>

當呼叫string的replace()方法修改指定字符或字串時,也需要重新指定記憶體區域賦值,不能使用原有的value進行賦值

@Test
public void test3() {
    String s1 = "abc";
    String s2 = s1.replace('a', 'm');
    System.out.println(s1);
    System.out.println(s2);
}

來看看 replace() 方法的原始碼

  • new String(buf, true); 后,回傳新的 String 物件
public String replace(char oldChar, char newChar) {
    if (oldChar != newChar) {
        int len = value.length;
        int i = -1;
        char[] val = value;

        while (++i < len) {
            if (val[i] == oldChar) {
                break;
            }
        }
        if (i < len) {
            char buf[] = new char[len];
            for (int j = 0; j < i; j++) {
                buf[j] = val[j];
            }
            while (i < len) {
                char c = val[i];
                buf[i] = (c == oldChar) ? newChar : c;
                i++;
            }
            return new String(buf, true);
        }
    }
    return this;
}

課后練習:String 的不可變性

  • 代碼

public class StringExer {
    String str = new String("good");
    char[] ch = {'t', 'e', 's', 't'};

    public void change(String str, char ch[]) {
        str = "test ok";
        ch[0] = 'b';
    }

    public static void main(String[] args) {
        StringExer ex = new StringExer();
        ex.change(ex.str, ex.ch);
        System.out.println(ex.str);
        System.out.println(ex.ch);
    }
}
  • str 的內容并沒有變:"test ok" 位于字串常量池中的另一個區域(地址),進行賦值操作并沒有修改原來 str 指向的參考的內容
good
best

1.3、String 的底層結構

String 底層 Hashtable 結構的說明

字串常量池是不會存盤相同內容的字串的

  1. String的String Pool是一個固定大小的Hashtable,默認值大小長度是1009,如果放進String Pool的String非常多,就會造成Hash沖突嚴重,從而導致鏈表會很長,而鏈表長了后直接會造成的影響就是當呼叫String.intern()方法時性能會大幅下降,
  2. 使用-XX:StringTablesize可設定StringTable的長度
  3. 在JDK6中StringTable是固定的,就是1009的長度,所以如果常量池中的字串過多就會導致效率下降很快,StringTablesize設定沒有要求
  4. 在JDK7中,StringTable的長度默認值是60013,StringTablesize設定沒有要求
  5. 在JDK8中,StringTable的長度默認值是60013,StringTable可以設定的最小值為1009

代碼示例:設定 StringTable 的長度

  • 代碼

public class StringTest2 {
    public static void main(String[] args) {

        System.out.println("我來打個醬油");
        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

通過 -XX:StringTableSize 設定 StringTable 長度

  • JVM 引數
-XX:StringTableSize=6666
  • jinfo 查看變數值
jps
jinfo -flag StringTableSize 行程id

測驗不同 StringTable 長度下,程式的性能

  • 代碼

public class StringTest2 {
    public static void main(String[] args) {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader("words.txt"));
            long start = System.currentTimeMillis();
            String data;
            while ((data = https://www.cnblogs.com/spiritmark/p/br.readLine()) != null) {

                data.intern();
            }

            long end = System.currentTimeMillis();

            System.out.println("花費的時間為:" + (end - start));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }
    }
}
  • -XX:StringTableSize=1009 :程式耗時 143ms
  • -XX:StringTableSize=100009 :程式耗時 47ms

2、String 的記憶體分配

2.1、String 記憶體分配演程序序

String 型別

  1. 在Java語言中有8種基本資料型別和一種比較特殊的型別String,這些型別為了使它們在運行程序中速度更快、更節省記憶體,都提供了一種常量池的概念,
  2. 常量池就類似一個Java系統級別提供的快取,8種基本資料型別的常量池都是系統協調的,String型別的常量池比較特殊,它的主要使用方法有兩種,
  • 直接使用雙引號宣告出來的String物件會直接存盤在常量池中,比如: String info="atguigu.com";
  • 如果不是用雙引號宣告的String物件,可以使用String提供的intern()方法,

String 記憶體分配的演程序序

  1. Java 6及以前,字串常量池存放在永久代
  2. Java 7中 Oracle的工程師對字串池的邏輯做了很大的改變,即將字串常量池的位置調整到Java堆內
  • 所有的字串都保存在堆(Heap)中,和其他普通物件一樣,這樣可以讓你在進行調優應用時僅需要調整堆大小就可以了,
  • 字串常量池概念原本使用得比較多,但是這個改動使得我們有足夠的理由讓我們重新考慮在Java 7中使用String.intern(),
  1. Java8元空間,字串常量在堆

2.2、為什么要調整 String 位置

StringTable 為什么要調整?

官方檔案

https://www.oracle.com/java/technologies/javase/jdk7-relnotes.html#jdk7changes

  1. 為什么要調整位置?
  • 永久代的默認比較小
  • 永久代垃圾回收頻率低
  • 堆中空間足夠大,字串可被及時回收
  1. 在JDK 7中,interned字串不再在Java堆的永久代中分配,而是在Java堆的主要部分(稱為年輕代和年老代)中分配,與應用程式創建的其他物件一起分配,
  2. 此更改將導致駐留在主Java堆中的資料更多,駐留在永久生成中的資料更少,因此可能需要調整堆大小,

代碼示例

  • 代碼

public class StringTest3 {
    public static void main(String[] args) {

        Set<String> set = new HashSet<String>();

        short i = 0;
        while(true){
            set.add(String.valueOf(i++).intern());
        }
    }
}
  • 例外日志說:我真沒騙你,字串真的在堆中(JDK8)
"C:\Program Files\Java\jdk1.8.0_144\bin\java" -XX:MetaspaceSize=6m -XX:MaxMetaspaceSize=6m -Xms6m -Xmx6m "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2017.3.1\lib\idea_rt.jar=1799:C:\Program Files\JetBrains\IntelliJ IDEA 2017.3.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;C:\Users\Heygo\Desktop\JVMDemo\out\production\chapter13;D:\JavaTools\apache-maven-3.3.9\repository\junit\junit\4.12\junit-4.12.jar;D:\JavaTools\apache-maven-3.3.9\repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar" com.atguigu.java.StringTest3
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.HashMap.resize(HashMap.java:703)
	at java.util.HashMap.putVal(HashMap.java:662)
	at java.util.HashMap.put(HashMap.java:611)
	at java.util.HashSet.add(HashSet.java:219)
	at com.atguigu.java.StringTest3.main(StringTest3.java:22)

Process finished with exit code 1

3、String 的基本操作

核心思想

Java語言規范里要求完全相同的字串字面量,應該包含同樣的Unicode字符序列(包含同一份碼點序列的常量),并且必須是指向同一個String類實體,

題目一

  • 代碼

public class StringTest4 {
    public static void main(String[] args) {
        System.out.println();
        System.out.println("1");
        System.out.println("2");
        System.out.println("3");
        System.out.println("4");
        System.out.println("5");
        System.out.println("6");
        System.out.println("7");
        System.out.println("8");
        System.out.println("9");
        System.out.println("10");

        System.out.println("1");
        System.out.println("2");
        System.out.println("3");
        System.out.println("4");
        System.out.println("5");
        System.out.println("6");
        System.out.println("7");
        System.out.println("8");
        System.out.println("9");
        System.out.println("10");
    }
}
  • 分析字串常量池的變化
    • 程式啟動時已經加載了 2330 個字串常量
    • 加載 換行符
    • 加載了字串常量 "1"~"9"
    • 加載字串常量 "10"
    • 之后的字串"1" 到 "10"不會再次加載

題目二

  • 代碼

class Memory {
    public static void main(String[] args) {
        int i = 1;
        Object obj = new Object();
        Memory mem = new Memory();
        mem.foo(obj);
    }

    private void foo(Object param) {
        String str = param.toString();
        System.out.println(str);
    }
}
  • 分析運行時記憶體(foo() 方法是實體方法,其實圖中少了一個 this 區域變數)

4、字串拼接操作

4.1、符串拼接操作的結論

字串拼接操作的結論

  1. 常量與常量的拼接結果在常量池,原理是編譯期優化
  2. 常量池中不會存在相同內容的變數
  3. 拼接前后,只要其中有一個是變數,結果就在堆中,變數拼接的原理是StringBuilder
  4. 如果拼接的結果呼叫intern()方法,則主動將常量池中還沒有的字串物件放入池中,并回傳此物件地址
  • 如果存在,則回傳字串在常量池中的地址
  • 如果字串常量池中不存在該字串,則在常量池中創建一份,并回傳此物件的地址

常量與常量的拼接結果在常量池,原理是編譯期優化

  • 代碼
@Test
public void test1() {
    String s1 = "a" + "b" + "c";
    String s2 = "abc";

    System.out.println(s1 == s2);
    System.out.println(s1.equals(s2));
}
  • 從位元組碼指令看出:編譯器做了優化,將 "a" + "b" + "c" 優化成了 "abc"
 0 ldc #2 <abc>
 2 astore_1
 3 ldc #2 <abc>
 5 astore_2
 6 getstatic #3 <java lang system.out>
 9 aload_1
10 aload_2
11 if_acmpne 18 (+7)
14 iconst_1
15 goto 19 (+4)
18 iconst_0
19 invokevirtual #4 <java io printstream.println>
22 getstatic #3 <java lang system.out>
25 aload_1
26 aload_2
27 invokevirtual #5 <java lang string.equals>
30 invokevirtual #4 <java io printstream.println>
33 return
</java></java></java></java></java></abc></abc>
  • IDEA 反編譯 class 檔案后,來看這個問題

拼接前后,只要其中有一個是變數,結果就在堆中

呼叫 intern() 方法,則主動將字串物件存入字串常量池中,并將其地址回傳

  • 代碼
@Test
public void test2(){
    String s1 = "javaEE";
    String s2 = "hadoop";

    String s3 = "javaEEhadoop";
    String s4 = "javaEE" + "hadoop";

    String s5 = s1 + "hadoop";
    String s6 = "javaEE" + s2;
    String s7 = s1 + s2;

    System.out.println(s3 == s4);
    System.out.println(s3 == s5);
    System.out.println(s3 == s6);
    System.out.println(s3 == s7);
    System.out.println(s5 == s6);
    System.out.println(s5 == s7);
    System.out.println(s6 == s7);

    String s8 = s6.intern();
    System.out.println(s3 == s8);
}
  • 從位元組碼角度來看:拼接前后有變數,都會使用到 StringBuilder 類
  0 ldc #6 <javaee>
  2 astore_1
  3 ldc #7 <hadoop>
  5 astore_2
  6 ldc #8 <javaeehadoop>
  8 astore_3
  9 ldc #8 <javaeehadoop>
 11 astore 4
 13 new #9 <java lang stringbuilder>
 16 dup
 17 invokespecial #10 <java lang stringbuilder.<init>>
 20 aload_1
 21 invokevirtual #11 <java lang stringbuilder.append>
 24 ldc #7 <hadoop>
 26 invokevirtual #11 <java lang stringbuilder.append>
 29 invokevirtual #12 <java lang stringbuilder.tostring>
 32 astore 5
 34 new #9 <java lang stringbuilder>
 37 dup
 38 invokespecial #10 <java lang stringbuilder.<init>>
 41 ldc #6 <javaee>
 43 invokevirtual #11 <java lang stringbuilder.append>
 46 aload_2
 47 invokevirtual #11 <java lang stringbuilder.append>
 50 invokevirtual #12 <java lang stringbuilder.tostring>
 53 astore 6
 55 new #9 <java lang stringbuilder>
 58 dup
 59 invokespecial #10 <java lang stringbuilder.<init>>
 62 aload_1
 63 invokevirtual #11 <java lang stringbuilder.append>
 66 aload_2
 67 invokevirtual #11 <java lang stringbuilder.append>
 70 invokevirtual #12 <java lang stringbuilder.tostring>
 73 astore 7
 75 getstatic #3 <java lang system.out>
 78 aload_3
 79 aload 4
 81 if_acmpne 88 (+7)
 84 iconst_1
 85 goto 89 (+4)
 88 iconst_0
 89 invokevirtual #4 <java io printstream.println>
 92 getstatic #3 <java lang system.out>
 95 aload_3
 96 aload 5
 98 if_acmpne 105 (+7)
101 iconst_1
102 goto 106 (+4)
105 iconst_0
106 invokevirtual #4 <java io printstream.println>
109 getstatic #3 <java lang system.out>
112 aload_3
113 aload 6
115 if_acmpne 122 (+7)
118 iconst_1
119 goto 123 (+4)
122 iconst_0
123 invokevirtual #4 <java io printstream.println>
126 getstatic #3 <java lang system.out>
129 aload_3
130 aload 7
132 if_acmpne 139 (+7)
135 iconst_1
136 goto 140 (+4)
139 iconst_0
140 invokevirtual #4 <java io printstream.println>
143 getstatic #3 <java lang system.out>
146 aload 5
148 aload 6
150 if_acmpne 157 (+7)
153 iconst_1
154 goto 158 (+4)
157 iconst_0
158 invokevirtual #4 <java io printstream.println>
161 getstatic #3 <java lang system.out>
164 aload 5
166 aload 7
168 if_acmpne 175 (+7)
171 iconst_1
172 goto 176 (+4)
175 iconst_0
176 invokevirtual #4 <java io printstream.println>
179 getstatic #3 <java lang system.out>
182 aload 6
184 aload 7
186 if_acmpne 193 (+7)
189 iconst_1
190 goto 194 (+4)
193 iconst_0
194 invokevirtual #4 <java io printstream.println>
197 aload 6
199 invokevirtual #13 <java lang string.intern>
202 astore 8
204 getstatic #3 <java lang system.out>
207 aload_3
208 aload 8
210 if_acmpne 217 (+7)
213 iconst_1
214 goto 218 (+4)
217 iconst_0
218 invokevirtual #4 <java io printstream.println>
221 return
</java></java></java></java></java></java></java></java></java></java></java></java></java></java></java></java></java></java></java></java></java></java></java></java></java></javaee></java></java></java></java></hadoop></java></java></java></javaeehadoop></javaeehadoop></hadoop></javaee>

4.2、字串拼接的底層細節

字串拼接的底層細節

代碼示例 1

  • 代碼
@Test
public void test3(){
    String s1 = "a";
    String s2 = "b";
    String s3 = "ab";

    String s4 = s1 + s2;
    System.out.println(s3 == s4);
}
  • 位元組碼指令
 0 ldc #14 <a>
 2 astore_1
 3 ldc #15 <b>
 5 astore_2
 6 ldc #16 <ab>
 8 astore_3
 9 new #9 <java lang stringbuilder>
12 dup
13 invokespecial #10 <java lang stringbuilder.<init>>
16 aload_1
17 invokevirtual #11 <java lang stringbuilder.append>
20 aload_2
21 invokevirtual #11 <java lang stringbuilder.append>
24 invokevirtual #12 <java lang stringbuilder.tostring>
27 astore 4
29 getstatic #3 <java lang system.out>
32 aload_3
33 aload 4
35 if_acmpne 42 (+7)
38 iconst_1
39 goto 43 (+4)
42 iconst_0
43 invokevirtual #4 <java io printstream.println>
46 return

</java></java></java></java></java></java></java></ab></b></a>
  • 分析拼接的步驟
    • new StringBuilder()
 9 new #9 <java lang stringbuilder>
12 dup
13 invokespecial #10 <java lang stringbuilder.<init>>
</java></java>
  • 加載字串變數,進行 append 操作
16 aload_1
17 invokevirtual #11 <java lang stringbuilder.append>
20 aload_2
21 invokevirtual #11 <java lang stringbuilder.append>
24 invokevirtual #12 <java lang stringbuilder.tostring>
</java></java></java>
  • 呼叫 StringBuilder 類的 toString() 方法,轉換為字串,并存盤在區域變數中
24 invokevirtual #12 <java lang stringbuilder.tostring>
27 astore 4
</java>

代碼示例 2

  • 代碼

@Test
public void test4(){
    final String s1 = "a";
    final String s2 = "b";
    String s3 = "ab";
    String s4 = s1 + s2;
    System.out.println(s3 == s4);
}
  • 從位元組碼角度來看:為變數 s3 賦值時,直接使用 #16 符號參考,即字串常量 "ab"
 0 ldc #14 <a>
 2 astore_1
 3 ldc #15 <b>
 5 astore_2
 6 ldc #16 <ab>
 8 astore_3
 9 ldc #16 <ab>
11 astore 4
13 getstatic #3 <java lang system.out>
16 aload_3
17 aload 4
19 if_acmpne 26 (+7)
22 iconst_1
23 goto 27 (+4)
26 iconst_0
27 invokevirtual #4 <java io printstream.println>
30 return
</java></java></ab></ab></b></a>
  • IDEA 反編譯結果

課后練習

  • 代碼

@Test
public void test5(){
    String s1 = "javaEEhadoop";
    String s2 = "javaEE";
    String s3 = s2 + "hadoop";
    System.out.println(s1 == s3);

    final String s4 = "javaEE";
    String s5 = s4 + "hadoop";
    System.out.println(s1 == s5);

}
  • 位元組碼指令: ldc #8 <javaeehadoop></javaeehadoop>(帶 final 的變數在編譯時就已經確定了該變數的值,當做常量來處理)
 0 ldc #8 <javaeehadoop>
 2 astore_1
 3 ldc #6 <javaee>
 5 astore_2
 6 new #9 <java lang stringbuilder>
 9 dup
10 invokespecial #10 <java lang stringbuilder.<init>>
13 aload_2
14 invokevirtual #11 <java lang stringbuilder.append>
17 ldc #7 <hadoop>
19 invokevirtual #11 <java lang stringbuilder.append>
22 invokevirtual #12 <java lang stringbuilder.tostring>
25 astore_3
26 getstatic #3 <java lang system.out>
29 aload_1
30 aload_3
31 if_acmpne 38 (+7)
34 iconst_1
35 goto 39 (+4)
38 iconst_0
39 invokevirtual #4 <java io printstream.println>
42 ldc #6 <javaee>
44 astore 4
46 ldc #8 <javaeehadoop>
48 astore 5
50 getstatic #3 <java lang system.out>
53 aload_1
54 aload 5
56 if_acmpne 63 (+7)
59 iconst_1
60 goto 64 (+4)
63 iconst_0
64 invokevirtual #4 <java io printstream.println>
67 return
</java></java></javaeehadoop></javaee></java></java></java></java></hadoop></java></java></java></javaee></javaeehadoop>

拼接操作與 append 操作的效率對比

  • 代碼

@Test
public void test6(){

    long start = System.currentTimeMillis();

    method2(100000);

    long end = System.currentTimeMillis();

    System.out.println("花費的時間為:" + (end - start));
}

public void method1(int highLevel){
    String srchttps://www.cnblogs.com/spiritmark/p/= "";
    for(int i = 0;i < highLevel;i++){
        src = https://www.cnblogs.com/spiritmark/p/src +"a";
    }
}

public void method2(int highLevel){

    StringBuilder src = https://www.cnblogs.com/spiritmark/p/new StringBuilder();
    for (int i = 0; i < highLevel; i++) {
        src.append("a");
    }
}
  1. 體會執行效率:通過StringBuilder的append()的方式添加字串的效率要遠高于使用String的字串拼接方式!
  2. 分析原因:
  3. StringBuilder的append()的方式:
+ 自始至終中只創建過一個StringBuilder的物件
+ 使用String的字串拼接方式:創建過多個StringBuilder和String的物件
  1. 使用String的字串拼接方式:
+ 記憶體中由于創建了較多的StringBuilder和String的物件,記憶體占用更大;
+ 如果進行GC,需要花費額外的時間,
  1. 改進的空間:
  • 在實際開發中,如果基本確定要前前后后添加的字串長度不高于某個限定值highLevel的情況下,建議使用構造器實體化:
  • StringBuilder s = new StringBuilder(highLevel); //new char[highLevel]

通過位元組碼分析

  • method1() 方法的位元組碼指令:
    • 每次 for 回圈都會創建一個 StringBuilder 物件
    • 呼叫 StringBuilder 的 toString() 方法又會創建新的 String 物件
 0 ldc #23
 2 astore_2
 3 iconst_0
 4 istore_3
 5 iload_3
 6 iload_1
 7 if_icmpge 36 (+29)
10 new #9 <java lang stringbuilder>
13 dup
14 invokespecial #10 <java lang stringbuilder.<init>>
17 aload_2
18 invokevirtual #11 <java lang stringbuilder.append>
21 ldc #14 <a>
23 invokevirtual #11 <java lang stringbuilder.append>
26 invokevirtual #12 <java lang stringbuilder.tostring>
29 astore_2
30 iinc 3 by 1
33 goto 5 (-28)
36 return
</java></java></a></java></java></java>
  • method2() 方法的位元組碼指令:
 0 new #9 <java lang stringbuilder>
 3 dup
 4 invokespecial #10 <java lang stringbuilder.<init>>
 7 astore_2
 8 iconst_0
 9 istore_3
10 iload_3
11 iload_1
12 if_icmpge 28 (+16)
15 aload_2
16 ldc #14 <a>
18 invokevirtual #11 <java lang stringbuilder.append>
21 pop
22 iinc 3 by 1
25 goto 10 (-15)
28 return
</java></a></java></java>

關于 StringBuilder 構造器

  • StringBuilder 構造器:可傳入一個 int 型別的變數,用于初始化內部的 char[] 陣列
public StringBuilder(int capacity) {
	super(capacity);
}
  • AbstractStringBuilder(StringBuilder 的父類)的構造器
AbstractStringBuilder(int capacity) {
    value = https://www.cnblogs.com/spiritmark/p/new char[capacity];
}

5、intern() 的使用

5.1、intern() 方法的說明

intern() 方法的說明

先來點逼格,看看官方檔案

When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.

It follows that for any two strings s and t, s.intern() == t.intern() is true if and only if s.equals(t) is true.

public native String intern();

關于 intern() 方法的說明

  1. intern是一個native方法,呼叫的是底層C的方法
  2. 字串池最初是空的,由String類私有地維護,在呼叫intern方法時,如果池中已經包含了由equals(object)方法確定的與該字串物件相等的字串,則回傳池中的字串,否則,該字串物件將被添加到池中,并回傳對該字串物件的參考,
  3. 如果不是用雙引號宣告的String物件,可以使用String提供的intern方法:intern方法會從字串常量池中查詢當前字串是否存在,若不存在就會將當前字串放入常量池中,比如:
String myInfo = new string("I love atguigu").intern();
  1. 也就是說,如果在任意字串上呼叫String.intern方法,那么其回傳結果所指向的那個類實體,必須和直接以常量形式出現的字串實體完全相同,因此,下列運算式的值必定是true
("a"+"b"+"c").intern()=="abc"
  1. 通俗點講,Interned String就是確保字串在記憶體里只有一份拷貝,這樣可以節約記憶體空間,加快字串操作任務的執行速度,注意,這個值會被存放在字串內部池(String Intern Pool)

5.2、new String() 的說明

new String("ab")會創建幾個物件?

  • 代碼

public class StringNewTest {
    public static void main(String[] args) {
        String str = new String("ab");
    }
}
  • 位元組碼指令
 0 new #2 <java lang string>
 3 dup
 4 ldc #3 <ab>
 6 invokespecial #4 <java lang string.<init>>
 9 astore_1
10 return
</java></ab></java>
  • 0 new #2 <java lang string></java>:在堆中創建了一個 String 物件
  • 4 ldc #3 <ab></ab> :在字串常量池中放入 "ab"(如果之前字串常量池中沒有 "ab" 的話)

new String("a") + new String("b") 會創建幾個物件?

  • 代碼

public class StringNewTest {
    public static void main(String[] args) {
        String str = new String("a") + new String("b");
    }
}
  • 位元組碼指令
 0 new #2 <java lang stringbuilder>
 3 dup
 4 invokespecial #3 <java lang stringbuilder.<init>>
 7 new #4 <java lang string>
10 dup
11 ldc #5 <a>
13 invokespecial #6 <java lang string.<init>>
16 invokevirtual #7 <java lang stringbuilder.append>
19 new #4 <java lang string>
22 dup
23 ldc #8 <b>
25 invokespecial #6 <java lang string.<init>>
28 invokevirtual #7 <java lang stringbuilder.append>
31 invokevirtual #9 <java lang stringbuilder.tostring>
34 astore_1
35 return
</java></java></java></b></java></java></java></a></java></java></java>
  • 位元組碼指令分析:
    1. 0 new #2 <java lang stringbuilder></java> :拼接字串會創建一個 StringBuilder 物件
    2. 7 new #4 <java lang string></java> :創建 String 物件,對應于 new String("a")
    3. 11 ldc #5 :在字串常量池中放入 "a"(如果之前字串常量池中沒有 "a" 的話)
    4. 19 new #4 <java lang string></java> :創建 String 物件,對應于 new String("b")
    5. 23 ldc #8 :在字串常量池中放入 "b"(如果之前字串常量池中沒有 "b" 的話)
    6. 31 invokevirtual #9 <java lang stringbuilder.tostring></java> :呼叫 StringBuilder 的 toString() 方法,會生成一個 String 物件

深入剖析 StringBuilder 的toString() 方法

  • toString() 方法
@Override
public String toString() {

    return new String(value, 0, count);
}
  • value 是個 char[] 陣列
char[] value;

5.3、有點難的面試題

有點難的面試題

  • 代碼

public class StringIntern {
    public static void main(String[] args) {
        String s = new String("1");
        s.intern();
        String s2 = "1";

        System.out.println(s == s2);

        String s3 = new String("1") + new String("1");

        s3.intern();
        String s4 = "11";

        System.out.println(s3 == s4);
    }
}

記憶體分析

  • JDK6 :正常眼光判斷即可
    • new String() 即在堆中
    • str.intern() 則把字串放入常量池中

  • JDK7/8 :這就有點不一樣了
    • new String() 即在堆中
    • str.intern() 則把字串放入常量池中,出于節省空間的目的,如果 str 不存在于字串常量池中,則將 str 在堆中的參考存盤在字串常量池中,沒錯,字串常量池中存的是 str 在堆中的參考,所以 s3 == s4 為 true

面試題的拓展


public class StringIntern1 {
    public static void main(String[] args) {

        String s3 = new String("1") + new String("1");

        String s4 = "11";
        String s5 = s3.intern();

        System.out.println(s3 == s4);

        System.out.println(s5 == s4);
    }
}

5.4、intern() 方法的總結

關于 intern() 的總結

  1. JDK1.6中,將這個字串物件嘗試放入串池,
  2. 如果串池中有,則并不會放入,回傳已有的串池中的物件的地址
  3. 如果沒有,會 把此物件復制一份,放入串池,并回傳串池中的物件地址
  4. JDK1.7起,將這個字串物件嘗試放入串池,
  • 如果串池中有,則并不會放入,回傳已有的串池中的物件的地址
  • 如果沒有,則會 把物件的參考地址復制一份,放入串池,并回傳串池中的參考地址

5.5、intern() 方法的練習

intern() 方法的課后練習

練習 1

  • 代碼

public class StringExer1 {
    public static void main(String[] args) {

        String s = new String("a") + new String("b");

        String s2 = s.intern();
        System.out.println(s2 == "ab");
        System.out.println(s == "ab");
    }
}
  • JDK 6 中:在串池中創建一個字串"ab"

  • JDK 7/8 中:串池中沒有創建字串"ab",而是創建一個參考,指向new String("ab"),將此參考回傳

練習 2

  • 代碼

public class StringExer1 {
    public static void main(String[] args) {

        String x = "ab";

        String s = new String("a") + new String("b");

        String s2 = s.intern();
        System.out.println(s2 == "ab");
        System.out.println(s == "ab");
    }
}
  • 記憶體分析

練習 3

  • 代碼 1

public class StringExer2 {
    public static void main(String[] args) {
        String s1 = new String("ab");
        s1.intern();
        String s2 = "ab";
        System.out.println(s1 == s2);
    }
}

  • 代碼 2

public class StringExer2 {

    public static void main(String[] args) {
        String s1 = new String("a") + new String("b");
        System.out.println(System.identityHashCode(s1));
        s1.intern();
        System.out.println(System.identityHashCode(s1));
        String s2 = "ab";
        System.out.println(System.identityHashCode(s2));
        System.out.println(s1 == s2);
    }
}

5.6、intern() 方法效率測驗

intern() 的效率測驗

  • 代碼

public class StringIntern2 {
    static final int MAX_COUNT = 1000 * 10000;
    static final String[] arr = new String[MAX_COUNT];

    public static void main(String[] args) {
        Integer[] data = https://www.cnblogs.com/spiritmark/p/new Integer[]{1,2,3,4,5,6,7,8,9,10};

        long start = System.currentTimeMillis();
        for (int i = 0; i < MAX_COUNT; i++) {

            arr[i] = new String(String.valueOf(data[i % data.length])).intern();

        }
        long end = System.currentTimeMillis();
        System.out.println("花費的時間為:" + (end - start));

        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.gc();
    }
}
  • 直接 new String :由于每個 String 物件都是 new 出來的,所以程式需要維護大量存放在堆空間中的 String 實體,程式記憶體占用也會變高

  • 使用 intern() 方法:由于陣列中字串的參考都指向字串常量池中的字串,所以程式需要維護的 String 物件更少,記憶體占用也更低

結論

  1. 對于程式中大量使用存在的字串時,尤其存在很多已經重復的字串時,使用intern()方法能夠節省記憶體空間,
  2. 大的網站平臺,需要記憶體中存盤大量的字串,比如社交網站,很多人都存盤:北京市、海淀區等資訊,這時候如果字串都呼叫intern() 方法,就會很明顯降低記憶體的大小,

6、StringTable 的垃圾回收

  • 代碼

public class StringGCTest {
    public static void main(String[] args) {
        for (int j = 0; j < 100000; j++) {
            String.valueOf(j).intern();
        }
    }
}
  • JVM 引數
-Xms15m -Xmx15m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails
  • 程式日志:
    • 在 PSYoungGen 區發生了垃圾回收
    • Number of entries 和 Number of literals 明顯沒有 100000
    • 以上兩點均說明 StringTable 區發生了垃圾回收
"C:\Program Files\Java\jdk1.8.0_144\bin\java" -Xms15m -Xmx15m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2017.3.1\lib\idea_rt.jar=11487:C:\Program Files\JetBrains\IntelliJ IDEA 2017.3.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;C:\Users\Heygo\Desktop\JVMDemo\out\production\chapter13;D:\JavaTools\apache-maven-3.3.9\repository\junit\junit\4.12\junit-4.12.jar;D:\JavaTools\apache-maven-3.3.9\repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar" com.atguigu.java3.StringGCTest
[GC (Allocation Failure) [PSYoungGen: 4096K->488K(4608K)] 4096K->716K(15872K), 0.0024275 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
 PSYoungGen      total 4608K, used 3883K [0x00000000ffb00000, 0x0000000100000000, 0x0000000100000000)
  eden space 4096K, 82% used [0x00000000ffb00000,0x00000000ffe50fb0,0x00000000fff00000)
  from space 512K, 95% used [0x00000000fff00000,0x00000000fff7a020,0x00000000fff80000)
  to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
 ParOldGen       total 11264K, used 228K [0x00000000ff000000, 0x00000000ffb00000, 0x00000000ffb00000)
  object space 11264K, 2% used [0x00000000ff000000,0x00000000ff039010,0x00000000ffb00000)
 Metaspace       used 3472K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 381K, capacity 388K, committed 512K, reserved 1048576K
SymbolTable statistics:
Number of buckets       :     20011 =    160088 bytes, avg   8.000
Number of entries       :     14158 =    339792 bytes, avg  24.000
Number of literals      :     14158 =    603200 bytes, avg  42.605
Total footprint         :           =   1103080 bytes
Average bucket size     :     0.708
Variance of bucket size :     0.711
Std. dev. of bucket size:     0.843
Maximum bucket size     :         6
StringTable statistics:
Number of buckets       :     60013 =    480104 bytes, avg   8.000
Number of entries       :     62943 =   1510632 bytes, avg  24.000
Number of literals      :     62943 =   3584040 bytes, avg  56.941
Total footprint         :           =   5574776 bytes
Average bucket size     :     1.049
Variance of bucket size :     0.824
Std. dev. of bucket size:     0.908
Maximum bucket size     :         5

Process finished with exit code 0

String.valueOf() 方法原始碼

  • String 類的 valueOf() 方法
public static String valueOf(int i) {
    return Integer.toString(i);
}
  • Integer.toString() 方法中執行了 new String() ,即在堆中創建了一個 String 物件
public static String toString(int i) {
    if (i == Integer.MIN_VALUE)
        return "-2147483648";
    int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
    char[] buf = new char[size];
    getChars(i, size, buf);
    return new String(buf, true);
}

7、G1 中的 String 去重操作

官方檔案

http://openjdk.java.net/jeps/192

String 去重操作的背景

  1. 背景:對許多Java應用(有大的也有小的)做的測驗得出以下結果:
  • 堆存活資料集合里面String物件占了25%
  • 堆存活資料集合里面重復的String物件有13.5%
  • String物件的平均長度是45
  1. 許多大規模的Java應用的瓶頸在于記憶體,測驗表明,在這些型別的應用里面,Java堆中存活的資料集合差不多25%是String物件,更進一步,這里面差不多一半String物件是重復的,重復的意思是說:
  2. str1.equals(str2)= true,堆上存在重復的String物件必然是一種記憶體的浪費,這個專案將在G1垃圾收集器中實作自動持續對重復的String物件進行去重,這樣就能避免浪費記憶體,

String 去重的的具體實作

  1. 當垃圾收集器作業的時候,會訪問堆上存活的物件,對每一個訪問的物件都會檢查是否是候選的要去重的String物件,
  2. 如果是,把這個物件的一個參考插入到佇列中等待后續的處理,一個去重的執行緒在后臺運行,處理這個佇列,處理佇列的一個元素意味著從佇列洗掉這個元素,然后嘗試去重它參考的String物件,
  3. 使用一個Hashtable來記錄所有的被String物件使用的不重復的char陣列,當去重的時候,會查這個Hashtable,來看堆上是否已經存在一個一模一樣的char陣列,
  4. 如果存在,String物件會被調整參考那個陣列,釋放對原來的陣列的參考,最侄訓被垃圾收集器回收掉,
  5. 如果查找失敗,char陣列會被插入到Hashtable,這樣以后的時候就可以共享這個陣列了,

命令列選項

  1. UseStringDeduplication(bool) :開啟String去重,默認是不開啟的,需要手動開啟,
  2. PrintStringDeduplicationStatistics(bool) :列印詳細的去重統計資訊
  3. stringDeduplicationAgeThreshold(uintx) :達到這個年齡的String物件被認為是去重的候選物件

你只管學習,我來負責記筆記?? 關注公眾號! ,更多筆記,等你來拿,謝謝



轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/170563.html

標籤:Java

上一篇:CDH5部署三部曲之一:準備作業

下一篇:laravel8更新之模型目錄及模型工廠類調整

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more