Android複習資料——Java知識點彙總(一)

接觸 Android 開發也有一段時間了,前段時間便開始想抽空整理一些知識點,經過筆記整理的方式減小本身重複學習的時間成本和提升自身的效率。java

目前先是總結了部分 Java 的知識點,這就是本文的主要分享內容。想特地申明的一點是,這個總結更多的是從本人本身的編程基礎和側重點出發,因此在內容上會有選擇性的忽略以及側重點,參考的博客和圖文有不少,沒辦法一一列出,若是有引用不當的部分會當即刪除,望你們見諒。 知識點目錄能夠再右邊側邊欄查看跳轉。git

另外,以後會整理的知識點還會有 Android SDK、Android 源碼、其餘的一些計算機基礎以及常見的面試題等幾個部分,日後的一個月時間裏會陸續補充更新,在 Github 上建立了項目,想關注的歡迎 stargithub

jvm

jvm工做流程

運行時數據區(Runtime Data Area)

區域 說明
程序計數器 每條線程都須要有一個程序計數器,計數器記錄的是正在執行的指令地址,若是正在執行的是Natvie 方法,這個計數器值爲空(Undefined)
java虛擬機棧 Java方法執行的內存模型,每一個方法執行的時候,都會建立一個棧幀用於保存局部變量表,操做數棧,動態連接,方法出口信息等。一個方法調用的過程就是一個棧幀從VM棧入棧到出棧的過程
本地方法棧 與VM棧發揮的做用很是類似,VM棧執行Java方法(字節碼)服務,Native方法棧執行的是Native方法服務。
Java堆 此內存區域惟一的目的就是存放對象實例,幾乎全部的對象都在這分配內存
方法區 方法區是各個內存所共享的內存空間,方法區中主要存放被JVM加載的類信息、常量、靜態變量、即時編譯後的代碼等數據

方法指令

指令 說明
invokeinterface 用以調用接口方法
invokevirtual 指令用於調用對象的實例方法
invokestatic 用以調用類/靜態方法
invokespecial 用於調用一些須要特殊處理的實例方法,包括實例初始化方法、私有方法和父類方法

類加載器

類加載器 說明
BootstrapClassLoader Bootstrap類加載器負責加載rt.jar中的JDK類文件,它是全部類加載器的父加載器。Bootstrap類加載器沒有任何父類加載器,若是你調用String.class.getClassLoader(),會返回null,任何基於此的代碼會拋出NUllPointerException異常。Bootstrap加載器被稱爲初始類加載器
ExtClasssLoader 而Extension將加載類的請求先委託給它的父加載器,也就是Bootstrap,若是沒有成功加載的話,再從jre/lib/ext目錄下或者java.ext.dirs系統屬性定義的目錄下加載類。Extension加載器由sun.misc.Launcher$ExtClassLoader實現
AppClassLoader 第三種默認的加載器就是System類加載器(又叫做Application類加載器)了。它負責從classpath環境變量中加載某些應用相關的類,classpath環境變量一般由-classpath或-cp命令行選項來定義,或者是JAR中的Manifest的classpath屬性。Application類加載器是Extension類加載器的子加載器

 

工做原理 說明
委託機制 加載任務委託交給父類加載器,若是不行就向下傳遞委託任務,由其子類加載器加載,保證java核心庫的安全性
可見性機制 子類加載器能夠看到父類加載器加載的類,而反之則不行
單一性機制 父加載器加載過的類不能被子加載器加載第二次

static

  • static關鍵字修飾的方法或者變量不須要依賴於對象來進行訪問,只要類被加載了,就能夠經過類名去進行訪問。
  • 靜態變量被全部的對象所共享,在內存中只有一個副本,它當且僅當在類初次加載時會被初始化。
  • 能經過this訪問靜態成員變量嗎? 全部的靜態方法和靜態變量均可以經過對象訪問(只要訪問權限足夠)。
  • static是不容許用來修飾局部變量

final

  • 能夠聲明成員變量、方法、類以及本地變量
  • final成員變量必須在聲明的時候初始化或者在構造器中初始化,不然就會報編譯錯誤
  • final變量是隻讀的
  • final申明的方法不能夠被子類的方法重寫
  • final類一般功能是完整的,不能被繼承
  • final變量能夠安全的在多線程環境下進行共享,而不須要額外的同步開銷
  • final關鍵字提升了性能,JVM和Java應用都會緩存final變量,會對方法、變量及類進行優化
  • 方法的內部類訪問方法中的局部變量,但必須用final修飾才能訪問

String、StringBuffer、StringBuilder

  • String是final類,不能被繼承。對於已經存在的Stirng對象,修改它的值,就是從新建立一個對象
  • StringBuffer是一個相似於String的字符串緩衝區,使用append()方法修改Stringbuffer的值,使用toString()方法轉換爲字符串,是線程安全的
  • StringBuilder用來替代於StringBuffer,StringBuilder是非線程安全的,速度更快

異常處理

  • Exception、Error是Throwable類的子類
  • Error類對象由Java虛擬機生成並拋出,不可捕捉
  • 無論有沒有異常,finally中的代碼都會執行
  • 當try、catch中有return時,finally中的代碼依然會繼續執行
常見的Error
OutOfMemoryError StackOverflowError NoClassDeffoundError
常見的Exception
常見的非檢查性異常
ArithmeticException ArrayIndexOutOfBoundsException ClassCastException
IllegalArgumentException IndexOutOfBoundsException NullPointerException
NumberFormatException SecurityException UnsupportedOperationException
常見的檢查性異常
IOException CloneNotSupportedException IllegalAccessException
NoSuchFieldException NoSuchMethodException FileNotFoundException

內部類

  • 內部類提供了更好的封裝,能夠把內部類隱藏在外部類以內,不容許同一個包中的其餘類訪問該類。
  • 內部類的方法能夠直接訪問外部類的全部數據,包括私有的數據。

多態

  • 父類的引用能夠指向子類的對象
  • 建立子類對象時,調用的方法爲子類重寫的方法或者繼承的方法
  • 若是咱們在子類中編寫一個獨有的方法,此時就不能經過父類的引用建立的子類對象來調用該方法

抽象和接口

  • 抽象類不能有對象(不能用new此關鍵字來建立抽象類的對象)
  • 抽象類中的抽象方法必須在子類中被重寫
  • 接口中的全部屬性默認爲:public static final ****;
  • 接口中的全部方法默認爲:public abstract ****;

集合

  • List接口存儲一組不惟一,有序(插入順序)的對象, Set接口存儲一組惟一,無序的對象。
  • HashMap是非synchronized的,性能更好,HashMap能夠接受爲null的key和value,而Hashtable是線程安全的,比HashMap要慢,不接受null

反射

try {
    Class cls = Class.forName("com.jasonwu.Test");
    //獲取構造方法
    Constructor[] publicConstructors = cls.getConstructors();
    //獲取所有構造方法
    Constructor[] declaredConstructors = cls.getDeclaredConstructors();
    //獲取公開方法
    Method[] methods = cls.getMethods();
    //獲取所有方法
    Method[] declaredMethods = cls.getDeclaredMethods();
    //獲取公開屬性
    Field[] publicFields = cls.getFields();
    //獲取所有屬性
    Field[] declaredFields = cls.getDeclaredFields();
    Object clsObject = cls.newInstance();
    Method method = cls.getDeclaredMethod("getModule1Functionality");
    Object object = method.invoke(null);
} catch (ClassNotFoundException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
} catch (InstantiationException e) {
    e.printStackTrace();
} catch (NoSuchMethodException e) {
    e.printStackTrace();
} catch (InvocationTargetException e) {
    e.printStackTrace();
}
複製代碼

單例

餓漢式

public class CustomManager {
    private Context mContext;
    private static final Object mLock = new Object();
    private static CustomManager mInstance;

    public static CustomManager getInstance(Context context) {
        synchronized (mLock) {
            if (mInstance == null) {
                mInstance = new CustomManager(context);
            }

            return mInstance;
        }
    }

    private CustomManager(Context context) {
        this.mContext = context.getApplicationContext();
    }
}
複製代碼

雙重檢查模式

public class CustomManager {
    private Context mContext;
    private volatile static CustomManager mInstance;

    public static CustomManager getInstance(Context context) {
        // 避免非必要加鎖
        if (mInstance == null) {
            synchronized (CustomManger.class) {
                if (mInstance == null) {
                    mInstacne = new CustomManager(context);
                }
            }
        }

        return mInstacne;
    }

    private CustomManager(Context context) {
        this.mContext = context.getApplicationContext();
    }
}
複製代碼

靜態內部類模式

public class CustomManager{
    private CustomManager(){}
 
    private static class CustomManagerHolder {
        private static CustomManager INSTANCE = new CustomManager();
    }
 
    public static CustomManager getInstance() {
        return CustomManagerHolder.INSTANCE;
    } 
}
複製代碼

靜態內部類的原理是:面試

當SingleTon第一次被加載時,並不須要去加載SingleTonHoler,只有當getInstance()方法第一次被調用時,纔會去初始化INSTANCE,這種方法不只能確保線程安全,也能保證單例的惟一性,同時也延遲了單例的實例化。getInstance()方法並無屢次去new對象,取的都是同一個INSTANCE對象。算法

虛擬機會保證一個類的<clinit>()方法在多線程環境中被正確地加鎖、同步,若是多個線程同時去初始化一個類,那麼只會有一個線程去執行這個類的<clinit>()方法,其餘線程都須要阻塞等待,直到活動線程執行<clinit>()方法完畢編程

缺點在於沒法傳遞參數,如Context等數組

線程

valatile

當把變量聲明爲volatile類型後,編譯器與運行時都會注意到這個變量是共享的,所以不會將該變量上的操做與其餘內存操做一塊兒重排序。volatile變量不會被緩存在寄存器或者對其餘處理器不可見的地方,JVM 保證了每次讀變量都從內存中讀,跳過 CPU cache 這一步,所以在讀取volatile類型的變量時總會返回最新寫入的值。緩存

當一個變量定義爲 volatile 以後,將具有如下特性:安全

  • 保證此變量對全部的線程的可見性,不能保證它具備原子性(可見性,是指線程之間的可見性,一個線程修改的狀態對另外一個線程是可見的)
  • 禁止指令重排序優化
  • volatile 的讀性能消耗與普通變量幾乎相同,可是寫操做稍慢,由於它須要在本地代碼中插入許多內存屏障指令來保證處理器不發生亂序執行

AtomicInteger 中主要實現了整型的原子操做,防止併發狀況下出現異常結果,其內部主要依靠JDK 中的unsafe 類操做內存中的數據來實現的。volatile 修飾符保證了value在內存中其餘線程能夠看到其值得改變。CAS操做保證了AtomicInteger 能夠安全的修改value 的值。數據結構

HashMap

HashMap的工做原理

HashMap基於hashing原理,咱們經過put()和get()方法儲存和獲取對象。當咱們將鍵值對傳遞給put()方法時,它調用鍵對象的hashCode()方法來計算hashcode,讓後找到bucket位置來儲存Entry對象。當兩個對象的hashcode相同時,它們的bucket位置相同,‘碰撞’會發生。由於HashMap使用鏈表存儲對象,這個Entry會存儲在鏈表中,當獲取對象時,經過鍵對象的equals()方法找到正確的鍵值對,而後返回值對象。

若是HashMap的大小超過了負載因子(load factor)定義的容量,怎麼辦?
默認的負載因子大小爲0.75,也就是說,當一個map填滿了75%的bucket時候,和其它集合類(如ArrayList等)同樣,將會建立原來HashMap大小的兩倍的bucket數組,來從新調整map的大小,並將原來的對象放入新的bucket數組中。這個過程叫做rehashing,由於它調用hash方法找到新的bucket位置。

爲何String, Interger這樣的wrapper類適合做爲鍵?
由於String是不可變的,也是final的,並且已經重寫了equals()和hashCode()方法了。其餘的wrapper類也有這個特色。不可變性是必要的,由於爲了要計算hashCode(),就要防止鍵值改變,若是鍵值在放入時和獲取時返回不一樣的hashcode的話,那麼就不能從HashMap中找到你想要的對象。不可變性還有其餘的優勢如線程安全。若是你能夠僅僅經過將某個field聲明成final就能保證hashCode是不變的,那麼請這麼作吧。由於獲取對象的時候要用到equals()和hashCode()方法,那麼鍵對象正確的重寫這兩個方法是很是重要的。若是兩個不相等的對象返回不一樣的hashcode的話,那麼碰撞的概率就會小些,這樣就能提升HashMap的性能。

synchronized

當它用來修飾一個方法或者一個代碼塊的時候,可以保證在同一時刻最多隻有一個線程執行該段代碼。

在 Java 中,每一個對象都會有一個 monitor 對象,這個對象其實就是 Java 對象的鎖,一般會被稱爲「內置鎖」或「對象鎖」。類的對象能夠有多個,因此每一個對象有其獨立的對象鎖,互不干擾。針對每一個類也有一個鎖,能夠稱爲「類鎖」,類鎖其實是經過對象鎖實現的,即類的 Class 對象鎖。每一個類只有一個 Class 對象,因此每一個類只有一個類鎖。

Monitor是線程私有的數據結構,每個線程都有一個可用monitor record列表,同時還有一個全局的可用列表。每個被鎖住的對象都會和一個monitor關聯,同時monitor中有一個Owner字段存放擁有該鎖的線程的惟一標識,表示該鎖被這個線程佔用。Monitor是依賴於底層的操做系統的Mutex Lock(互斥鎖)來實現的線程同步。

根據獲取的鎖分類

獲取對象鎖

  • synchronized(this|object) {}
  • 修飾非靜態方法

獲取類鎖

  • synchronized(類.class) {}
  • 修飾靜態方法

原理

同步代碼塊:

  • monitorenter和monitorexit指令實現的

同步方法

  • 方法修飾符上的ACC_SYNCHRONIZED實現

Lock

悲觀鎖、樂觀鎖

悲觀鎖認爲本身在使用數據的時候必定有別的線程來修改數據,所以在獲取數據的時候會先加鎖,確保數據不會被別的線程修改。Java中,synchronized關鍵字和Lock的實現類都是悲觀鎖。悲觀鎖適合寫操做多的場景,先加鎖能夠保證寫操做時數據正確。

而樂觀鎖認爲本身在使用數據時不會有別的線程修改數據,因此不會添加鎖,只是在更新數據的時候去判斷以前有沒有別的線程更新了這個數據。若是這個數據沒有被更新,當前線程將本身修改的數據成功寫入。若是數據已經被其餘線程更新,則根據不一樣的實現方式執行不一樣的操做(例如報錯或者自動重試)。樂觀鎖在Java中是經過使用無鎖編程來實現,最常採用的是CAS算法,Java原子類中的遞增操做就經過CAS自旋實現。樂觀鎖適合讀操做多的場景,不加鎖的特色可以使其讀操做的性能大幅提高。

自旋鎖、適應性自旋鎖

阻塞或喚醒一個Java線程須要操做系統切換CPU狀態來完成,這種狀態轉換須要耗費處理器時間。若是同步代碼塊中的內容過於簡單,狀態轉換消耗的時間有可能比用戶代碼執行的時間還要長。

在許多場景中,同步資源的鎖定時間很短,爲了這一小段時間去切換線程,線程掛起和恢復現場的花費可能會讓系統得不償失。若是物理機器有多個處理器,可以讓兩個或以上的線程同時並行執行,咱們就可讓後面那個請求鎖的線程不放棄CPU的執行時間,看看持有鎖的線程是否很快就會釋放鎖。

而爲了讓當前線程「稍等一下」,咱們需讓當前線程進行自旋,若是在自旋完成後前面鎖定同步資源的線程已經釋放了鎖,那麼當前線程就能夠沒必要阻塞而是直接獲取同步資源,從而避免切換線程的開銷。這就是自旋鎖。

自旋鎖自己是有缺點的,它不能代替阻塞。自旋等待雖然避免了線程切換的開銷,但它要佔用處理器時間。若是鎖被佔用的時間很短,自旋等待的效果就會很是好。反之,若是鎖被佔用的時間很長,那麼自旋的線程只會白浪費處理器資源。因此,自旋等待的時間必需要有必定的限度,若是自旋超過了限定次數(默認是10次,可使用-XX:PreBlockSpin來更改)沒有成功得到鎖,就應當掛起線程。

自旋鎖的實現原理一樣也是CAS,AtomicInteger中調用unsafe進行自增操做的源碼中的do-while循環就是一個自旋操做,若是修改數值失敗則經過循環來執行自旋,直至修改爲功。

死鎖

當前線程擁有其餘線程須要的資源,當前線程等待其餘線程已擁有的資源,都不放棄本身擁有的資源。


本次的分享就到這啦,喜歡的話能夠點個贊👍或關注。若有錯誤的地方歡迎你們在評論裏指出。

本文爲我的原創,轉載請註明出處。

相關文章
相關標籤/搜索