note for java

java常見面試彙總

某大佬總結的java面試重點

鏈接:https://www.nowcoder.com/discuss/123525

  • Java基礎(例如各種集合類,Java的特性,重寫重載區別,equals和 hashcode的聯繫等等)
  • 數據庫(主要是MySQL,最常問的例如索引,事務隔離級別等等)
  • Spring(IOC,AOP的基礎和底層例如cglib,jdk proxy等)
  • 計網和***作系統(這個有好多,但是重點的也就那些,比如三次握手四 次揮手,死鎖,進程線程區別,線程的生命週期等)
  • 併發、JVM、線程池(把深入理解JVM的第2、3、7、最後兩章看會感覺就足夠 了,還有併發編程的藝術(這本沒全看–,但是感覺面試經常問,比如原子 類,併發包工具等)
  • 算法(面試個人感覺就是劍指offer的難度,遇到了好多原題,但是筆試 就得會貪心動態規劃各種了雖然我不怎麼會尷尬)
  • Redis、微服務(這個如果你簡歷上寫了,面試官纔會問,沒寫一般不 問)

java中數據類型分爲基本數據類型和引用數據類型。

基本數據類型

整型:byte,short,int,long
浮點型:float,double
字符型:char
布爾型:boolean

引用數據類型

數組

接口

Java程序設計語言對對象採用的不是引用調用,實際上,對象引用是按 值傳遞的

在 Java 中定義一個不做事且沒有參數的構造方法的作用

Java 程序在執行子類的構造方法之前,如果沒有用 super()來調用父類特 定的構造方法,則會調用父類中“沒有參數的構造方法”。因此,如果父類中 只定義了有參數的構造方法,而在子類的構造方法中又沒有用 super()來 調用父類中特定的構造方法,則編譯時將發生錯誤,因爲 Java 程序在父類 中找不到沒有參數的構造方法可供執行。解決辦法是在父類里加上一個不做 事且沒有參數的構造方法。

創建一個對象用什麼運算符?對象實體與對象引用有何不同?

new運算符,new創建對象實例(對象實例在堆內存中),對象引用指向對象 實例(對象引用存放在棧內存中)。一個對象引用可以指向0個或1個對象 (一根繩子可以不繫氣球,也可以系一個氣球);一個對象可以有n個引用指 向它(可以用n條繩子繫住一個氣球)。

關於 final 關鍵字的一些總結

final關鍵字主要用在三個地方:變量、方法、類。
對於一個final變量,如果是基本數據類型的變量,則其數值一旦在初始化 之後便不能更改;如果是引用類型的變量,則在對其初始化之後便不能再讓 其指向另一個對象。
當用final修飾一個類時,表明這個類不能被繼承。final類中的所有成員方 法都會被隱式地指定爲final方法。
使用final方法的原因有兩個。第一個原因是把方法鎖定,以防任何繼承類修 改它的含義;第二個原因是效率。在早期的Java實現版本中,會將final方 法轉爲內嵌調用。但是如果方法過於龐大,可能看不到內嵌調用帶來的任何 性能提升(現在的Java版本已經不需要使用final方法進行這些優化了)。 類中所有的private方法都隱式地指定爲final。

爲什麼TCP客戶端最後還要發送一次確認呢?

一句話,主要防止已經失效的連接請求報文突然又傳送到了服務器,從而產生錯誤。
如果使用的是兩次握手建立連接,假設有這樣一種場景,客戶端發送了第一 個請求連接並且沒有丟失,只是因爲在網絡結點中滯留的時間太長了,由於 TCP的客戶端遲遲沒有收到確認報文,以爲服務器沒有收到,此時重新向服 務器發送這條報文,此後客戶端和服務器經過兩次握手完成連接,傳輸數 據,然後關閉連接。此時此前滯留的那一次請求連接,網絡通暢了到達了服 務器,這個報文本該是失效的,但是,兩次握手的機制將會讓客戶端和服務 器再次建立連接,這將導致不必要的錯誤和資源的浪費。
如果採用的是三次握手,就算是那一次失效的報文傳送過來了,服務端接受 到了那條失效報文並且回覆了確認報文,但是客戶端不會再次發出確認。由 於服務器收不到確認,就知道客戶端並沒有請求連接。
三次握手

爲什麼在四次揮手客戶端最後還要等待2MSL?

MSL(Maximum Segment Lifetime),TCP允許不同的實現可以設置不同 的MSL值。
第一,保證客戶端發送的最後一個ACK報文能夠到達服務器,因爲這個ACK報 文可能丟失,站在服務器的角度看來,我已經發送了FIN+ACK報文請求斷開 了,客戶端還沒有給我回應,應該是我發送的請求斷開報文它沒有收到,於 是服務器又會重新發送一次,而客戶端就能在這個2MSL時間段內收到這個重 傳的報文,接着給出迴應報文,並且會重啓2MSL計時器。
第二,防止類似與“三次握手”中提到了的“已經失效的連接請求報文段”出現 在本連接中。客戶端發送完最後一個確認報文後,在這個2MSL時間中,就可 以使本連接持續的時間內所產生的所有報文段都從網絡中消失。這樣新的連 接中不會出現舊連接的請求報文。

爲什麼建立連接是三次握手,關閉連接確是四次揮手呢?

建立連接的時候, 服務器在LISTEN狀態下,收到建立連接請求的SYN報文 後,把ACK和SYN放在一個報文裏發送給客戶端。
而關閉連接時,服務器收到對方的FIN報文時,僅僅表示對方不再發送數據了但是還能接收數據,而自己也未必全部數據都發送給對方了,所以己方可以立即關閉,也可以發送一些數據給對方後,再發送FIN報文給對方來表示同意現在關閉連接,因此,己方ACK和FIN一般都會分開發送,從而導致多了一次。

四 TCP 協議如何保證可靠傳輸

  • 應用數據被分割成 TCP 認爲最適合發送的數據塊。
  • TCP 給發送的每一個包進行編號,接收方對數據包進行排序,把有序數據傳 送給應用層。
  • 校驗和: TCP 將保持它首部和數據的檢驗和。這是一個端到端的檢驗和,目的是檢測數據在傳輸過程中的任何變化。如果收到段的檢驗和有差錯,TCP將丟棄這個報文段和不確認收到此報文段。
  • TCP的接收端會丟棄重複的數據。
  • 流量控制: TCP 連接的每一方都有固定大小的緩衝空間,TCP的接收端只 允許發送端發送接收端緩衝區能接納的數據。當接收方來不及處理髮送方的 數據,能提示發送方降低發送的速率,防止包丟失。TCP 使用的流量控制協 議是可變大小的滑動窗口協議。(TCP 利用滑動窗口實現流量控制)
  • 擁塞控制: 當網絡擁塞時,減少數據的發送。
  • ARQ協議: 也是爲了實現可靠傳輸的,它的基本原理就是每發完一個分組就 停止發送,等待對方確認。在收到確認後再發下一個分組。
  • 超時重傳: 當 TCP 發出一個段後,它啓動一個定時器,等待目的端確認收 到這個報文段。如果不能及時收到一個確認,將重發這個報文段。
    三次握手

五.接口與抽象的異同

1.抽象類

抽象類是關鍵字abstract修飾的類,既爲抽象類,抽象抽象即不能被實例化。而不能被實例化就無用處,所以抽象類只能作爲基類(父類),即被繼承的類。抽象類中可以包含抽象方法也可以不包含,但具有抽象方法的類一定是抽象類。
  抽象類的使用原則如下:

(1)被繼承性:抽象方法必須爲public或者protected(因爲如果爲private,則不能被子類繼承,子類便無法實現該方法),缺省情況下默認爲public;
(2)抽象性:抽象類不能直接實例化,需要依靠子類採用向上轉型的方式處理;
(3)抽象類必須有子類,使用extends繼承,一個子類只能繼承一個抽象類;
(4)子類(如果不是抽象類)則必須覆寫抽象類之中的全部抽象方法(如果子類沒有實現父類的抽象方法,則必須將子類也定義爲爲abstract類。

1、接口與類相似點

一個接口可以有多個方法。
接口文件保存在 .java 結尾的文件中,文件名使用接口名。
接口的字節碼文件保存在 .class 結尾的文件中。
接口相應的字節碼文件必須在與包名稱相匹配的目錄結構中。

2、接口與類的區別

接口不能用於實例化對象。
接口沒有構造方法。
接口中所有的方法必須是抽象方法。
接口不能包含成員變量,除了 static 和 final 變量。
接口不是被類繼承了,而是要被類實現。
接口支持多繼承。

3、接口特性

接口中每一個方法也是隱式抽象的,接口中的方法會被隱式的指定爲 public abstract(只能是 public abstract,其他修飾符都會報錯)。
接口中可以含有變量,但是接口中的變量會被隱式的指定爲 public static final 變量(並且只能是 public,用 private 修飾會報編譯錯誤)。
接口中的方法是不能在接口中實現的,只能由實現接口的類來實現接口中的方法。
[外鏈圖片轉存失敗(img-frQfVMLv-1565529618679)(https://i.imgur.com/28uQkav.png)]
[外鏈圖片轉存失敗(img-rSHjvOTi-1565529618692)(https://i.imgur.com/mxRbvpg.png)]

六.static關鍵字

  • static關鍵字並不會改變變量和方法的訪問權限。 與C/C++中的static不同,Java中的static關鍵字不會影響到變量或者方法的作用域。在Java中能夠影響到訪問權限的只有private、public、protected(包括包訪問權限)這幾個關鍵字。
  • 爲什麼說static塊可以用來優化程序性能,是因爲它的特性:只會在類加載的時候執行一次。 很多時候會將一些只需要進行一次的初始化操作都放在static代碼塊中進行
  • static成員變量的初始化順序按照定義的順序進行初始化。
  • 即使沒有顯示地聲明爲static,類的構造器實際上也是靜態方法。
  • static方法一般稱作靜態方法,由於靜態方法不依賴於任何對象就可以進行訪問,因此對於靜態方法來說,是沒有this的,因爲它不依附於任何對象,既然都沒有對象,就談不上this了。並且由於這個特性,在靜態方法中不能訪問類的非靜態成員變量和非靜態成員方法,因爲非靜態成員方法/變量都是必須依賴具體的對象才能夠被調用。
    • static代碼塊也叫靜態代碼塊,是在類中獨立於類成員的static語句塊,可以有多個,位置可以隨便放,它不在任何的方法體內,JVM加載類時會執行這些靜態的代碼塊,如果static代碼塊有多個,JVM將按照它們在類中出現的先後順序依次執行它們,每個代碼塊只會被執行一次。
  • 普通類是不允許聲明爲靜態的,只有內部類纔可以。

被static修飾的內部類可以直接作爲一個普通類來使用,而不需實例一個外部類

  • static能作用於局部變量麼?

在C/C++中static是可以作用域局部變量的,但是在Java中切記:static是不允許用來修飾局部變量。不要問爲什麼,這是Java語法的規定。

  • 常見的筆試面試題
  1. public class Test extends Base{

    static{ System.out.println("test static"); }

    public Test(){ System.out.println("test constructor"); }
    public static void main(String[] args) { new Test(); } }

class Base{

static{
    System.out.println("base static");
}
 
public Base(){
    System.out.println("base constructor");
}

}
輸出:
base static test static base constructor test constructor
先來想一下這段代碼具體的執行過程,在執行開始,先要尋找到main方法,因爲main方法是程序的入口,但是在執行main方法之前,必須先加載Test類,而在加載Test類的時候發現Test類繼承自Base類,因此會轉去先加載Base類,在加載Base類的時候,發現有static塊,便執行了static塊。在Base類加載完成之後,便繼續加載Test類,然後發現Test類中也有static塊,便執行static塊。在加載完所需的類之後,便開始執行main方法。在main方法中執行new Test()的時候會先調用父類的構造器,然後再調用自身的構造器。因此,便出現了上面的輸出結果。

  • static塊可以出現類中的任何地方(只要不是方法內部,記住,任何方法內部都不行),並且執行是按照static塊的順序執行的。

  • 這段代碼的輸出結果是什麼?
    public class Test { Person person = new Person("Test");
    static{ System.out.println("test static"); }

    public Test() {
    System.out.println("test constructor");

    } `

public static void main(String[] args) { new MyClass(); }
}

class Person{
static{ System.out.println("person static"); }
public Person(String str) {
System.out.println("person "+str); }
}

class MyClass extends Test {
Person person = new Person("MyClass");
static{ System.out.println("myclass static"); }

public MyClass() {  
    System.out.println("myclass constructor");  
}  

}
輸出:

test static 
 myclass static 
 person static  
 person Test
 test constructor
 person MyClass
 myclass constructor
思路:

首先加載Test類,因此會執行Test類中的static塊。接着執行new MyClass(),而MyClass類還沒有被加載,因此需要加載MyClass類。在加載MyClass類的時候,發現MyClass類繼承自Test類,但是由於Test類已經被加載了,所以只需要加載MyClass類,那麼就會執行MyClass類的中的static塊。在加載完之後,就通過構造器來生成對象。而在生成對象的時候,必須先初始化父類的成員變量,因此會執行Test中的Person person = new Person(),而Person類還沒有被加載過,因此會先加載Person類並執行Person類中的static塊,接着執行父類的構造器,完成了父類的初始化,然後就來初始化自身了,因此會接着執行MyClass中的Person person = new Person(),最後執行MyClass的構造器。

七.final

  • 根據程序上下文環境,Java關鍵字final有“這是無法改變的”或者“終態的”含義,它可以修飾非抽象類、非抽象類成員方法和變量。你可能出於兩種理解而需要阻止改變:設計或效率。
    final類不能被繼承,沒有子類,final類中的方法默認是final的。
    final方法不能被子類的方法覆蓋,但可以被繼承。
    final成員變量表示常量,只能被賦值一次,賦值後值不再改變。
    final不能用於修飾構造方法。
    注意:父類的private成員方法是不能被子類方法覆蓋的,因此private類型的方法默認是final類型的。

  • final類不能被繼承,因此final類的成員方法沒有機會被覆蓋,默認都是final的。在設計類時候,如果這個類不需要有子類,類的實現細節不允許改變,並且確信這個類不會載被擴展,那麼就設計爲final類。

  • final方法
    如果一個類不允許其子類覆蓋某個方法,則可以把這個方法聲明爲final方法。
    使用final方法的原因有二:
    第一、把方法鎖定,防止任何繼承類修改它的意義和實現。
    第二、高效。編譯器在遇到調用final方法時候會轉入內嵌機制,大大提高執行效率。

  • final變量(常量)
    用final修飾的成員變量表示常量,值一旦給定就無法改變!
    final修飾的變量有三種:靜態變量、實例變量和局部變量,分別表示三種類型的常量。
    從下面的例子中可以看出,一旦給final變量初值後,值就不能再改變了。
    另外,final變量定義的時候,可以先聲明,而不給初值,這中變量也稱爲final空白,無論什麼情況,編譯器都確保空白final在使用之前必須被初始化。但是,final空白在final關鍵字final的使用上提供了更大的靈活性,爲此,一個類中的final數據成員就可以實現依對象而有所不同,卻有保持其恆定不變的特徵。

  • static和final一塊用表示什麼
    static final用來修飾成員變量和成員方法,可簡單理解爲“全局常量”!
    對於變量,表示一旦給值就不可修改,並且通過類名可以訪問。
    對於方法,表示不可覆蓋,並且可以通過類名直接訪問。

      ** 特別要注意一個問題: **
      對於被static和final修飾過的實例常量,實例本身不能再改變了,但**對於一些容器類型(比如,ArrayList、HashMap)的實例變量,不可以改變容器變量本身,但可以修改容器中存放的對象,**這一點在編程中用到很多。
    

八.Java關鍵字this、super使用總結

一、this

Java關鍵字this只能用於方法方法體內。當一個對象創建後,
Java虛擬機(JVM)就會給這個對象分配一個引用自身的指針,這個指針的名字就是this。因此,this只能在類中的非靜態方法中使用,靜態方法和靜態的代碼塊中絕對不能出現this,這在“Java關鍵字static、final使用總結”一文中給出了明確解釋。並且this只和特定的對象關聯,而不和類關聯,同一個類的不同對象有不同的this。

  • 在什麼情況下需要用到this:
    第一、通過this調用另一個構造方法,用發是this(參數列表),這個僅僅在類的構造方法中,別的地方不能這麼用。
    第二、函數參數或者函數中的局部變量和成員變量同名的情況下,成員變量被屏蔽,此時要訪問成員變量則需要用“this.成員變量名”的方式來引用成員變量。當然,在沒有同名的情況下,可以直接用成員變量的名字,而不用this,用了也不爲錯,呵呵。
    第三、在函數中,需要引用該函所屬類的當前對象時候,直接用this。

二、super

    super關鍵和this作用類似,是被屏蔽的成員變量或者成員方法  或變爲可見,或者說用來引用被屏蔽的成員變量和成員成員方法。

不過super是用在子類中,目的是訪問直接父類中被屏蔽的成員,注意是直接父類(就是類之上最近的超類)。

  • 總結一下super的用法:
    第一、在子類構造方法中要調用父類的構造方法,用“super(參數列表)”的方式調用,參數不是必須的。同時還要注意的一點是:“super(參數列表)”這條語句只能用在子類構造方法體中的第一行。
    第二、當子類方法中的局部變量或者子類的成員變量與父類成員變量同名時,也就是子類局部變量覆蓋父類成員變量時,用“super.成員變量名”來引用父類成員變量。當然,如果父類的成員變量沒有被覆蓋,也可以用“super.成員變量名”來引用父類成員變量,不過這是不必要的。
    第三、當子類的成員方法覆蓋了父類的成員方法時,也就是子類和父類有完全相同的方法定義(但方法體可以不同),此時,用“super.方法名(參數列表)”的方式訪問父類的方法。

九.java中的匿名內部類總結

匿名內部類也就是沒有名字的內部類

正因爲沒有名字,所以匿名內部類只能使用一次,它通常用來簡化代碼編寫

但使用匿名內部類還有個前提條件:必須繼承一個父類或實現一個接口

匿名內部類的基本實現

abstract class Person {

public abstract void eat();
}
public class Demo {

public static void main(String[] args) {

    Person p = new Person() {

        public void eat() {

            System.out.println("eat something");

        }

    };

    p.eat();

}}  

可以看到,我們直接將抽象類Person中的方法在大括號中實現了

這樣便可以省略一個類的書寫

並且,匿名內部類還能用於接口上

在接口上使用匿名內部類

interface Person {

public void eat();}
public class Demo {

public static void main(String[] args) {

    Person p = new Person() {

        public void eat() {

            System.out.println("eat something");

        }

    };

    p.eat();

}}  

由上面的例子可以看出,只要一個類是抽象的或是一個接口,那麼其子類中的方法都可以使用匿名內部類來實現

最常用的情況就是在多線程的實現上,因爲要實現多線程必須繼承Thread類或是繼承Runnable接口

Thread類的匿名內部類實現

public class Demo {

public static void main(String[] args) {

    Thread t = new Thread() {

        public void run() {

            for (int i = 1; i <= 5; i++) {

                System.out.print(i + " ");

            }

        }

    };

    t.start();

}}  

Runnable接口的匿名內部類實現

public class Demo {

public static void main(String[] args) {

    Runnable r = new Runnable() {

        public void run() {

            for (int i = 1; i <= 5; i++) {

                System.out.print(i + " ");

            }

        }

    };

    Thread t = new Thread(r);

    t.start();

}}  

垃圾回收算法原理

轉載自:https://blog.csdn.net/FateRuler/article/details/81158510

第一種:標記清除

它是最基礎的收集算法。
原理:分爲標記和清除兩個階段:首先標記出所有的需要回收的對象,在標記完成以後統一回收所有被標記的對象。
特點:(1)效率問題,標記和清除的效率都不高;(2)空間的問題,標記清除以後會產生大量不連續的空間碎片,空間碎片太多可能會導致程序運行過程需要分配較大的對象時候,無法找到足夠連續內存而不得不提前觸發一次垃圾收集。
地方 :適合在老年代進行垃圾回收,比如CMS收集器就是採用該算法進行回收的。

第二種:標記整理

原理:分爲標記和整理兩個階段:首先標記出所有需要回收的對象,讓所有存活的對象都向一端移動,然後直接清理掉端邊界以外的內存。
特點:不會產生空間碎片,但是整理會花一定的時間。
地方:適合老年代進行垃圾收集,parallel Old(針對parallel scanvange gc的) gc和Serial old收集器就是採用該算法進行回收的。

第三種:複製算法

原理:它先將可用的內存按容量劃分爲大小相同的兩塊,每次只是用其中的一塊。當這塊內存用完了,就將還存活着的對象複製到另一塊上面,然後把已經使用過的內存空間一次清理掉。
特點:沒有內存碎片,只要移動堆頂指針,按順序分配內存即可。代價是將內存縮小位原來的一半。
地方:適合新生代區進行垃圾回收。serial new,parallel new和parallel scanvage
收集器,就是採用該算法進行回收的。

複製算法改進思路:

由於新生代都是朝生夕死的,所以不需要1:1劃分內存空間,可以將內存劃分爲一塊較大的Eden和兩塊較小的Suvivor空間。每次使用Eden和其中一塊Survivor。當回收的時候,將Eden和Survivor中還活着的對象一次性地複製到另一塊Survivor空間上,最後清理掉Eden和剛纔使用過的Suevivor空間。其中Eden和Suevivor的大小比例是8:1。缺點是需要老年代進行分配擔保,如果第二塊的Survovor空間不夠的時候,需要對老年代進行垃圾回收,然後存儲新生代的對象,這些新生代當然會直接進入來老年代。

優化收集方法的思路
分代收集算法
原理:根據對象存活的週期的不同將內存劃分爲幾塊,然後再選擇合適的收集算法。
一般是把java堆分成新生代和老年代,這樣就可以根據各個年待的特點採用最適合的收集算法。在新生代中,每次垃圾收集都會有大量的對象死去,只有少量存活,所以選用複製算法。老年代因爲對象存活率高,沒有額外空間對他進行分配擔保,所以一般採用標記整理或者標記清除算法進行回收。

對於以上兩種標記算法存在爭議,在深入瞭解JVM最佳實踐第二版中,是寫的標記需要回收的對象,我也沒太深入思考,直到有人提出來,我也去查了一下和想了一下。我個人現在偏向,標記存活的對象。
標記算法的大概流程:通過引用鏈給所有存活的對象做個標記,然後回收所有沒有標記的對象 和 清除存活對象的標記,等待下一次GC

GC用的引用可達性分析算法中,哪些對象可作爲GC Roots對象

先說一下可達性分析算法的思想:從一個被稱爲GC Roots的對象開始向下搜索,如果一個對象到GC Roots沒有任何引用鏈相連時,則說明此對象不可用。

在java中可以作爲GC Roots的對象有以下幾種:

轉載自:https://blog.csdn.net/ma345787383/article/details/77099522

虛擬機棧中引用的對象、方法區類靜態屬性引用的對象、方法區常量池引用的對象、本地方法棧JNI引用的對象
雖然這些算法可以判定一個對象是否能被回收,但是當滿足上述條件時,一個對象 **不一定會被回收。**當一個對象不可達GC Roots時,這個對象並不會馬上被回收,而是處於一個死緩的階段,**若要被真正的回收需要經歷兩次標記。**如果對象在可達性分析中沒有與GC Roots的引用鏈,那麼此時就會被第一次標記並且進行一次篩選,篩選的條件是是否有必要執行finalize()方法。當對象沒有覆蓋finalize()方法或者已經被虛擬機調用過,那麼就認爲是沒必要的。
如果該對象有必要執行finalize()方法,那麼這個對象將會放在一個稱爲F-Queue的隊列中,虛擬機會觸發一個finalize()線程去執行,此線程是低優先級的,並且虛擬機不會承諾一直等待它運行完,這還是因爲如果finalize()執行緩慢或者發生了死鎖,那麼就會造成F-Queue隊列一直等待,造成了內存回收系統的崩潰。GC對處於F-Queue中的對象進行第二次被標記,這時,該對象將被移除“即將回收”集合,等待回收。

hashmap中key是否可以爲null

1、 HashMap計算key的hash值時調用單獨的方法,在該方法中會判斷key是否爲null,如果是則返回0;而Hashtable中則直接調用key的hashCode()方法,因此如果key爲null,則拋出空指針異常。

2、 HashMap將鍵值對添加進數組時,不會主動判斷value是否爲null;而Hashtable則首先判斷value是否爲null。

3、以上原因主要是由於Hashtable繼承自Dictionary,而HashMap繼承自AbstractMap。

4、雖然ConcurrentHashMap也繼承自AbstractMap,但是其也過濾掉了key或value爲null的鍵值對。

  • JDK1.8 之前 HashMap 底層是 數組和鏈表 結合在一起使用也就是 鏈表散列。HashMap 通過 key 的 hashCode 經過擾動函數處理過後得到 hash 值,然後通過**(n - 1) & hash** 判斷當前元素存放的位置(這裏的 n 指的是數組的長度),如果當前位置存在元素的話,就判斷該元素與要存入的元素的 hash 值以及 key 是否相同,如果相同的話,直接覆蓋,不相同就通過拉鍊法解決衝突。

所謂擾動函數指的就是 HashMap 的 hash 方法。使用 hash 方法也就是擾動函數是爲了防止一些實現比較差的 hashCode() 方法 換句話說使用擾動函數之後可以減少碰撞。

十.基本數據類型和對象的區別

(1) 基本數據類型的存儲原理:所有的簡單數據類型不存在“引用”的概念,基本數據類型都是直接存儲在內存中的棧上的,數據本身的值就是存儲在棧空間裏面,Java語言裏面八種數據類型是這種存儲模型;

(2) 引用類型的存儲原理:引用類型繼承於Object類(也是引用類型)都是按照Java裏面存儲對象的內存模型來進行數據存儲的,使用Java堆和棧來進行這種類型的數據存儲,簡單地講,“引用”(存儲對象在內存堆上的地址)是存儲在有序的棧上的,而對象本身的值存儲在堆上的;

不論是基本數據類型還是引用類型,他們都會先在棧中分配一塊內存,對於基本類型來說,這塊區域包含的是基本類型的內容;而對於引用類型來說,這塊區域包含的是指向真正內容的指針,真正的內容被手動的分配在堆上。

Synchronized的使用

synchronized的三種應用方式

Java中每一個對象都可以作爲鎖,這是synchronized實現同步的基礎:

  1. 普通同步方法(實例方法),鎖是當前實例對象 ,進入同步代碼前要獲得當前實例的鎖
  2. 靜態同步方法,鎖是當前類的class對象 ,進入同步代碼前要獲得當前類對象的鎖
  3. 同步方法塊,鎖是括號裏面的對象,對給定對象加鎖,進入同步代碼庫前要獲得給定對象的鎖。

一個類中存在一個synchronized修飾的方法和一個普通的方法,不同線程同時訪問這兩個方法,會出現什麼情況?

  • 一個線程持有對象鎖,另一個線程可以以異步的方式調用對象裏面的非synchronized方法,輸出結果是不按照順序的

  • 一個線程持有對象鎖,另一個線程可以以同步的方式調用對象裏面的synchronized方法,需要等待上一個線程釋放資源,也就是同步。

  • 兩個線程訪問不同對象中不同的synchronized方法不會受到synchronized的限制

Java常見的幾種內存溢出及解決方案

  • 1.JVM Heap(堆)溢出:java.lang.OutOfMemoryError: Java heap space

    JVM在啓動的時候會自動設置JVM Heap的值, 可以利用JVM提供的-Xmn -Xms -Xmx等選項可進行設置。Heap的大小是Young Generation 和Tenured Generaion 之和。在JVM中如果98%的時間是用於GC,且可用的Heap size 不足2%的時候將拋出此異常信息。
    解決方法:手動設置JVM Heap(堆)的大小。
    Java堆用於儲存對象實例。當需要爲對象實例分配內存,而堆的內存佔用又已經達到-Xmx設置的最大值。將會拋出OutOfMemoryError異常。

  • 2.PermGen space溢出: java.lang.OutOfMemoryError: PermGen space
    PermGen space的全稱是Permanent Generation space,是指內存的永久保存區域。爲什麼會內存溢出,這是由於這塊內存主要是被JVM存放Class和Meta信息的,Class在被Load的時候被放入PermGen space區域,它和存放Instance的Heap區域不同,sun的 GC不會在主程序運行期對PermGen space進行清理,所以如果你的APP會載入很多CLASS的話,就很可能出現PermGen space溢出。一般發生在程序的啓動階段。
    解決方法: 通過-XX:PermSize和-XX:MaxPermSize設置永久代大小即可。
    方法區用於存放java類型的相關信息,如類名、訪問修飾符、常量池、字段描述、方法描述等。在類裝載器加載class文件到內存的過程中,虛擬機會提取其中的類型信息,並將這些信息存儲到方法區。當需要存儲類信息而方法區的內存佔用又已經達到-XX:MaxPermSize設置的最大值,將會拋出OutOfMemoryError異常。

  • 3.棧溢出: java.lang.StackOverflowError : Thread Stack space
    棧溢出了,JVM依然是採用棧式的虛擬機,這個和C和Pascal都是一樣的。函數的調用過程都體現在堆棧和退棧上了。調用構造函數的 “層”太多了,以致於把棧區溢出了。 通常來講,一般棧區遠遠小於堆區的,因爲函數調用過程往往不會多於上千層,而即便每個函數調用需要 1K的空間(這個大約相當於在一個C函數內聲明瞭256個int類型的變量),那麼棧區也不過是需要1MB的空間。通常棧的大小是1-2MB的。通俗一點講就是單線程的程序需要的內存太大了。 通常遞歸也不要遞歸的層次過多,很容易溢出。
    解決方法:1:修改程序。2:通過 -Xss: 來設置每個線程的Stack大小即可。
    在Java虛擬機規範中,對這個區域規定了兩種異常狀況:StackOverflowError和OutOfMemoryError異常。

  • (1)StackOverflowError異常

     每當java程序代碼啓動一個新線程時,Java虛擬機都會爲它分配一個Java棧。Java棧以幀爲單位保存線程的運行狀態。當線程調用java方法時,虛擬機壓入一個新的棧幀到該線程的java棧中。只要這個方法還沒有返回,它就一直存在。如果線程的方法嵌套調用層次太多(如遞歸調用),隨着java棧中幀的逐漸增多,最終會由於該線程java棧中所有棧幀大小總和大於-Xss設置的值,而產生StackOverflowError內存溢出異常。  
    

    (2)OutOfMemoryError異常
    java程序代碼啓動一個新線程時,沒有足夠的內存空間爲該線程分配java 棧(一個線程java棧的大小由-Xss參數確定),jvm則拋出OutOfMemoryError異常。

  • Java 虛擬機棧會出現兩種異常:StackOverFlowError 和 OutOfMemoryError。
    • StackOverFlowError: 若 Java 虛擬機棧的內存大小不允許動態擴展,那麼當線程請求棧的深度超過當前 Java 虛擬機棧的最大深度的時候,就拋出 StackOverFlowError 異常。
    • OutOfMemoryError: 若 Java 虛擬機棧的內存大小允許動態擴展,且當線程請求棧時內存用完了,無法再動態擴展了,此時拋出 OutOfMemoryError 異常。

十一.內存泄漏

  • 內存泄漏與內存溢出的關係:
    內存泄漏的堆積最終會導致內存溢出
    內存溢出就是你要的內存空間超過了系統實際分配給你的空間,此時系 統相當於沒法滿足你的需求,就會報內存溢出的錯誤。
    內存泄漏是指你向系統申請分配內存進行使用(new),可是使用完了以 後卻不歸還(delete),結果你申請到的那塊內存你自己也不能再訪問 (也許你把它的地址給弄丟了),而系統也不能再次將它分配給需要的 程序。就相當於你租了個帶鑰匙的櫃子,你存完東西之後把櫃子鎖上之 後,把鑰匙丟了或者沒有將鑰匙還回去,那麼結果就是這個櫃子將無法 供給任何人使用,也無法被垃圾回收器回收,因爲找不到他的任何信 息。
    內存溢出:一個盤子用盡各種方法只能裝4個果子,你裝了5個,結果掉 倒地上不能吃了。這就是溢出。比方說棧,棧滿時再做進棧必定產生空 間溢出,叫上溢,棧空時再做退棧也產生空間溢出,稱爲下溢。就是分 配的內存不足以放下數據項序列,稱爲內存溢出。說白了就是我承受不了 那麼多,那我就報錯,

內存泄漏的幾種情況:

  • 1、靜態集合類,如HashMap、LinkedList等等。如果這些容器爲靜態的,那麼它們的生命週期與程序一致,則容器中的對象在程序結束之前將不能被釋放,從而造成內存泄漏。簡單而言,長生命週期的對象持有短生命週期對象的引用,儘管短生命週期的對象不再使用,但是因爲長生命週期對象持有它的引用而導致不能被回收。

  • 2、各種連接,如數據庫連接、網絡連接和IO連接等。在對數據庫進行操作的過程中,首先需要建立與數據庫的連接,當不再使用時,需要調用close方法來釋放與數據庫的連接。只有連接被關閉後,垃圾回收器纔會回收對應的對象。否則,如果在訪問數據庫的過程中,對Connection、Statement或ResultSet不顯性地關閉,將會造成大量的對象無法被回收,從而引起內存泄漏。

  • 3、變量不合理的作用域。一般而言,一個變量的定義的作用範圍大於其使用範圍,很有可能會造成內存泄漏。另一方面,如果沒有及時地把對象設置爲null,很有可能導致內存泄漏的發生。

      public class UsingRandom {
      	private String msg;
      	public void receiveMsg(){
      		readFromNet();// 從網絡中接受數據保存到msg中
      		saveDB();// 把msg保存到數據庫中
      	}
      }  
    

如上面這個僞代碼,通過readFromNet方法把接受的消息保存在變量msg 中,然後調用saveDB方法把msg的內容保存到數據庫中,此時msg已經就沒 用了,由於msg的生命週期與對象的生命週期相同,此時msg還不能回收,因 此造成了內存泄漏。
實際上這個msg變量可以放在receiveMsg方法內部,當方法使用完,那麼 msg的生命週期也就結束,此時就可以回收了。還有一種方法,在使用完msg 後,把msg設置爲null,這樣垃圾回收器也會回收msg的內存空間。

  • 4、內部類持有外部類,如果一個外部類的實例對象的方法返回了一個內部類的實例對象,這個內部類對象被長期引用了,即使那個外部類實例對象不再被使用,但由於內部類持有外部類的實例對象,這個外部類對象將不會被垃圾回收,這也會造成內存泄露。
  • 5、改變哈希值,當一個對象被存儲進HashSet集合中以後,就不能修改這個對象中的那些參與計算哈希值的字段了,否則,對象修改後的哈希值與最初存儲進HashSet集合中時的哈希值就不同了,在這種情況下,即使在contains方法使用該對象的當前引用作爲的參數去HashSet集合中檢索對象,也將返回找不到對象的結果,這也會導致無法從HashSet集合中單獨刪除當前對象,造成內存泄露

Java中常見的幾種RuntimeException

JAVA中常見的幾種RuntimeException,大約有如下幾種:

NullPointerException - 空指針引用異常
ClassCastException - 類型強制轉換異常。
IllegalArgumentException - 傳遞非法參數異常。
ArithmeticException - 算術運算異常
ArrayStoreException - 向數組中存放與聲明類型不兼容對象異常
IndexOutOfBoundsException - 下標越界異常
NegativeArraySizeException - 創建一個大小爲負數的數組錯誤異常
NumberFormatException - 數字格式異常
SecurityException - 安全異常
UnsupportedOperationException - 不支持的操作異常

常見的RuntimeException

RuntimeException是開發中最容易遇到的,下面列舉一下常見的RuntimeException:

  • 1、NullPointerException:見的最多了,其實很簡單,一般都是在null對象上調用方法了。
    String s=null;
    boolean eq=s.equals(""); // NullPointerException
    這裏你看的非常明白了,爲什麼一到程序中就暈呢?
    public int getNumber(String str){
      if(str.equals(“A”)) return 1;
       else if(str.equals(“B”)) return 2;
    }
    這個方法就有可能拋出NullPointerException,我建議你主動拋出異常,因爲代碼一多,你可能又暈了。
    public int getNumber(String str){
      if(str==null) throw new NullPointerException(“參數不能爲空”);
    //你是否覺得明白多了
      if(str.equals(“A”)) return 1;
       else if(str.equals(“B”)) return 2;
    }
  • 2、NumberFormatException:繼承IllegalArgumentException,字符串轉換爲數字時出現。比如int i= Integer.parseInt(“ab3”);
  • 3、ArrayIndexOutOfBoundsException:數組越界。比如 int[] a=new int[3]; int b=a[3];
  • 4、StringIndexOutOfBoundsException:字符串越界。比如 String s=“hello”; char c=s.chatAt(6);
  • 5、ClassCastException:類型轉換錯誤。比如 Object obj=new Object(); String s=(String)obj;
  • 6、UnsupportedOperationException:該操作不被支持。如果我們希望不支持這個方法,可以拋出這個異常。既然不支持還要這個幹嗎?有可能子類中不想支持父類中有的方法,可以直接拋出這個異常。
  • 7、ArithmeticException:算術錯誤,典型的就是0作爲除數的時候。
  • 8、IllegalArgumentException:非法參數,在把字符串轉換成數字的時候經常出現的一個異常,我們可以在自己的程序中好好利用這個異常。

java中hashmap的原理

較詳細鏈接(評論裏也有詳細解答)

https://www.nowcoder.com/questionTerminal/6bd3857199564b3fb2d3fee4f4de06ea?toCommentId=1185246
鏈接:https://www.nowcoder.com/questionTerminal/6bd3857199564b3fb2d3fee4f4de06ea?toCommentId=1185246
來源:牛客網

hashmap是一個key-value鍵值對的數據結構,從結構上來講在jdk1.8之 前是用數組加鏈表的方式實現,jdk1.8加了紅黑樹,hashmap數組的默認初 始長度是16,hashmap數組只允許一個key爲null,允許多個value爲null
hashmap的內部實現,hashmap是使用數組+鏈表+紅黑樹的形式實現的, 其中數組是一個一個Node[]數組,我們叫他hash桶數組,它上面存放的是 key-value鍵值對的節點。HashMap是用hash表來存儲的,在hashmap裏 爲解決hash衝突,使用鏈地址法,簡單來說就是數組加鏈表的形式來解決 ,當數據被hash後,得到數組下標,把數據放在對應下表的鏈表中。
然後再說一下hashmap的方法實現
put方法,put方法的第一步,就是計算出要put元素在hash桶數組中的索引 位置,得到索引位置需要三步,去put元素key的hashcode值,高位運算, 取模運算,高位運算就是用第一步得到的值h,用h的高16位和低16位進行異 或操作,第三步爲了使hash桶數組元素分佈更均勻,採用取模運算,取模運 算就是用第二步得到的值和hash桶數組長度-1的值取與。這樣得到的結果和 傳統取模運算結果一致,而且效率比取模運算高
jdk1.8中put方法的具體步驟,先判斷hashmap是否爲空,爲空的話擴容, 不爲空計算出key的hash值i,然後看table[i]是否爲空,爲空就直接插 入,不爲空判斷當前位置的key和table[i]是否相同,相同就覆蓋,不相同 就查看table[i]是否是紅黑樹節點,如果是的話就用紅黑樹直接插入鍵值 對,如果不是開始遍歷鏈表插入,如果遇到重複值就覆蓋,否則直接插入, 如果鏈表長度大於8,轉爲紅黑樹結構,執行完成後看size是否大於閾值 threshold,大於就擴容,否則直接結束
get方法就是計算出要獲取元素的hash值,去對應位置取即可。
擴容機制,hashmap的擴容中主要進行兩部,第一步把數組長度變爲原來的 兩倍,第二部把舊數組的元素重新計算hash插入到新數組中,在jdk1.8時,不用重新計算hash,只用看看原來的hash值新增的一位是零還是1,如果是1這個元素在新數組中的位置,是原數組的位置加原數組長度,如果是零就插入到原數組中。擴容過程第二部一個非常重要的方法是transfer方法,採用頭插法,把舊數組的元素插入到新數組中。
3.hashmap大小爲什麼是2的冪次方
在計算插入元素在hash桶數組的索引時第三步,爲了使元素分佈的更加均勻,用取模操作,但是傳統取模操作效率低,然後優化成h&(length-1),設置成2冪次方,是因爲2的冪次方-1後的值每一位上都是1,然後與第二步計算出的h值與的時候,最終的結果只和key的hashcode值本身有關,這樣不會造成空間浪費並且分佈均勻,如果不是2的冪次方
如果length不爲2的冪,比如15。那麼length-1的2進制就會變成1110。在h爲隨機數的情況下,和1110做&操作。尾數永遠爲0。那麼0001、1001、1101等尾數爲1的位置就永遠不可能被entry佔用。這樣會造成浪費,不隨機等問題。

spring

1.單例及線程安全

spring依賴注入時,使用了雙重判斷加鎖的單例模式,首先從緩存中獲取bean實例,如果爲null,對緩存map加鎖,然後再從緩存中獲取bean,如果繼續爲null,就創建一個bean。這樣雙重判斷,能夠避免在加鎖的瞬間,有其他依賴注入引發bean實例的創建,從而造成重複創建的結果

java靜態方法不能調用非靜態方法的原因

靜態方法是屬於類的,即靜態方法是隨着類的加載而加載的,在加載類時,程序就會爲靜態方法分配內存,而非靜態方法是屬於對象的,對象是在類加載之後創建的,也就是說靜態方法先於對象存在,當你創建一個對象時,程序爲其在堆中分配內存,一般是通過this指針來指向該對象。靜態方法不依賴於對象的調用,它是通過‘類名.靜態方法名’這樣的方式來調用的。而對於非靜態方法,在對象創建的時候程序纔會爲其分配內存,然後通過類的對象去訪問非靜態方法。因此在對象未存在時非靜態方法也不存在,靜態方法自然不能調用一個不存在的方法

JDK1.8新特性

  • Lambda表達式
  • 函數式接口
  • 方法引用和構造器調用
  • Stream API
  • 接口中的默認方法和靜態方法
  • 新時間日期API
  • default關鍵字
    在jdk1.8中對hashMap等map集合的數據結構優化。hashMap數據結構的優化
    原來的hashMap採用的數據結構是哈希表(數組+鏈表),hashMap默認大小是16,一個0-15索引的數組,如何往裏面存儲元素,首先調用元素的hashcode 方法,計算出哈希碼值,經過哈希算法算成數組的索引值,如果對應的索引處沒有元素,直接存放,如果有對象在,那麼比較它們的equals方法比較內容
    如果內容一樣,後一個value會將前一個value的值覆蓋,如果不一樣,在1.7的時候,後加的放在前面,形成一個鏈表,形成了碰撞,在某些情況下如果鏈表 無限下去,那麼效率極低,碰撞是避免不了的
    加載因子:0.75,數組擴容,達到總容量的75%,就進行擴容,但是無法避免碰撞的情況發生
    在1.8之後,在數組+鏈表+紅黑樹來實現hashmap,當碰撞的元素個數大於8時 & 總容量大於64,會有紅黑樹的引入
    除了添加之後,效率都比鏈表高,1.8之後鏈表新進元素加到末尾
    ConcurrentHashMap (鎖分段機制),concurrentLevel,jdk1.8採用CAS算法(無鎖算法,不再使用鎖分段)數組+鏈表中也引入了紅黑樹的使用
    – default關鍵字
    通常都是認爲接口裏面是只能有抽象方法,不能有任何方法的實現的,那麼在jdk1.8裏面打破了這個規定,引入了新的關鍵字default,通過使用default修飾方法,可以讓我們在接口裏面定義具體的方法實現,如下:
public interface NewCharacter {
    
    public void test1();
    
    public default void test2(){
        System.out.println("我是新特性1");
    }

}

定義一個方法的作用是什麼呢?爲什麼不在接口的實現類裏面再去實現方法呢?
  其實這麼定義一個方法的主要意義是定義一個默認方法,也就是說這個接口的實現類實現了這個接口之後,不用管這個default修飾的方法,也可以直接調用,如下:

public class NewCharacterImpl implements NewCharacter{

    @Override
    public void test1() {
        
    }
    
    public static void main(String[] args) {
        NewCharacter nca = new NewCharacterImpl();
        nca.test2();
    }

}

default方法是所有的實現類都不需要去實現的就可以直接調用,那麼比如說jdk的集合List裏面增加了一個sort方法,那麼如果定義爲一個抽象方法,其所有的實現類如arrayList,LinkedList等都需要對其添加實現,那麼現在用default定義一個默認的方法之後,其實現類可以直接使用這個方法了,這樣不管是開發還是維護項目,都會大大簡化代碼量

繼承和組合

借鑑這篇文章 :
https://blog.csdn.net/wrs120/article/details/88584705

繼承優點:

  • 代碼複用
  • 子類可重寫父類方法
  • 子類在父類的繼承上可根據自己的業務需求擴展
  • 創建子類對象時,無需創建父類對象,子類自動繼承父類的的成員變量和方法,如果權限允許,子類可直接訪問

缺點:

  • 不支持動態繼承,在編譯階段就確定了子類的父類
  • 破壞封裝性
  • 封裝性指出每個類都應該封裝它內容信息和實現細節,而只暴露必要的方法給其他類使用。但在繼承關係中,子類可以直接訪問父類的成員變量和方法,如下例子中父類Fruit中有成員變量weight。Apple繼承了Fruit之後,Apple可直接操作Fruit類的成員變量,因此破壞了封裝性!
  • 緊耦合
    當父類的實現做了修改時,父類也不得不修改(比如修改了父類某個接口名,子類也必須作相應修改);子類必須依賴父類存在

組合

優點:
  • 支持動態擴展,可在運行時根據具體對象選擇不同類型的組合對象(擴展性比繼承好)
  • 不破壞封裝性
  • 鬆耦合
缺點:
  • 整體類不能自動獲取局部類的接口(整體類可 以看成是上面的bird,如果想在bird裏使用Animal的方法,必須寫代碼來調用,但是繼承,bird自動就擁有了animal的方法)
  • 沒有實現多態

組合舉例

public class Animal {
    private void beat(){
        System.out.println("心臟跳動...");
    }
    public void breath(){
        beat();
        System.out.println("呼吸中...");
    }
}


public class Bird {
    //將Animal作爲Bird的成員變量
    private Animal a;
    public Bird(Animal a){
        this.a = a;
    }
    public void breath(){
        a.breath();
    }
    public void fly(){
        System.out.println("我在飛..");
    }
 
    public static void main(String[] args){
        Animal animal = new Animal();
        Bird b = new Bird(animal);
        b.breath();
        b.fly();
    }
}

如何選擇?

  • 想實現複用,並且複用部分可能改變,用繼承;複用不會改變,用組合
  • 當兩個類之間明顯存在整體與部分的關係時,用組合關係

進程間的通訊方式

liunx六大進程間通信方式

管道,消息隊列,共享內存,信號量,socket,信號,文件鎖
  • 1,管道
    1,匿名管道:

               概念:在內核中申請一塊固定大小的緩衝區,程序擁有寫入和讀取的權利,一般使用fork函數實現父子進程的通信。
    2,命名管道: 
    
                概念:在內核中申請一塊固定大小的緩衝區,程序擁有寫入和讀取的權利,沒有血緣關係的進程也可以進程間通信。
    3,特點:
              1,面向字節流,
    
              2,生命週期隨內核
    
              3,自帶同步互斥機制。
    
              4,半雙工,單向通信,兩個管道實現雙向通信。
    
  • 2,消息隊列
    1,概念:在內核中創建一隊列,隊列中每個元素是一個數據報,不同的進程可以通過句柄去訪問這個隊列。
    消息隊列提供了⼀個從⼀個進程向另外⼀個進程發送⼀塊數據的⽅法。
    每個數據塊都被認爲是有⼀個類型,接收者進程接收的數據塊可以有不同的類型值
    消息隊列也有管道⼀樣的不⾜,就是每個消息的最⼤⻓度是有上限的(MSGMAX),
    每個消息隊 列的總的字節數是有上限的(MSGMNB),系統上消息隊列的總數也有⼀個上限(MSGMNI)
    2,特點:
    1, 消息隊列可以認爲是一個全局的一個鏈表,鏈表節點鐘存放着數據報的類型和內容,有消息隊列的標識符進行標記。
    2,消息隊列允許一個或多個進程寫入或者讀取消息。
    3,消息隊列的生命週期隨內核。
    4,消息隊列可實現雙向通信。

  • 3,信號量
    1,概念
    在內核中創建一個信號量集合(本質是個數組),數組的元素(信號量)都是1,使用P操作進行-1,使用V操作+1,

                    (1) P(sv):如果sv的值⼤大於零,就給它減1;如果它的值爲零,就掛起該進程的執⾏ 。
                    (2) V(sv):如果有其他進程因等待sv而被掛起,就讓它恢復運⾏,如果沒有進程因等待sv⽽掛起,就給它加1。
                         PV操作用於同一進程,實現互斥。
                        PV操作用於不同進程,實現同步。
       2,功能:
                   對臨界資源進行保護。       
    
  • 4,共享內存
    1,概念:
    將同一塊物理內存一塊映射到不同的進程的虛擬地址空間中,實現不同進程間對同一資源的共享。
    共享內存可以說是最有用的進程間通信方式,也是最快的IPC形式。
    2,特點:
    1,不用從用戶態到內核態的頻繁切換和拷貝數據,直接從內存中讀取就可以。
    2,共享內存是臨界資源,所以需要操作時必須要保證原子性。使用信號量或者互斥鎖都可以。
    3,生命週期隨內核。

  • 5,總結
    所有的以上的方式都是生命週期隨內核,不手動釋就不會消失。

爲什麼java不提供指針訪問內存就會內存更安全

構造器 Constructor 是否可被 override?

在講繼承的時候我們就知道父類的私有屬性和構造方法並不能被繼承,所以 Constructor 也就不能被 override(重寫),但是可以 overload(重載),所以你可以看到一個類中有多個構造函數的情況。

10. 重載和重寫的區別

  • 重載: 發生在同一個類中,方法名必須相同,參數類型不同、個數不同、順序不同,方法返回值和訪問修飾符可以不同,發生在編譯時。
  • 重寫: 發生在父子類中,方法名、參數列表必須相同,返回值範圍小於等於父類,拋出的異常範圍小於等於父類,訪問修飾符範圍大於等於父類;如果父類方法訪問修飾符爲 private 則子類就不能重寫該方法。

關於繼承如下 3 點請記住:

子類擁有父類對象所有的屬性和方法(包括私有屬性和私有方法),但是父類中的私有屬性和方法子類是無法訪問,只是擁有。
子類可以擁有自己屬性和方法,即子類可以對父類進行擴展。
子類可以用自己的方式實現父類的方法。(以後介紹)。

18. 成員變量與局部變量的區別有那些?

從語法形式上看:成員變量是屬於類的,而局部變量是在方法中定義的變量 或是方法的參數;成員變量可以被 public,private,static 等修飾符 所修飾,而局部變量不能被訪問控制修飾符及 static 所修飾;但是,成員變量和局部變量都能被 final 所修飾。
從變量在內存中的存儲方式來看:如果成員變量是使用static修飾的,那麼 這個成員變量是屬於類的,如果沒有使用static修飾,這個成員變量是屬於 實例的。而對象存在於堆內存,局部變量則存在於棧內存。
從變量在內存中的生存時間上看:成員變量是對象的一部分,它隨着對象的 創建而存在,而局部變量隨着方法的調用而自動消失。
成員變量如果沒有被賦初值:則會自動以類型的默認值而賦值(一種情況例 外:被 final 修飾的成員變量也必須顯式地賦值),而局部變量則不會自 動賦值。

在調用子類構造方法之前會先調用父類沒有參數的構造方法,其目的是?

幫助子類做初始化工作

總結一下Java中方法參數的使用情況:

  • 一個方法不能修改一個基本數據類型的參數(即數值型或布爾型)。
  • 一個方法可以改變一個對象參數的狀態。
  • 一個方法不能讓對象參數引用一個新的對象。 (對象引用做參數實際上是對引用做了一份拷貝,所以可以相應改變對象的狀態,但是卻沒法讓對象參數引用一個新的對象)

Java異常類層次結構圖

Java異常類層次結構圖

異常處理總結

  • try 塊:用於捕獲異常。其後可接零個或多個catch塊,如果沒有catch塊,則必須跟一個finally塊
  • catch 塊:用於處理try捕獲到的異常。
  • finally 塊:無論是否捕獲或處理異常,finally塊裏的語句都會被執行。當在try塊或catch塊中遇到return語句時,finally語句塊將在方法返回之前被執行

在以下4種特殊情況下,finally塊不會被執行:

  • 在finally語句塊第一行發生了異常。 因爲在其他行,finally塊還是會得到執行
  • 在前面的代碼中用了System.exit(int)已退出程序。 exit是帶參函數 ;若該語句在異常語句之後,finally會執行
  • 程序所在的線程死亡。
  • 關閉CPU。

static 關鍵字

static 關鍵字主要有以下四種使用場景:

  • 修飾成員變量和成員方法: 被 static 修飾的成員屬於類,不屬於單個這個類的某個對象,被類中所有對象共享,可以並且建議通過類名調用。被static 聲明的成員變量屬於靜態成員變量,靜態變量 存放在 Java 內存區域的方法區。調用格式:類名.靜態變量名 類名.靜態方法名()
  • 靜態代碼塊: 靜態代碼塊定義在類中方法外, 靜態代碼塊在非靜態代碼塊之前執行(靜態代碼塊—>非靜態代碼塊—>構造方法)。 該類不管創建多少對象,靜態代碼塊只執行一次.
  • 靜態內部類(static修飾類的話只能修飾內部類): 靜態內部類與非靜態內部類之間存在一個最大的區別: 非靜態內部類在編譯完成之後會隱含地保存着一個引用,該引用是指向創建它的外圍類,但是靜態內部類卻沒有。沒有這個引用就意味着:1. 它的創建是不需要依賴外圍類的創建。2. 它不能使用任何外圍類的非static成員變量和方法。
  • 靜態導包(用來導入類中的靜態資源,1.5之後的新特性): 格式爲:import static 這兩個關鍵字連用可以指定導入某個類中的指定靜態資源,並且不需要使用類名調用類中靜態成員,可以直接使用類中靜態成員變量和成員方法。

所有整形包裝類對象值得比較必須使用equals方法。

先看下面這個例子:

Integer x = 3;  
Integer y = 3;
System.out.println(x == y);// true
Integer a = new Integer(3);
Integer b = new Integer(3);
System.out.println(a == b);//false
System.out.println(a.equals(b));//false

當使用自動裝箱方式創建一個Integer對象時,當數值在-128 ~127時,會將創建的Integer對象緩存起來,當下次再出現該數值時,直接從緩存中取出對應的Integer對象。所以上述代碼中,x和y引用的是相同的Integer對象。

get和post請求的區別

網上也有文章說:get和post請求實際上是沒有區別,大家可以自行查詢相關文章(參考文章:https://www.cnblogs.com/logsharing/p/8448446.html,知乎對應的問題鏈接:get和post區別?)!我下面給出的只是一種常見的答案。

  • ①get請求用來從服務器上獲得資源,而post是用來向服務器提交數據;

  • ②get將表單中數據按照name=value的形式,添加到action 所指向的URL 後面,並且兩者使用"?“連接,而各個變量之間使用”&"連接;post是將表單中的數據放在HTTP協議的請求頭或消息體中,傳遞到action所指向URL;

  • ③get傳輸的數據要受到URL長度限制(最大長度是 2048 個字符);而post可以傳輸大量的數據,上傳文件通常要使用post方式;

  • ④使用get時參數會顯示在地址欄上,如果這些數據不是敏感數據,那麼可以使用get;對於敏感數據還是應用使用post;

  • ⑤get使用MIME類型application/x-www-form-urlencoded的URL編碼(也叫百分號編碼)文本的格式傳遞參數,保證被傳送的參數由遵循規範的文本組成,例如一個空格的編碼是"%20"。

補充:GET方式提交表單的典型應用是搜索引擎。GET方式就是被設計爲查詢用的。

對於GET方式的請求,瀏覽器會把http header和data一併發送出去,服務器響應200(返回數據);

而對於POST,瀏覽器先發送header,服務器響應100 continue,瀏覽器再發送data,服務器響應200 ok(返回數據)。

  • 也就是說,GET只需要汽車跑一趟就把貨送到了,而POST得跑兩趟,第一趟,先去和服務器打個招呼“嗨,我等下要送一批貨來,你們打開門迎接我”,然後再回頭把貨送過去。

轉發(Forward)和重定向(Redirect)的區別

  • 轉發是服務器行爲,重定向是客戶端行爲。

  • 轉發(Forward) 通過RequestDispatcher對象的forward(HttpServletRequest request,HttpServletResponse response)方法實現的。RequestDispatcher可以通過HttpServletRequest 的getRequestDispatcher()方法獲得。例如下面的代碼就是跳轉到login_success.jsp頁面。

    request.getRequestDispatcher(“login_success.jsp”).forward(request, response);

  • 重定向(Redirect) 是利用服務器返回的狀態碼來實現的。客戶端瀏覽器請求服務器的時候,服務器會返回一個狀態碼。服務器通過 HttpServletResponse 的 setStatus(int status) 方法設置狀態碼。如果服務器返回301或者302,則瀏覽器會到新的網址重新請求該資源。

  • 從地址欄顯示來說
    forward是服務器請求資源,服務器直接訪問目標地址的URL,把那個URL的響應內容讀取過來,然後把這些內容再發給瀏覽器.瀏覽器根本不知道服務器發送的內容從哪裏來的,所以它的地址欄還是原來的地址. redirect是服務端根據邏輯,發送一個狀態碼,告訴瀏覽器重新去請求那個地址.所以地址欄顯示的是新的URL.

  • 從數據共享來說
    forward:轉發頁面和轉發到的頁面可以共享request裏面的數據. redirect:不能共享數據.

  • 從運用地方來說
    forward:一般用於用戶登陸的時候,根據角色轉發到相應的模塊. redirect:一般用於用戶註銷登陸時返回主頁面和跳轉到其它的網站等

  • 從效率來說
    forward:高. redirect:低.

關於java的對象和集合長度注意

  • java 中的 length 屬性是針對數組說的,比如說你聲明瞭一個數組,想知道這個數組的長度則用到了 length 這個屬性.
  • java 中的 length() 方法是針對字符串說的,如果想看這個字符串的長度則用到 length() 這個方法.
  • java 中的 size() 方法是針對泛型集合說的,如果想看這個泛型有多少個元素,就調用此方法來查看!

LinkedHashMap 結構圖(hashmap結構基礎上增加了雙向鏈表)

LinkedHashMap

4.1. 爲什麼要用線程池?

線程池提供了一種限制和管理資源(包括執行一個任務)。 每個線程池還維護一些基本統計信息,例如已完成任務的數量。

  • 降低資源消耗。 通過重複利用已創建的線程降低線程創建和銷燬造成的消耗。

  • 提高響應速度。 當任務到達時,任務可以不需要的等到線程創建就能立即執行。

  • 提高線程的可管理性。 線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。

Class文件字節碼結構組織示意圖

Class文件字節碼結構組織示意圖

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章