主頁 > 後端開發 > 學習一下 JVM (二) -- 學習一下 JVM 中物件、String 相關知識

學習一下 JVM (二) -- 學習一下 JVM 中物件、String 相關知識

2020-09-16 04:17:09 後端開發

一、JDK 8 版本下 JVM 物件的分配、布局、訪問(簡單了解下)

1、物件的創建程序

(1)前言
  Java 是一門面向物件的編程語言,程式運行程序中在任意時刻都可能有物件被創建,開發中常用 new 關鍵字、反射等方式創建物件, JVM 底層是如何處理的呢?

(2)物件的創建的幾種常見方式?
  Type1:使用 new 關鍵字創建(常見比如:單例模式、工廠模式等創建),
  Type2:反射機制創建(呼叫 class 的 newInstance() 方法),
  Type3:克隆創建(實作 Cloneable 介面,并重寫 clone() 方法),
  Type4:反序列化創建,

(3)物件創建步驟
Step1:判斷物件對應的類 是否已經被 加載、決議、初始化過,
  虛擬機執行 new 指令時,先去檢查該指令的引數 能否在 方法區(元空間)的運行時常量池中 定位到 某個類的符號參考,并檢查這個符號參考代表的 類是否 被加載、決議、初始化過,如果沒有,則在雙親委派模式下,查找相應的類 并加載,

Step2:為物件分配記憶體空間,
  類加載完成后,即可確定物件所需的記憶體大小,在堆中根據適當演算法劃分記憶體空間給物件,
劃分演算法:
  劃分演算法根據 Java 堆中記憶體是否 規整進行可劃分為:指標碰撞、空閑串列,
  堆記憶體規整時,采用指標碰撞方式分配記憶體空間,由于記憶體規整,即指標只需移動 所需物件記憶體 大小即可,
  堆記憶體不規整時,采用空閑串列方式分配記憶體空間,存在記憶體碎片,需要維護一個串列用于記錄哪些記憶體塊可用,在串列中找到足夠大的記憶體空間分配給物件,

堆記憶體是否規整:
  堆記憶體是否規整由 垃圾回收器演算法決定,
  使用 Serial、ParNew 等帶有 Compact(壓縮)程序的垃圾回收器時,堆記憶體規整,即指標碰撞,
  使用 CMS 等帶有 Mark-Sweep(標記清除)演算法的垃圾回收器時,堆記憶體不規整,即空閑串列,

Step3:處理并發安全問題,
  分配記憶體空間時,指標修改可能會碰到并發問題(比如 物件 A 分配記憶體后,但指標還沒修改,此時 物件 B 仍使用原來指標 進行記憶體分配,那么 A 與 B 就會出現沖突),
解決方式一:
  對分配記憶體空間的動作進行同步處理(CAS 加上失敗重試 保證更新操作的原子性),

解決方式二:
  將分配記憶體空間的動作按照執行緒劃分到不同空間中執行(Thread Local Allocation Buffer,TLAB,每個執行緒在堆中預先分配一小塊記憶體空間,哪個執行緒需要分配記憶體,就在哪個 TLAB 上進行分配),

Step4:初始化屬性值,
  將記憶體空間中的屬性 賦 零值(默認值),

Step5:設定物件的 物件頭,
  將物件所屬 類的元資料資訊、物件的哈希值、物件 GC 分代年齡 等資訊存盤在物件的物件頭,

Step6:執行 <init> 方法進行初始化,
  執行 <init> 方法,加載 非靜態代碼塊、非靜態變數、構造器,且執行順序為從上到下執行,但構造器最后執行,并將堆內物件的 首地址 賦值給 參考變數,

2、物件記憶體布局

(1)物件記憶體布局
  物件在記憶體中存盤布局可以分為:物件頭(Header)、實體資料(Instance Data)、對齊填充(Padding),

(2)物件頭(Header)
  物件頭用于存盤 運行時元資料 以及 型別指標,
運行時元資料:
  物件的哈希值、GC 分代年齡、鎖狀態標志、偏向時間戳等,

型別指標:
  即物件指向 類元資料的 指標(通過該指標確定該物件屬于哪個類),

(3)實體資料(Instance Data)
  其為物件 存盤的真實有效資訊,即程式中 各型別欄位的內容,

(4)對齊填充(Padding)
  不是必然存在的,起著占位符的作用,比如 HotSpot 中物件大小為 8 位元組的整數倍,當物件實體資料不是 8 位元組的整數倍時,通過對齊填充補全,

3、物件訪問定位(句柄訪問、直接指標)

(1)問題
  物件 存于堆中,而物件的參考 存放在堆疊幀中,如何根據 堆疊幀存放的參考 定位 堆中存盤的物件,即為物件訪問定位問題,取決于 JVM 的具體實作,常見方式:句柄訪問、直接指標,

(2)句柄訪問
  在堆中劃分出一塊記憶體作為 句柄池,用于保存物件的句柄地址(指標),而堆疊幀中存放的即為 句柄地址,
  當物件被移動(垃圾回收)時,只需要改變 句柄池中 指向物件實體資料的指標 即可,不需要修改堆疊幀中的資料,

 

 

(3)直接訪問(HotSpot 使用)
  堆疊幀中直接存放 物件實體資料的地址,物件移動時,需要修改堆疊幀中的資料,
  相較于 句柄訪問,減少了一次 指標定位的時間開銷(積少成多還是很可觀的),

 

 

 

二、JDK8 中的 String(可以深入研究一下,有不對的地方還望不吝賜教)

1、String 基本概念(JDK9 稍作改變)

(1)基本概念
  String 指的是字串,一般使用雙引號括起來 "" 表示(比如: "hello"),
  使用 final 型別修飾 String 類,表示不可被繼承,
  String 類實作了 Serializable 介面,表示字串支持序列化,
  String 類實作了 Comparable 介面,表示可以比較大小,
  String 類內部使用 final 修飾的陣列存盤字符,
注:
  JDK8 及以前 內部使用 final char[] value 用于存盤字串資料,
  JDK9 時改為 final byte[] 存盤資料(內部將 每個字符 與 0xFF 比較,當有一個比 0xFF 大時,使用 2 個位元組存盤,否則使用 1 個位元組存盤),

【JDK 8:】
public
final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; }

 

 

 

(2)賦值方式
  String 賦值一般分為:字面量直接賦值、使用 new 關鍵字通過構造器賦值,

【字面量直接賦值:(值會存放于 字串常量池 中)】
    String a = "hello";

【new + 構造器賦值:(值可能會存放于 字串常量池 中,且 new 關鍵字會在堆中創建一個物件)】
    String a = new String("hello");    
注:
    值不一定會存放于 字串常量池中,可以呼叫 String 的 intern() 方法將值放于字串常量池中,
    intern() 方法在不同 JDK 版本中實作不同,后面會舉例,此處大概有個印象即可,

 

2、字串常量池(String Pool)、String 不可變性

(1)字串常量池(String Pool)
  JVM 內部維護一個 字串常量池(String Pool),當 String 以字面量形式賦值時,此時字串會宣告在字串常量池中(比如:String a = "hello" 賦值時,會生成一個 "hello" 字串存于 常量池中),
  字串常量池中不會存盤相同內容的字串,其內部實作是一個固定大小的 Hashtable,如果常量池中存盤 String 過多,將會造成 hash 沖突,從而造成性能下降,可以通過 -XX:StringTableSize 設定 StringTable 大小(比如:-XX:StringTableSize=2000),

注:
  常量池類似于 快取,使程式運行更快、節省記憶體,
  JDK 6 及以前,字串常量池存放于 永久代中,StringTable 默認長度為 1009,
  JDK 7 及之后,字串常量池存放于 堆中,StringTable 默認長度為 60013,其最小值為 1009,

【常用 JVM 引數:】
-XX:StringTableSize    配置字串常量池中的 StringTable 大小,JDK 8 默認:60013-XX:+PrintStringTableStatistics  在JVM 行程退出時,列印出 StringTable 相關統計資訊,

 

(2)String 不可變性:
  String 一旦在記憶體中創建,其值將是不可變的(反射場景除外),當值改變時,改變的是指向記憶體的參考,而非直接修改記憶體中的值,

JDK 8 String 不可變:
  JDK8 采用 final 修飾 String 類,表示該類不可被繼承,
  String 類內部采用 private final char value[] 存盤字串,使用 private 修飾陣列且不對外提供 setter 方法,即 外部不可修改字串,使用 final 修飾陣列,表示 內部不可修改字串(參考地址不變,內容可變,使用反射可能會改變字串),且 String 提供的相關方法中,并沒有去修改原有字串中的值,而是回傳一個新的參考指向記憶體中新的 String 值(比如 replace() 方法回傳一個 new String() 物件),

 

 

 

(3)常見場景(修改參考地址)

  對現有字串重新賦值時,
  對現有字串進行連接操作時,
  使用字串的 replace() 方法修改指定字串時,

【舉例:(給現有字串重新賦值)】
package com.test;

public class JVMDemo {

    public static void main(String[] args) {
        String C1 = new String("abc");
        String C2 = C1;
        System.out.println(C1 == C2); // true
        System.out.println(System.identityHashCode(C1));
        System.out.println(System.identityHashCode(C2));
        C2 = "abc";
        System.out.println(C1 == C2); // false
        System.out.println(System.identityHashCode(C1));
        System.out.println(System.identityHashCode(C2));
    }
}

 

 

 

 

 

 

3、String 拼接操作 -- 筆試題

(1)拼接操作可能存在的情況:
  常量與常量(字面量或者 final 修飾的變數)的拼接結果會存放于常量池中,由編譯期優化導致,
  拼接資料中若有一個是變數,則拼接結果 會存放于 堆中,由 StringBuilder 拼接,
  如果拼接結果呼叫 intern() 方法,且常量池中不存在該字串物件,則將拼接結果 存放于 常量池中,

(2)常量(字面量)拼接 -- 拼接結果存于常量池
  對于兩個及以上字面量拼接操作,在編譯時會進行優化,若該拼接結果不存在于常量池中,則直接將其拼接結果存于常量池,并回傳其參考地址,否則,回傳常量池中該結果所在的參考地址,

【舉例:】
package com.test;

public class JVMDemo {

    public static void main(String[] args) {
        String c1 = "a" + "b" + "c";// 編譯期優化,等同于 "abc",并存放于常量池中
        String c2 = "abc"; // "abc" 已存在于常量池,此時直接將常量池中的地址 賦給 c2
        System.out.println(c1 == c2); // true
        System.out.println(System.identityHashCode(c1));
        System.out.println(System.identityHashCode(c2));
    }
}

 

 

 

(3)final 修飾的變數拼接(可以理解為常量) -- 拼接結果存于常量池
  由于 final 修飾的變數不可被修改,在編譯期優化等同于 常量進行拼接操作,所以結果存放于常量池中,

【舉例:】
package com.test;

public class JVMDemo {

    public static void main(String[] args) {
        final String c1 = "hello";
        final String c2 = "world";
        String c3 = "helloworld"; // "helloworld" 存放于常量池中
        String c4 = c1 + c2; // 等價于 "hello" + "world" 常量進行拼接操作
        System.out.println(c3 == c4); // true
        System.out.println(System.identityHashCode(c3));
        System.out.println(System.identityHashCode(c4));
    }
}

 

 

 

(4)一般變數拼接 -- 拼接結果存于 堆
  拼接操作中出現變數時,會觸發 new StringBuilder 操作,并使用 StringBuilder 的 append 方法進行字串拼接,最終呼叫其 toString 方法轉為字串,并回傳參考地址,
注:
  StringBuilder 的 toString 方法內部呼叫 new String(),其最終拼接結果存放于 堆 中(不會將拼接結果存放于常量池,可以手動呼叫 intern() 方法將結果放入常量池,后面介紹,往下看),

使用 StringBuilder 進行字串拼接操作效率要遠高于使用 String 進行字串拼接操作,
  使用 String 直接進行拼接操作時,若出現變數,則會先創建 StringBuilder 物件,最終輸出結果還得轉為 String 物件,即使用 String 進行字串拼接程序中 可能出現多個 StringBuilder 和 String 物件(比如在 回圈中 進行字串拼接操作),且創建物件過多會占用更多的記憶體,
  而使用 StringBuilder 進行拼接操作時,只需要創建一個 StringBuilder 物件即可,可以節省記憶體空間以及提高效率執行,

【舉例:】
package com.test;

public class JVMDemo {

    public static void main(String[] args) {
        String c1 = "hello";
        String c2 = "world";
        String c3 = "hello" + "world";  // 等價于 "helloworld",存于常量池
        String c4 = "helloworld"; // 常量池中已存在,直接賦值常量池參考
        String c5 = "hello" + c2; // 拼接結果存于 堆
        String c6 = c1 + "world"; // 拼接結果存于 堆
        String c7 = c1 + c2; // 拼接結果存于 堆
        System.out.println(System.identityHashCode(c3));
        System.out.println(System.identityHashCode(c4));
        System.out.println(System.identityHashCode(c5));
        System.out.println(System.identityHashCode(c6));
        System.out.println(System.identityHashCode(c7));
    }
}

 

 

 

 

 

(5)拼接結果呼叫 intern 方法 -- 結果存放于常量池
  由于不同版本 JDK 的 intern() 方法執行結果不同,此處暫時略過,接著往下看,

4、String 使用 new 關鍵字創建物件問題 -- 筆試題

(1)new String("hello") 會創建幾個物件?
  可能會創建 1 個或 2 個物件,
  new 關鍵字會在堆中創建一個物件,而當字串常量池中不存在 "hello" 時,會創建一個物件存入字串常量池,若常量池中存在物件,則不會創建、會直接參考,

【舉例:】
public class JVMDemo {
    public static void main(String[] args) {
        String c1 = new String("hello");
        String c2 = new String("hello");
    }
}

 

 

 

(2)new String("hello") + new String("world") 創建了幾個物件?
  創建了 6 個物件(不考慮常量池是否存在資料),
物件創建:
  由于涉及到變數的拼接,所以會觸發 new StringBuilder() 操作,此處創建 1 個物件,
  new String("hello") 通過上例分析,可以知道會創建 2 個物件(堆 1 個,字串常量池 1 個),
  同理 new String("world") 也會創建 2 個物件,
  最終拼接結果 會觸發 StringBuilder 的 toString() 方法,內部呼叫 new String() 在堆中創建一個物件(此處不會在字串常量池中創建物件),

注:
  StringBuilder 的 toString() 內部的 new String() 并不會在 字串常量池 中創建物件,
  String str = new String("hello"); 這種形式創建的字串 可以在字串常量池中創建物件,
此處我是根據 位元組碼檔案 中是否有 ldc 指令來判斷的(后續根據 intern() 方法同樣也可以證明這點),有不對的地方,還望不吝賜教,

【舉例:】
public class JVMDemo {
    public static void main(String[] args) {
        String a = new String("hello") + new String("world");
    }
}

 

 

 

5、String 中的 intern() 相關問題 -- 筆試題

(1)intern() 作用
  對于非字面量直接宣告的 String 物件(通過 new 創建的物件),可以使用 String 提供的 intern 方法獲取字串常量池中的資料,

該方法作用:

  從字串常量池中查詢當前字串是否存在(通過 equals 方法比較),如果不存在,則會將當前字串放入常量池中并回傳該參考地址(此處不同版本的 JDK 有不同的實作),若存在則直接回傳參考地址,

【JDK 8 注釋】
/**
 * 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();

 

(2)不同 JDK 版本中 intern() 使用的區別
  JDK 6:嘗試將該字串物件 放入 字串常量池中(字串常量池位于 方法區中),
    若字串常量池中已經存在 該物件,則回傳字串常量池 當前物件的參考地址,
    若沒有該物件,則將 當前物件值 復制一份放入字串常量池,并回傳此時物件的參考地址,

  JDK 7 之后:嘗試將該字串物件 放入 字串常量池中(字串常量池位于 堆中),
    若字串常量池中已經存在 該物件,則回傳字串常量池 當前物件的參考地址,
    若沒有該物件,則將 當前物件的 參考地址 復制一份放入字串常量池,并回傳參考地址,

(3)使用 JDK8 演示 intern()
  此處使用 JDK8 演示 intern() 方法,有興趣可以自行研究 JDK6 的操作,

【例一:】
public class JVMDemo {
    public static void main(String[] args) {
        String a = new String("hello"); // 此時常量池存在 "hello"
        String b = "hello"; // 直接參考常量池中 "hello"
        String c = a.intern(); // 直接參考常量池中 "hello"
        System.out.println(System.identityHashCode(a));
        System.out.println(System.identityHashCode(b));
        System.out.println(System.identityHashCode(c));
        System.out.println(b == a); // false,a 指向 堆 內物件,b 指向 字串常量池
        System.out.println(b == c); // true,b,c 均指向字串常量池
    }
}

【例二:(b,c 互換位置)】
public class JVMDemo {
    public static void main(String[] args) {
        String a = new String("hello"); // 此時常量池存在 "hello"
        String c = a.intern(); // 直接參考常量池中 "hello"
        String b = "hello"; // 直接參考常量池中 "hello"
        System.out.println(System.identityHashCode(a));
        System.out.println(System.identityHashCode(b));
        System.out.println(System.identityHashCode(c));
        System.out.println(b == a); // false,a 指向 堆 內物件,b 指向 字串常量池
        System.out.println(b == c); // true,b,c 均指向字串常量池
    }
}

【分析:】
    例一 與 例二 的區別在于 intern() 執行時機不同,且兩者輸出結果相同,
    JDK 8 中 intern() 執行時,若字串常量池中 equals 未比較出相同資料,則將當前物件的參考地址 復制一份并放入常量池,
    若存在資料,則回傳常量池中資料的參考地址,
    
    即 new String() 操作后,若常量池中不存在 資料,則呼叫 intern() 后,會復制 堆的地址 并存入 常量池中,后續獲得的均為 堆的地址,也即上述 例一、例二 中 a、b、c 操作后,均相同且指向 堆,
  若常量池存在資料,則呼叫 intern() 后,回傳常量池參考,后續獲得的均為 常量池參考,也即上述 例一、例二 中 a 為指向堆 的參考地址,b,c 均為指向常量池的參考地址,

    通過輸出結果可以看到,上述 例一、例二 中 a、b、c 操作后,b, c 相同且不同于 a(即 b、c 指向常量池),從側面也反映出 new String("hello") 執行后 常量池中存在 "hello",

 

 

 

 

【例四:】
public class JVMDemo {
    public static void main(String[] args) {
        char[] a = new char[]{'h', 'e', 'l', 'l', 'o'};
        String b = new String(a, 0 , a.length); // 此時常量池中不存在 "hello"
        String c = "hello"; // 此時常量池中存在 "hello"
        String d = b.intern(); // 直接參考常量池中的 "hello"

        System.out.println(System.identityHashCode(b));
        System.out.println(System.identityHashCode(c));
        System.out.println(System.identityHashCode(d));
        System.out.println(c == b); // false, b 指向堆物件, c 指向常量池
        System.out.println(c == d); // true,c,d 均指向常量池
    }
}

【例五:】
public class JVMDemo {
    public static void main(String[] args) {
        char[] a = new char[]{'h', 'e', 'l', 'l', 'o'};
        String b = new String(a, 0 , a.length); // 此時常量池中不存在 "hello"
        String d = b.intern(); // 常量池不存在 "hello",復制 b 的參考到常量池中(指向堆的參考)
        String c = "hello"; // 直接獲取常量池中的參考(指向堆的參考)

        System.out.println(System.identityHashCode(b));
        System.out.println(System.identityHashCode(c));
        System.out.println(System.identityHashCode(d));
        System.out.println(c == b); // true, c, b 均指向堆
        System.out.println(c == d); // true, c, d 均指向堆
    }
}

【分析:】
    例四 與 例五 的區別在于 intern() 執行時機不同,且兩者輸出結果相同,
    JDK 8 中 intern() 執行時,若字串常量池中 equals 未比較出相同資料,則將當前物件的參考地址 復制一份并放入常量池,
    若存在資料,則回傳常量池中資料的參考地址,
    
    例四中,new String() 執行后,常量池中不存在 "hello",
    但 String c = "hello" 執行后,常量池中存在 "hello",從而 intern() 獲取的是常量池中的參考地址,
    也即 b 為指向 堆的參考,c,d 均為指向常量池的參考,
    
    例五中,new String() 執行后,常量池中不存在 "hello",
    intern() 執行后會將當前物件地址(指向堆的參考)復制并放入常量池,從而 String c = "hello" 獲取的是常量池的參考地址,
    也即 b,c,d 獲取的均是指向 堆 的參考,

 

 

 

對例四、例五進行一下延伸,

【例六:】
public class JVMDemo {
    public static void main(String[] args) {
        String a = new String("hello") + new String("world");
        String b = "helloworld";
        String c = a.intern();
        System.out.println(System.identityHashCode(a));
        System.out.println(System.identityHashCode(b));
        System.out.println(System.identityHashCode(c));
        System.out.println(b == a); // false, a 指向堆, b 指向常量池
        System.out.println(b == c); // true, b、c 均指向常量池
    }
}

【例七:】
public class JVMDemo {
    public static void main(String[] args) {
        String a = new String("hello") + new String("world");
        String c = a.intern();
        String b = "helloworld";
        System.out.println(System.identityHashCode(a));
        System.out.println(System.identityHashCode(b));
        System.out.println(System.identityHashCode(c));
        System.out.println(b == a); // true, a、b 均指向堆,
        System.out.println(b == c); // true, b、c 均指向堆
    }
}

【分析:】
    涉及到變數字串拼接,會觸發 StringBuilder 進行相關操作,
    最終觸發 toString() 轉為 String,其內部呼叫的是 String(char value[], int offset, int count) 構造方法,
    此方法在堆中創建 字串 但不會向常量池中添加資料(與 例四、例五 是同樣的場景),

 

 

 

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

標籤:Java

上一篇:面試題系列第1篇:說說==和equals的區別?你的回答可能是錯誤的

下一篇:理解傳輸層中UDP協議首部校驗和以及校驗和計算方法的Java實作

標籤雲
其他(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