主頁 > 後端開發 > JVM | 第2部分:虛擬機執行子系統《深入理解 Java 虛擬機》

JVM | 第2部分:虛擬機執行子系統《深入理解 Java 虛擬機》

2022-03-25 06:27:04 後端開發

目錄
  • 前言
  • 5. 類檔案結構
    • 5.1 無關性概述
    • 5.2 Class 類檔案結構
    • 5.3 class 檔案的資料項
    • 5.4 位元組碼指令
    • 5.5 位元組碼用途分類
  • 6. 類加載機制
    • 6.1 必須要對類進行初始化的五種時機(對類的主動參考)
    • 6.2 類加載程序(生命周期)
    • 6.3 類加載器
    • 6.3 雙親委派模式
    • 6.4 破壞雙親委派模式
  • 7. 虛擬機位元組碼執行引擎
    • 7.1 確定被呼叫的方法
  • 最后


前言

參考資料
《深入理解 Java 虛擬機 - JVM 高級特性與最佳實踐》

第1部分主題為自動記憶體管理,以此延伸出 Java 記憶體區域與記憶體溢位、垃圾收集器與記憶體分配策略、引數配置與性能調優等相關內容;

第2部分主題為虛擬機執行子系統,以此延伸出 class 類檔案結構、虛擬機類加載機制、虛擬機位元組碼執行引擎等相關內容;

第3部分主題為程式編譯與代碼優化,以此延伸出程式前后端編譯優化、前端易用性優化、后端性能優化等相關內容;

第4部分主題為高效并發,以此延伸出 Java 記憶體模型、執行緒與協程、執行緒安全與鎖優化等相關內容;

本系列學習筆記可看做《深入理解 Java 虛擬機 - JVM 高級特性與最佳實踐》書籍的縮減版與總結版,想要了解細節請見紙質版書籍;


5. 類檔案結構

5.1 無關性概述

  • 實作語言無關性的基礎是虛擬機和位元組碼存盤格式;
  • Java 虛擬機不和包括 Java 在內的任何語言系結,它只與 class 檔案這種特定的二進制檔案格式所關聯;
  • Java 虛擬機不關心 class 的來源是何種語言,比如 Groovy、Scala 等語言都能產出符合規范的class檔案;
  • Java 虛擬機規范要求在 class 檔案中使用許多強制性的語法和結構化約束;

5.2 Class 類檔案結構

  • class 檔案是一組以 8位bit(1位元組)為基礎單位 的二進制流,各個資料專案嚴格按照順序緊湊的排列在 class 檔案之中,中間沒有任何分隔符,當遇到需要占用 1 位元組以上空間的資料項時,則會按照高位在前的方式分割成若干個 1 位元組進行存盤;
  • 包含兩種資料型別:
    • 無符號數:基本的資料型別,以 u1、u2、u4、u8 來分別代表 1 個位元組、2 個位元組、4 個位元組和 8 個位元組的無符號數,無符號數可以用來描述數字、索引參考、數量值或者字串值;
    • :由多個無符號數或者其他表作為資料項構成的復合資料型別,表用于描述有層次關系的復合結構的資料,整個 class 檔案本質上就是一張表;
  • class 檔案的資料項如下表:
    class檔案格式

5.3 class 檔案的資料項

  • u4 魔數(Magic Number):唯一的作用是確定這個檔案是否為一個能被虛擬機接受的 class 檔案,固定為 0xCAFEBABE;
  • u2+u2 版本:虛擬機也必須拒絕執行超過其版本號的 class 檔案;
  • u2+ 常量池:常量池容量計數器用來記錄常量個數,常量池中主要存放兩大類常量:
    • 字面量:近于 Java 語言層面的常量概念,如文本字串、final 修飾的常量值等;
    • 符號參考:編譯原理方面的概念,包括了:類和介面的全限定名、欄位的名稱和描述符、方法的名稱和描述符,常量池中的每一項常量都是一個表,可以用 javap 分析 class 檔案;
  • u2 訪問標記:用于標識一些類或者介面層次的訪問資訊;
  • 4*u2 類與介面索引集合:由這 4 項資料確定類的繼承關系;
  • u2+ 欄位表集合:用于描述介面或者類中宣告的變數,包括類級變數和實體級變數,不包括在方法內部宣告的區域變數;(public、static、final、volatile、transient 等)
  • u2+ 方法表集合:類似上面欄位表,方法里的 Java 代碼,經過編譯器編譯成位元組碼指令后,存放在方法屬性表集合中一個名為"Code"的屬性里,方法呼叫指令以常量池中指向方法的符號參考作為引數;
  • u2+ 屬性表集合:不是單獨的一部分,而是由 class 檔案、欄位表、方法表等攜帶,以描述某些場景專有的資訊;

5.4 位元組碼指令

  • 由一個位元組長度的、代表著某種特定操作含義的數字(稱為操作碼,Opcode)以及跟隨其后的0至多個所需引數(稱為運算元,Operands)構成;
  • 由于 Java 虛擬機采用面向運算元堆疊的架構,而不是暫存器,所以多大數的指令都不包含運算元,只有一個操作碼(追求小數量、高傳輸效率),對運算元堆疊進行出堆疊、入堆疊操作;
  • 指令集的操作碼總數不超過 256 條(操作碼只有1位元組),因此 Java 虛擬機的指令集對于特定的操作只提供了有限的型別相關指令去支持(例如有 int 型別的 iload,沒有 byte 型別的同類指令);
  • 對于沒有定義的資料型別的相關指令,大多數會在編譯期或運行期轉換成 int 型別作為運算型別;

5.5 位元組碼用途分類

  • 加載和存盤指令:用于將資料在堆疊幀中的區域變數表和運算元堆疊之間來回傳輸,比如 iload、istore、bipush等;
  • 運算指令:用于對兩個運算元堆疊上的值進行某種特定運算,并把結果重新存入到運算元堆疊頂,比如加法指令:iadd,減法指令:isub 等等;
  • 型別轉換指令:將兩種不同的數值型別進行相互轉換,這些轉換操作一般用于實作用戶代碼中的顯示型別轉換操作,或者處理前面提到的指令集中資料型別相關指令無法與資料型別一一對應的問題(byte、short等擴展為int);
  • 物件創建與訪問指令:要注意 Java 虛擬機對類實體和陣列的創建與操作使用了不同的位元組碼指令,創建類實體:new,創建陣列:nwarray、anewarray 等;
  • 運算元堆疊管理指令:類似于操作普通資料結構中的堆疊,Java虛擬機提供了一些用于直接操作運算元堆疊的指令,比如pop、dup、swap等;
  • 控制轉移指令:可以讓 Java 虛擬機有條件或無條件的修改程式計數器的值,包括條件分支(比如ifeq)、復合條件分支(比如tableswitch)、無條件分支(比如goto)等等;
  • 方法呼叫和回傳指令:方法呼叫指令包括,像 invokevirtual 指令:用于呼叫物件的實體方法,invokespecial指令:呼叫一些需要特殊處理的方法,包括實體初始化方法、私有方法和父類方法;方法呼叫指令與資料型別無關,但方法回傳指令是根據回傳值型別區分的,包括ireturn(回傳boolean、byte、char、short、int),lreturn、freturn、dreturn和areturn,另外還有一條return指令供宣告為void的方法、實體初始化方法以及類和介面類初始化方法使用;
  • 例外處理指令:Java 程式中顯示拋出例外的操作(throw)都是用 athrow 指令來實作,除此之外,Java 虛擬機規范還規定了許多運行時例外會在其他 Java 虛擬機指令檢測到例外狀況時自動拋出,比如在整數運算中,當除數為 0 時,虛擬機會在 idiv 或 ldiv 指令中拋出 ArithmeticException 例外,現在在 Java 虛擬機中處理例外是采用例外表完成的,以前則使用的是 jsr 和 ret 指令實作;
  • 同步指令:synchronized 陳述句塊對應的指令就是 monitorenter 和 monitorexit,編譯器必須確保無論方法通過何種方式完成,方法中呼叫過的每條 monitorenter 指令都必須執行其對應的 monitorexit 指令,所以為了保證在方法例外完成時,monitorenter 和 monitorexit 指令依然可以正確配對執行,編譯器會自動產生一個例外處理器,這個例外處理器宣告可以處理所有的例外;

6. 類加載機制

6.1 必須要對類進行初始化的五種時機(對類的主動參考)

  • 遇到 newgetstaticputstaticinvokestatic 這 4 條位元組碼指令時沒初始化觸發初始化;(即:new 關鍵字實體化物件、讀取一個類的 finel 靜態欄位、呼叫一個類的靜態方法);
  • 使用 java.lang.reflect 包的方法對類進行反射呼叫;
  • 發現某類的父類還沒有進行初始化,先觸發其父類的初始化;
  • 當虛擬機啟動時,用戶需指定一個要加載的主類(包含 main() 方法的那個類),虛擬機會先初始化這個主類;
  • 當使用 JDK 1.7 的動態語言支持時,如果一個 java.lang.invoke.MethodHandle 實體最后的決議結果 REF_getStaticREF_putStaticREF_invokeStatic 的方法句柄,并且這個方法句柄所對應的類沒有進行過初始化,則需先觸發其初始化;

6.2 類加載程序(生命周期)

  • 程式主動使用某個類時,如果該類還未被加載到記憶體中,則 JVM 會通過加載、連接、初始化 3 個步驟來對該類進行初始化;
  • 在程式運行期間完成;
  • 1. 加載:將類的 class 檔案讀入到記憶體,通過一個類的全限定名來獲取定義次類的二進制流,將這個位元組流所代表的靜態存盤結構轉換成方法區中的運行時資料結構,在堆中生成一個代表這個類的 java.lang.Class 物件,作為方法區類資料的訪問入口(反射介面),這個程序需要類加載器參與;
    • 陣列類的特殊性:陣列類本身不通過類加載器創建,它是由 Java 虛擬機直接創建的:
      • 如果陣列的組件型別是參考型別,那就遞回采用類加載加載;
      • 如果陣列的組件型別不是參考型別,Java 虛擬機會把陣列標記為引導類加載器關聯;
      • 陣列類的可見性與他的組件型別的可見性一致,如果組件型別不是參考型別,那陣列類的可見性將默認為 public;
  • 連接:負責把類的二進制資料合并到 JRE 中(將 Java 類的二進制代碼合并到 JVM 的運行狀態之中);
    • 2. 驗證:確保加載的類資訊符合 JVM 規范,沒有安全方面的問題,驗證是否符合 Class 檔案格式規范,并且是否能被當前的虛擬機加載處理;
      • (驗證即其之前都是操作位元組流的,之后操作基于方法區的存盤結構);
      • 驗證程序包括檔案格式驗證、元資料驗證、位元組碼驗證(最復雜)、符號參考驗證
    • 3. 準備:為類變數(static 變數)分配記憶體并設定類變數初始值的階段,這些記憶體都將在方法區中進行分配;(static 修飾的變數賦默認值,final 和 static 修飾的變數直接賦值(編譯時生成 ConstantValue 屬性));
    • 4. 決議:(這里是靜態決議)虛擬機常量池的符號參考替換為直接參考程序;
      • 符號參考:以一組符號來描述所參考的目標,符號可以使任何形式的字面量,與虛擬機的記憶體布局無關,參考的目標并不一定加載到記憶體中;
      • 直接參考:可以使直接指向目標的指標、相對偏移量或是一個能間接定位到目標的句柄(與記憶體布局有關),與虛擬機布局相關;
      • (決議及其之前都是虛擬機主導,之后是 Java 代碼主導);
  • 5. 初始化:執行類構造器 <clinit>() 方法的程序,為類的變數賦予正確的初始值,類構造器 <clinit>() 方法是由編譯器自動收藏類中的所有類變數的賦值動作和靜態陳述句塊(static塊)中的陳述句合并產生,代碼從上往下執行,如果發現父類還沒有進行過初始化,則需要先觸發其父類的初始化,虛擬機保證一個類的 <clinit>() 方法在多執行緒環境中被正確加鎖和同步;
  • 6. 使用
  • 7. 卸載

6.3 類加載器

  • 概述
    • 由 JVM 提供,是所有程式運行的基礎;
    • 開發者可以通過繼承 ClassLoader 基類來創建自己的類加載器;
    • 類加載器的任務就是根據一個類的全限定名來讀取此類的二進制位元組流到 JVM 中,然后轉換為一個與目標類對應的 java.lang.Class 物件實體;
    • 最終產物就是位于堆中的 Class 物件,該物件封裝了類在方法區中的資料結構,并且向用戶提供了訪問方法區資料結構的介面,即 Java 反射的介面;
  • 幾種類加載器
    • 啟動類加載器(Bootstrap Class Loader):用來加載 Java 的核心類,是用原生代碼來實作的,并不繼承自 java.lang.ClassLoader,加載 lib 下或被 -Xbootclasspath 路徑下的類,C++ 實作,不允許直接通過參考啟動類加載器進行操作,
    • 擴展類加載器(Extensions Class Loader):Sun 公司(已被 Oracle 收購)實作的 sun.misc.Launcher$ExtClassLoader 類,由 Java 語言實作的,是 Launcher 的靜態內部類,它負責加載 <JAVA_HOME>/lib/ext 目錄下或者由系統變數 -Djava.ext.dir 指定位路徑中的類別庫,開發者可以直接使用標準擴展類加載器;
    • 系統類加載器(System Class Loader)、應用程式類加載器(Application Class Loade):負責在 JVM 啟動時加載來自 Java 命令的 -classpath 選項、java.class.path 系統屬性,或者 CLASSPATH 將變數所指定的 JAR 包和類路徑,程式可以通過 ClassLoader 的靜態方法 getSystemClassLoader() 來獲取系統類加載器,如果沒有特別指定,則用戶自定義的類加載器都以此類加載器作為父加載器,由 Java 語言實作,父類加載器為 ExtClassLoader;
  • 類加載器間的關系
    • 啟動類加載器:C++ 實作,沒有父類;
    • 拓展類加載器(ExtClassLoader):Java 實作,父類加載器為 Null;
    • 系統類加載器(AppClassLoader):Java 實作,父類加載器為 ExtClassLoader;
    • 自定義類加載器,父類加載器為 AppClassLoader;
  • 類加載器的執行步驟
    • 1. 判斷緩沖區中是否有此 Class,如果有直接進入第 8 步,否則進入第 2 步;
    • 2. 判斷父類加載器是否存在,存在則進入第 3 步,否則說明 Parent / 本身是啟動類加載器,則跳到第 4 步;
    • 3. 請求使用父類加載器去載入目標類,如果載入成功則跳至第 8 步,否則接著執行第 5 步;
    • 4. 請求使用啟動類加載器去載入目標類,如果載入成功則跳至第 8 步,否則跳至第 7 步;
    • 5. 當前類加載器嘗試尋找 Class 檔案,如果找到則執行第 6 步,如果找不到則執行第 7 步;
    • 6. 從檔案中載入 Class,成功后跳至第 8 步;
    • 7. 拋出 ClassNotFountException 例外;
    • 8. 回傳對應的 java.lang.Class 物件;

類加載器作業流程

6.3 雙親委派模式

  • 作業原理:如果一個類加載器收到了類加載請求,它并不會自己先去加載,而是把這個請求委托給父類的加載器去執行,如果父類加載器還存在其父類加載器,則進一步向上委托,依次遞回,請求最終將到達頂層的啟動類加載器,如果父類加載器可以完成類加載任務,就成功回傳,倘若父類加載器無法完成此加載任務,子加載器才會嘗試自己去加載;
    • 優勢:Java 類隨著它的類加載器一起具備了一種帶有優先級的層次關系,通過這種層級關可以避免類的重復加載,即:當父親已經加載了該類時,就沒有必要子 ClassLoader 再加載一次,安全因素,Java 核心 API 中定義型別不會被隨意替換(父類已經加載過,從父類中查找回傳);

6.4 破壞雙親委派模式

  • 到目前為止,雙親委派模型主要出現過3次較大規模的“被破壞的”情況:
  • 第一次:主要是歷史問題,雙親委派模型在 JDK1.2 之后才被引入,在這之前用戶都是通過重寫 loadClass() 方法實作自定義加載器,為了向前兼容,JDK1.2 之后的 java.Lang.ClassLoader 添加了一個新的 protected 方法 findClass(),以此保證雙親委派模型;
  • 第二次:由模型本身的缺陷導致的,缺陷在于:當某個類的介面使用父類加載器,而其實作類使用子類加載器時,父類加載器無法委托子類加載器作業,Java 服務介面 SPI 由 Java 核心庫提供,靠啟動類加載器來加載的,而 SPI 的實作類需要由應用程式類加載器來加載,在加載 SPI 的實作類時,啟動類加載器無法找到應用程式類加載器,因為依照雙親委派模型,BootstrapClassloader 無法委派 AppClassLoader 來加載類,JDK 設定執行緒背景關系類加載器(Thread Context ClassLoader),當父類加載器需要使用子類加載器(子類加載器未創建)時,會從父執行緒中繼承一個執行緒背景關系類加載器,以此請求子類加載器去完成類加載的動作,這種行為實際上已經打破了雙親委派模型的層次結構來逆向使用類加載器,已經違背了雙親委派模型的一般性原則;
  • 第三次:由開發者對程式動態性的追求而導致,動態性指:代碼熱替換、模塊熱部署等,OSGi(面向Java的動態模塊化系統)實作模塊化熱部署的關鍵就是它自定義的類加載器機制的實作,當需要更換一個 Bundlle(程式模塊)時,就把 Bundle 連同類加載器一起換掉以實作代碼的熱替換,在替換時需要在平級間呼叫類加載器,在原則上破壞了雙親委派模型;

7. 虛擬機位元組碼執行引擎

“堆疊幀”的概念在《JVM | 第1部分:自動記憶體管理與性能調優》提到,這里不再贅述;

7.1 確定被呼叫的方法

  • 決議:所有方法呼叫的目標方法在 Class 檔案里都是一個常量池中的符號參考,有兩種決議:
    • 靜態決議:其中的一部分符號參考在類加載的決議階段會被轉化為直接參考(即:靜態方法、final 修飾的方法、私有方法、父類方法、<init>方法,統稱非虛方法);
    • 動態鏈接:其他的符號參考會在運行期被決議為直接參考;
    • Java 虛擬機提供了 5 條方法呼叫位元組碼指令:invokestatic(靜態方法)、invokespecial(實體構造器 <init> 方法、私有方法和父類方法)、invokevirtual(虛方法)、invokeinterface(介面方法)、invokedynamic(動態決議);
  • 分派:用來確定虛方法的目標方法,體現 Java 面向物件的繼承、封裝和多型 3 大特性,有如下 4 種:
    • 靜態分派:典型應用是處理方法多載,多載的方法在經過編譯期編譯后得到相同的方法呼叫位元組碼指令和指令引數,虛擬機在處理多載時是通過引數的靜態型別,方法引數的允許發送型別轉變,但方法接收者本身靜態型別不變;
      • 如果物件 A 繼承 B,那么對于陳述句:B b = new A(); 其中 B 稱為 b 變數的靜態型別(Static Type,編譯器可知),A 稱為 b 變數的實際型別(Actual Type,運行期可知);
      • 選擇靜態分派目標的程序(多載的本質),例如:嘗試呼叫方法 say('a')
        • 'a' 首先是一個 char 型別:對應 say(char arg)
        • 其次還可以代表數字 97(參照 ASCII 碼):對應 say(int arg)
        • 而轉化為 97 之后,還可以轉型為 long 型別的 97L:對應 say(long arg)
        • 另外還能被自動裝箱包裝為 Character:對應 say(Character arg)
        • 裝箱類 Character 還實作了 Serializable 介面(若直接或間接實作了多個介面,優先級都是一樣的,如果出現能適配多個介面的多個多載方法,會提示型別模糊,拒絕編譯):對應 say(Serializable)
        • 而且 Character 還繼承自 Object(如果有多個父類,那將在繼承關系中從下往上開始搜索,越接近上層的優先級越低),對應 say(Object arg)
        • 最侄訓能匹配到變長型別:對應 say(char... arg)
    • 動態分派:典型應用是方法重寫,Java 虛擬機在運行期會依據 invokevirtual 指令的多型查找程序,通過實際型別來分派方法執行版本的,程序如下:
      • 1. 找到運算元堆疊頂的第一個元素所指向的物件的實際型別,記做 M;
      • 2. 如果在型別 M 中找到與常量中的描述符和簡單名稱都相符的方法,則進行訪問權限校驗,若通過則回傳這個方法的直接參考,查找程序結束;否則則回傳 IllegalAccessError 例外;
      • 3. 否則,按照繼承關系從下往上依次對 M 的各個父類進行第 2 步的搜索和驗證程序;
      • 4. 如果始終沒有找到合適的方法,則拋出 AbstractMethodError 例外;
    • 單分派和多分派:方法的接收者和方法的引數統稱為方法的宗量, 根據分派基于多少種宗量,可以將分派劃分為單分派和多分派兩種;
  • 決議和分派不強調二選一的關系,強調的是在不同層次上的解決方案,例如:靜態方法會在類加載的決議階段就進行直接參考的轉化,而靜態方法也是可以擁有多載版本的,選擇多載版本的程序也是通過靜態分派完成的;
  • Java 語言的 靜態多分派、動態單分派 示例:
    • 方法多載:編譯期看靜態分派,運行期看動態分派
public class Main {
    static class A {
    }
    static class B extends A {
    }
    static class C extends B {
    }
    public void say(A a) {
        System.out.println("A");
    }
    public void say(B b) {
        System.out.println("B");
    }
    public void say(C c) {
        System.out.println("C");
    }
    public static void main(String[] args) throws Exception {
        Main main = new Main();
        Main superMain = new Super();
        B os = new C();
        main.say(os);
        superMain.say((A) os);
        //輸出 B S-A
    }
}
 
class Super extends Main {
    public void say(A a) {
        System.out.println("S-A");
    }
    public void say(B b) {
        System.out.println("S-B");
    }
    public void say(C c) {
        System.out.println("S-C");
    }
}
  • 編譯期看靜態分派 - 多分派:
    • main 和 superMain 的靜態型別都是 Main,方法引數的靜態型別一個是 B,一個是 A,所以此次選擇產生的兩條 invokevitrual 指令的引數分別為常量池中指向 Main.say(B) 和 Main.say(A) 的方法的符號參考,這里根據兩個宗量(方法接受者和引數)進行選擇;
  • 運行期看動態分派 - 單分派:
    • 這階段 Java 虛擬機此時不用關心引數的靜態型別、實際型別,只有方法接收者的實際型別會影響到方法版本的選擇,Main.say(B) 和 Main.say(A) 方法的實際型別分別是 Main.say(B) 和 Super.say(A),也就是只有一個宗量作為選擇依據;


最后

新人制作,如有錯誤,歡迎指出,感激不盡!
歡迎關注公眾號,會分享一些更日常的東西!
如需轉載,請標注出處!

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

標籤:Java

上一篇:回圈嵌套(基礎練習)---控制跳轉陳述句

下一篇:python之面向物件的程式開發

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