夯實Java基礎系列23:一文讀懂繼承、封裝、多態的底層實現原理

来源:https://www.cnblogs.com/xll1025/archive/2019/10/09/11645103.html
-Advertisement-
Play Games

本系列文章將整理到我在GitHub上的《Java面試指南》倉庫,更多精彩內容請到我的倉庫里查看 https://github.com/h2pl/Java Tutorial 喜歡的話麻煩點下Star哈 文章首發於我的個人博客: www.how2playlife.com <! more 從JVM結構開始 ...


本系列文章將整理到我在GitHub上的《Java面試指南》倉庫,更多精彩內容請到我的倉庫里查看

https://github.com/h2pl/Java-Tutorial

喜歡的話麻煩點下Star哈

文章首發於我的個人博客:

www.how2playlife.com

從JVM結構開始談多態

Java 對於方法調用動態綁定的實現主要依賴於方法表,但通過類引用調用和介面引用調用的實現則有所不同。總體而言,當某個方法被調用時,JVM 首先要查找相應的常量池,得到方法的符號引用,並查找調用類的方法表以確定該方法的直接引用,最後才真正調用該方法。以下分別對該過程中涉及到的相關部分做詳細介紹。

JVM 的結構

典型的 Java 虛擬機的運行時結構如下圖所示

圖 1.JVM 運行時結構

圖 1.JVM 運行時結構

此結構中,我們只探討和本文密切相關的方法區 (method area)。當程式運行需要某個類的定義時,載入子系統 (class loader subsystem) 裝入所需的 class 文件,併在內部建立該類的類型信息,這個類型信息就存貯在方法區。類型信息一般包括該類的方法代碼、類變數、成員變數的定義等等。可以說,類型信息就是類的 Java 文件在運行時的內部結構,包含了改類的所有在 Java 文件中定義的信息。

註意到,該類型信息和 class 對象是不同的。class 對象是 JVM 在載入某個類後於堆 (heap) 中創建的代表該類的對象,可以通過該 class 對象訪問到該類型信息。比如最典型的應用,在 Java 反射中應用 class 對象訪問到該類支持的所有方法,定義的成員變數等等。可以想象,JVM 在類型信息和 class 對象中維護著它們彼此的引用以便互相訪問。兩者的關係可以類比於進程對象與真正的進程之間的關係。

Java 的方法調用方式

Java 的方法調用有兩類,動態方法調用與靜態方法調用。靜態方法調用是指對於類的靜態方法的調用方式,是靜態綁定的;而動態方法調用需要有方法調用所作用的對象,是動態綁定的。類調用 (invokestatic) 是在編譯時刻就已經確定好具體調用方法的情況,而實例調用 (invokevirtual) 則是在調用的時候才確定具體的調用方法,這就是動態綁定,也是多態要解決的核心問題。

JVM 的方法調用指令有四個,分別是 invokestatic,invokespecial,invokesvirtual 和 invokeinterface。前兩個是靜態綁定,後兩個是動態綁定的。本文也可以說是對於 JVM 後兩種調用實現的考察。

常量池(constant pool)

常量池中保存的是一個 Java 類引用的一些常量信息,包含一些字元串常量及對於類的符號引用信息等。Java 代碼編譯生成的類文件中的常量池是靜態常量池,當類被載入到虛擬機內部的時候,在記憶體中產生類的常量池叫運行時常量池。

常量池在邏輯上可以分成多個表,每個表包含一類的常量信息,本文只探討對於 Java 調用相關的常量池表。

CONSTANT_Utf8_info

字元串常量表,該表包含該類所使用的所有字元串常量,比如代碼中的字元串引用、引用的類名、方法的名字、其他引用的類與方法的字元串描述等等。其餘常量池表中所涉及到的任何常量字元串都被索引至該表。

CONSTANT_Class_info

類信息表,包含任何被引用的類或介面的符號引用,每一個條目主要包含一個索引,指向 CONSTANT_Utf8_info 表,表示該類或介面的全限定名。

CONSTANT_NameAndType_info

名字類型表,包含引用的任意方法或欄位的名稱和描述符信息在字元串常量表中的索引。

CONSTANT_InterfaceMethodref_info

介面方法引用表,包含引用的任何介面方法的描述信息,主要包括類信息索引和名字類型索引。

CONSTANT_Methodref_info

類方法引用表,包含引用的任何類型方法的描述信息,主要包括類信息索引和名字類型索引。

圖 2. 常量池各表的關係

圖 2. 常量池各表的關係

可以看到,給定任意一個方法的索引,在常量池中找到對應的條目後,可以得到該方法的類索引(class_index)和名字類型索引 (name_and_type_index), 進而得到該方法所屬的類型信息和名稱及描述符信息(參數,返回值等)。註意到所有的常量字元串都是存儲在 CONSTANT_Utf8_info 中供其他表索引的。

方法表與方法調用

方法表是動態調用的核心,也是 Java 實現動態調用的主要方式。它被存儲於方法區中的類型信息,包含有該類型所定義的所有方法及指向這些方法代碼的指針,註意這些具體的方法代碼可能是被覆寫的方法,也可能是繼承自基類的方法。

如有類定義 Person, Girl, Boy,

清單 1
 class Person { 
 public String toString(){ 
    return "I'm a person."; 
     } 
 public void eat(){} 
 public void speak(){} 

 } 

 class Boy extends Person{ 
 public String toString(){ 
    return "I'm a boy"; 
     } 
 public void speak(){} 
 public void fight(){} 
 } 

 class Girl extends Person{ 
 public String toString(){ 
    return "I'm a girl"; 
     } 
 public void speak(){} 
 public void sing(){} 
 }

當這三個類被載入到 Java 虛擬機之後,方法區中就包含了各自的類的信息。Girl 和 Boy 在方法區中的方法表可表示如下:

圖 3.Boy 和 Girl 的方法表

圖 3.Boy 和 Girl 的方法表

可以看到,Girl 和 Boy 的方法表包含繼承自 Object 的方法,繼承自直接父類 Person 的方法及各自新定義的方法。註意方法表條目指向的具體的方法地址,如 Girl 的繼承自 Object 的方法中,只有 toString() 指向自己的實現(Girl 的方法代碼),其餘皆指向 Object 的方法代碼;其繼承自於 Person 的方法 eat() 和 speak() 分別指向 Person 的方法實現和本身的實現。

Person 或 Object 的任意一個方法,在它們的方法表和其子類 Girl 和 Boy 的方法表中的位置 (index) 是一樣的。這樣 JVM 在調用實例方法其實只需要指定調用方法表中的第幾個方法即可。

如調用如下:

清單 2

 class Party{ 
…
 void happyHour(){ 
 Person girl = new Girl(); 
 girl.speak(); 
…
     } 
 }

當編譯 Party 類的時候,生成 girl.speak()的方法調用假設為:

Invokevirtual #12

設該調用代碼對應著 girl.speak(); #12 是 Party 類的常量池的索引。JVM 執行該調用指令的過程如下所示:

圖 4. 解析調用過程

圖 4. 解析調用過程

JVM 首先查看 Party 的常量池索引為 12 的條目(應為 CONSTANT_Methodref_info 類型,可視為方法調用的符號引用),進一步查看常量池(CONSTANT_Class_info,CONSTANT_NameAndType_info ,CONSTANT_Utf8_info)可得出要調用的方法是 Person 的 speak 方法(註意引用 girl 是其基類 Person 類型),查看 Person 的方法表,得出 speak 方法在該方法表中的偏移量 15(offset),這就是該方法調用的直接引用。

當解析出方法調用的直接引用後(方法表偏移量 15),JVM 執行真正的方法調用:根據實例方法調用的參數 this 得到具體的對象(即 girl 所指向的位於堆中的對象),據此得到該對象對應的方法表 (Girl 的方法表 ),進而調用方法表中的某個偏移量所指向的方法(Girl 的 speak() 方法的實現)。

介面調用

因為 Java 類是可以同時實現多個介面的,而當用介面引用調用某個方法的時候,情況就有所不同了。Java 允許一個類實現多個介面,從某種意義上來說相當於多繼承,這樣同樣的方法在基類和派生類的方法表的位置就可能不一樣了。

清單 3

interface IDance{ 
   void dance(); 
 } 

 class Person { 
 public String toString(){ 
   return "I'm a person."; 
     } 
 public void eat(){} 
 public void speak(){} 

 } 

 class Dancer extends Person 
 implements IDance { 
 public String toString(){ 
   return "I'm a dancer."; 
     } 
 public void dance(){} 
 } 

 class Snake implements IDance{ 
 public String toString(){ 
   return "A snake."; 
     } 
 public void dance(){ 
 //snake dance 
     } 
 }
圖 5.Dancer 的方法表(查看大圖

圖 5.Dancer 的方法表

可以看到,由於介面的介入,繼承自於介面 IDance 的方法 dance()在類 Dancer 和 Snake 的方法表中的位置已經不一樣了,顯然我們無法通過給出方法表的偏移量來正確調用 Dancer 和 Snake 的這個方法。這也是 Java 中調用介面方法有其專有的調用指令(invokeinterface)的原因。

Java 對於介面方法的調用是採用搜索方法表的方式,對如下的方法調用

invokeinterface #13

JVM 首先查看常量池,確定方法調用的符號引用(名稱、返回值等等),然後利用 this 指向的實例得到該實例的方法表,進而搜索方法表來找到合適的方法地址。

因為每次介面調用都要搜索方法表,所以從效率上來說,介面方法的調用總是慢於類方法的調用的。

執行結果如下:
這裡寫圖片描述
可以看到System.out.println(dancer); 調用的是Person的toString方法。

繼承的實現原理

Java 的繼承機制是一種復用類的技術,從原理上來說,是更好的使用了組合技術,因此要理解繼承,首先需要瞭解類的組合技術是如何實現類的復用的。

使用組合技術復用類
假設現在的需求是要創建一個具有基本類型,String 類型以及一個其他非基本類型的對象。該如何處理呢?

對於基本類型的變數,在新類中成員變數處直接定義即可,但對於非基本類型變數,不僅需要在類中聲明其引用,並且還需要手動初始化這個對象。

這裡需要註意的是,編譯器並不會預設將所有的引用都創建對象,因為這樣的話在很多情況下會增加不必要的負擔,因此,在合適的時機初始化合適的對象,可以通過以下幾個位置做初始化操作:

在定義對象的地方,先於構造方法執行。
在構造方法中。
在正要使用之前,這個被稱為惰性初始化。
使用實例初始化。

class Soap {
    private String s;
    Soap() {
        System.out.println("Soap()");
        s = "Constructed";
    }
    public String tiString(){
        return s;
    }
}

public class Bath {
    // s1 初始化先於構造函數
    private String s1 = "Happy", s2 = "Happy", s3, s4;
    private Soap soap;
    private int i;
    private float f;
    
    public Both() {
        System.out.println("inSide Both");
        s3 = "Joy";
        f = 3.14f;
        soap = new Soap();
    }
    
    {
        i = 88;
    }
    
    public String toString() {
        if(s4 == null){
            s4 = "Joy"
        }
        return "s1 = " + s1 +"\n" +
               "s2 = " + s2 +"\n" +
               "s3 = " + s3 +"\n" +
               "s4 = " + s4 +"\n" +
               "i = " + i +"\n" +
               "f = " + f +"\n" +
               "soap = " + soap;
    }
}

繼承
Java 中的繼承由 extend 關鍵字實現,組合的語法比較平實,而繼承是一種特殊的語法。當一個類繼承自另一個類時,那麼這個類就可以擁有另一個類的域和方法。

class Cleanser{
    private String s = "Cleanser";
    
    public void append(String a){
        s += a;
    }
    public void apply(){
        append("apply");
    }
    public void scrub(){
        append("scrub");
    }
    public String toString(){
        return s;
    }
    public static void main(String args){
        Cleanser c = new Cleanser();
        
        c.apply();
        System.out.println(c);
    }
}

public class Deter extends Cleanser{
    public void apply(){
        append("Deter.apply");
        super.scrub();
    }
    public void foam(){
        append("foam");
    }
    public static void main(String args){
        Deter d = new Deter();
        
        d.apply();
        d.scrub();
        d.foam();
        System.out.println(d);
        Cleanser.main(args);
    }
}

上面的代碼中,展示了繼承語法中的一些特性:

子類可以直接使用父類中公共的方法和成員變數(通常為了保護數據域,成員變數均為私有)
子類中可以覆蓋父類中的方法,也就是子類重寫了父類的方法,此時若還需要調用被覆蓋的父類的方法,則需要用到 super 來指定是調用父類中的方法。
子類中可以自定義父類中沒有的方法。
可以發現上面兩個類中均有 main 方法,命令行中調用的哪個類就執行哪個類的 main 方法,例如:java Deter。
繼承語法的原理
接下來我們將通過創建子類對象來分析繼承語法在我們看不到的地方做了什麼樣的操作。

可以先思考一下,如何理解使用子類創建的對象呢,首先這個對象中包含子類的所有信息,但是也包含父類的所有公共的信息。

下麵來看一段代碼,觀察一下子類在創建對象初始化的時候,會不會用到父類相關的方法。

class Art{
    Art() {
        System.out.println("Art Construct");
    }
}

class Drawing extends Art {
    Drawing() {
        System.out.println("Drawing Construct");
    }
}

public class Cartoon extends Drawing {
    public Cartoon() {
        System.out.println("Cartoon construct");
    }
    public void static main(String args) {
        Cartoon c = new Cartoon();
    }
}
/*output:
Art Construct
Drawing Construct
Cartoon construct
*/

通過觀察代碼可以發現,在實例化Cartoon時,事實上是從最頂層的父類開始向下逐個實例化,也就是最終實例化了三個對象。編譯器會預設在子類的構造方法中增加調用父類預設構造方法的代碼。

因此,繼承可以理解為編譯器幫我們完成了類的特殊組合技術,即在子類中存在一個父類的對象,使得我們可以用子類對象調用父類的方法。而在開發者看來只不過是使用了一個關鍵字。

註意:雖然繼承很接近組合技術,但是繼承擁有其他更多的區別於組合的特性,例如父類的對象我們是不可見的,對於父類中的方法也做了相應的許可權校驗等。

那麼,如果類中的構造方法是帶參的,該如何操作呢?(使用super關鍵字顯示調用)

見代碼:

class Game {
    Game(int i){
        System.out.println("Game Construct");
    }
}

class BoardGame extends Game {
    BoardGame(int j){
        super(j);
        System.out.println("BoardGame Construct");
    }
}
public class Chess extends BoardGame{
    Chess(){
        super(99);
        System.out.println("Chess construct");
    }
    public static void main(String args) {
        Chess c = new Chess();
    }
}
/*output:
Game Construct
BoardGame Construct
Chess construc
*/

重載和重寫的實現原理

    剛開始學習Java的時候,就瞭解了Java這個比較有意思的特性:重寫 和 重載。開始的有時候從名字上還總是容易弄混。我相信熟悉Java這門語言的同學都應該瞭解這兩個特性,可能只是從語言層面上瞭解這種寫法,但是jvm是如何實現他們的呢 ?

重載官方給出的介紹:

一.  overload:
The Java programming language supports overloading methods, and Java can distinguish between methods with different method signatures. This means that methods within a class can have the same name if they have different parameter lists .

Overloaded methods are differentiated by the number and the type of the arguments passed into the method.

You cannot declare more than one method with the same name and the same number and type of arguments, because the compiler cannot tell them apart.

The compiler does not consider return type when differentiating methods, so you cannot declare two methods with the same signature even if they have a different return type.

首先看一段代碼,來看看代碼的執行結果:

public class OverrideTest {
 
    class Father{}
 
    class Sun extends Father {}
 
    public void doSomething(Father father){
        System.out.println("Father do something");
    }
 
    public void doSomething(Sun father){
        System.out.println("Sun do something");
    }
 
    public static void main(String [] args){
        OverrideTest overrideTest = new OverrideTest();
        Father sun = overrideTest.new Sun();
        Father father = overrideTest.new Father();
        overrideTest.doSomething(father);
        overrideTest.doSomething(sun);
    }
}

看下這段代碼的執行結果,最後會列印:

Father do something
Father do something

為什麼會列印出這樣的結果呢? 首先要介紹兩個概念:靜態分派和動態分派

靜態分派:依賴靜態類型來定位方法執行版本的分派動作稱為靜態分派

動態分派:運行期根據實際類型確定方法執行版本的分派過程。

他們的區別是:

1.  靜態分派發生在編譯期,動態分派發生在運行期;

2.  private,static,final 方法發生在編譯期,並且不能被重寫,一旦發生了重寫,將會在運行期處理。

3.  重載是靜態分派,重寫是動態分派

回到上面的問題,因為重載是發生在編譯期,所以在編譯期已經確定兩次 doSomething 方法的參數都是Father類型,在class文件中已經指向了Father類的符號引用,所以最後會列印兩次Father do something。

二. override:
An instance method in a subclass with the same signature (name, plus the number and the type of its parameters) and return type as an instance method in the superclass overrides the superclass's method.

The ability of a subclass to override a method allows a class to inherit from a superclass whose behavior is "close enough" and then to modify behavior as needed. The overriding method has the same name, number and type of parameters, and return type as the method that it overrides. An overriding method can also return a subtype of the type returned by the overridden method. This subtype is called a covariant return type.

還是上面那個代碼,稍微改動下

public class OverrideTest {
 
    class Father{}
 
    class Sun extends Father {}
 
    public void doSomething(){
        System.out.println("Father do something");
    }
 
    public void doSomething(){
        System.out.println("Sun do something");
    }
 
    public static void main(String [] args){
        OverrideTest overrideTest = new OverrideTest();
        Father sun = overrideTest.new Sun();
        Father father = overrideTest.new Father();
        overrideTest.doSomething();
        overrideTest.doSomething();
    }
}


最後會列印:

Father do something

Sun do something

 

相信大家都會知道這個結果,那麼這個結果jvm是怎麼實現的呢?

在編譯期,只會識別到是調用Father類的doSomething方法,到運行期才會真正找到對象的實際類型。

首先該方法的執行,jvm會調用invokevirtual指令,該指令會找棧頂第一個元素所指向的對象的實際類型,如果該類型存在調用的方法,則會走驗證流程,否則繼續找其父類。這也是為什麼子類可以直接調用父類具有訪問許可權的方法的原因。簡而言之,就是在運行期才會去確定對象的實際類型,根據這個實際類型確定方法執行版本,這個過程稱為動態分派。override 的實現依賴jvm的動態分派。

參考文章

https://blog.csdn.net/dj_dengjian/article/details/80811348

https://blog.csdn.net/chenssy/article/details/12757911

https://blog.csdn.net/fan2012huan/article/details/51007517

https://blog.csdn.net/fan2012huan/article/details/50999777

https://www.cnblogs.com/serendipity-fly/p/9469289.html

https://blog.csdn.net/m0_37264516/article/details/86709537

微信公眾號

Java技術江湖

如果大家想要實時關註我更新的文章以及分享的乾貨的話,可以關註我的公眾號【Java技術江湖】一位阿裡 Java 工程師的技術小站,作者黃小斜,專註 Java 相關技術:SSM、SpringBoot、MySQL、分散式、中間件、集群、Linux、網路、多線程,偶爾講點Docker、ELK,同時也分享技術乾貨和學習經驗,致力於Java全棧開發!

Java工程師必備學習資源: 一些Java工程師常用學習資源,關註公眾號後,後臺回覆關鍵字 “Java” 即可免費無套路獲取。

我的公眾號

個人公眾號:黃小斜

作者是跨考軟體工程的 985 碩士,自學 Java 兩年,拿到了 BAT 等近十家大廠 offer,從技術小白成長為阿裡工程師。

作者專註於 JAVA 後端技術棧,熱衷於分享程式員乾貨、學習經驗、求職心得和程式人生,目前黃小斜的CSDN博客有百萬+訪問量,知乎粉絲2W+,全網已有10W+讀者。

黃小斜是一個斜杠青年,堅持學習和寫作,相信終身學習的力量,希望和更多的程式員交朋友,一起進步和成長!

關註公眾號【黃小斜】後回覆【原創電子書】即可領取我原創的電子書《菜鳥程式員修煉手冊:從技術小白到阿裡巴巴Java工程師》

程式員3T技術學習資源: 一些程式員學習技術的資源大禮包,關註公眾號後,後臺回覆關鍵字 “資料” 即可免費無套路獲取。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 中介者模式(Mediator): 在現實生活中,有很多中介者模式的身影,例如QQ游戲平臺,聊天室、QQ群、簡訊平臺和房產中介。不論是QQ游戲還是QQ群,它們都是充當一個中間平臺,QQ用戶可以登錄這個中間平臺與其他QQ用戶進行交流,如果沒有這些中間平臺,我們如果想與朋友進行聊天的話,可能就需要當面才可 ...
  • 一、多線程調試斷點 的 斷點調試是有一個模式的選擇的,就像下麵這張圖,平時我們都使用的是預設的 (在 中預設是線程模式) ,這種模式我們只能將一個線程斷下來,但其他線程卻已經執行過了;而將其改為 後,就可以多個線程都斷下來,並且可以很方便的切換線程的執行流程,這就是多線程調試。 在 控制台能夠很方便 ...
  • 產品定位 騰訊bugly和fabric不僅僅是可以幫助運營人員分析用戶、優化推廣的數據分析平臺,也是移動開發者的異常上報平臺和應用更新平臺。可以同時為公司產品運營和開發人員提供服務。 產品功能 fabric在查看dashboard頁面之前必須在應用里集成SDK,所以這裡只對fabric官網上給出的功 ...
  • Java的日期類Date Date類 註意:是 java.util.Date ; 而非 java.sql.Date,此類是給資料庫訪問的時候使用的 示例 1 : 時間原點概念 所有的數據類型,無論是整數,布爾,浮點數還是字元串,最後都需要以數字的形式表現出來。 日期類型也不例外,換句話說,一個日期, ...
  • 這是 Java 網路爬蟲系列博文的第二篇,在上一篇 "Java 網路爬蟲,就是這麼的簡單" 中,我們簡單的學習了一下如何利用 Java 進行網路爬蟲。在這一篇中我們將簡單的聊一聊在網路爬蟲時,遇到需要登錄的網站,我們該怎麼辦? 在做爬蟲時,遇到需要登陸的問題也比較常見,比如寫腳本搶票之類的,但凡需要 ...
  • 一、楔子 你現在已經學會了寫python代碼,假如你寫了兩個python文件a.py和b.py,分別去運行,你就會發現,這兩個python的文件分別運行的很好。但是如果這兩個程式之間想要傳遞一個數據,你要怎麼做呢? 這個問題以你現在的知識就可以解決了,我們可以創建一個文件,把a.py想要傳遞的內容寫 ...
  • ① 向資料庫發送SQL查詢語句 首先使用Statement聲明一個SQL語句對象,然後讓已創建的連接對象con調用方法createStatement()創建SQL語句對象。 Statement sql = con.createStatement(); Connection con = null; S ...
  • 一、主方法註意 每一個類都可以編寫一個主方法,但是一般情況下,一個系統只有一個入口,所以主方法一般寫一個 二、Myeclipse的使用 1.在workspace中工作區中有一個文件夾.metadata:在該文件夾中存儲了當前MyEclipse的工作狀態 2.將.metadata文件夾刪除之後,下一次 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 插件化的需求主要源於對軟體架構靈活性的追求,特別是在開發大型、複雜或需要不斷更新的軟體系統時,插件化可以提高軟體系統的可擴展性、可定製性、隔離性、安全性、可維護性、模塊化、易於升級和更新以及支持第三方開發等方面的能力,從而滿足不斷變化的業務需求和技術挑戰。 一、插件化探索 在WPF中我們想要開 ...
  • 歡迎ReaLTaiizor是一個用戶友好的、以設計為中心的.NET WinForms項目控制項庫,包含廣泛的組件。您可以使用不同的主題選項對項目進行個性化設置,並自定義用戶控制項,以使您的應用程式更加專業。 項目地址:https://github.com/Taiizor/ReaLTaiizor 步驟1: ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • Channel 是乾什麼的 The System.Threading.Channels namespace provides a set of synchronization data structures for passing data between producers and consume ...
  • efcore如何優雅的實現按年分庫按月分表 介紹 本文ShardinfCore版本 本期主角: ShardingCore 一款ef-core下高性能、輕量級針對分表分庫讀寫分離的解決方案,具有零依賴、零學習成本、零業務代碼入侵適配 距離上次發文.net相關的已經有很久了,期間一直在從事java相關的 ...
  • 前言 Spacesniffer 是一個免費的文件掃描工具,通過使用樹狀圖可視化佈局,可以立即瞭解大文件夾的位置,幫助用戶處理找到這些文件夾 當前系統C盤空間 清理後系統C盤空間 下載 Spacesniffer 下載地址:https://spacesniffer.en.softonic.com/dow ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • 一、ReZero簡介 ReZero是一款.NET中間件 : 全網唯一開源界面操作就能生成API , 可以集成到任何.NET6+ API項目,無破壞性,也可讓非.NET用戶使用exe文件 免費開源:MIT最寬鬆協議 , 一直從事開源事業十年,一直堅持開源 1.1 純ReZero開發 適合.Net Co ...
  • 一:背景 1. 講故事 停了一個月沒有更新文章了,主要是忙於寫 C#內功修煉系列的PPT,現在基本上接近尾聲,可以回頭繼續更新這段時間分析dump的一些事故報告,有朋友微信上找到我,說他們的系統出現了大量的http超時,程式不響應處理了,讓我幫忙看下怎麼回事,dump也抓到了。 二:WinDbg分析 ...
  • 開始做項目管理了(本人3年java,來到這邊之後真沒想到...),天天開會溝通整理需求,他們講話的時候忙裡偷閑整理一下常用的方法,其實語言還是有共通性的,基本上看到方法名就大概能猜出來用法。出去打水的時候看到外面太陽好好,真想在外面坐著曬太陽,回來的時候好兄弟三年前送給我的鍵盤D鍵不靈了,在打"等待 ...