Java、Android—零碎難記筆試考點(持續更新)

String類是final類

“對String對象的任何改變都不影響到原對象,相關的任何change操作都會生成新的對象”。

public class Test{
    public static void main(String[] args){
        String a = "aaaa";
        String b = a.replace('a', 'b');
        System.out.println(a);
        System.out.print(b);
    }
}

當對String類對象進行subString(),replace()等,應該賦值給新的String對象,因爲a還是原來的內容。

String str="hello world"和String str=new String("hello world")的區別:

public class Test{
    public static void main(String[] args){
        String a = new String("aaaa");
        String b = new String("aaaa");
        String c = "aaaa";
        System.out.println(a==b);
        System.out.println(a.equals(b));
        System.out.println(a==c);
        System.out.println(a.equals(c));
    }
}

String c = "aaaa";在編譯期間生成了 字面常量和符號引用,運行期間字面常量"aaaa"被存儲在運行時常量池(當然只保存了一份)。通過這種方式來將String對象跟引用綁定的話,JVM執行引擎會先在運行時常量池查找是否存在相同的字面常量,如果存在,則直接將引用指向已經存在的字面常量;否則在運行時常量池開闢一個空間來存儲該字面常量,並將引用指向該字面常量。

通過new關鍵字來生成對象是在堆區進行的,而在堆區進行對象生成的過程是不會去檢測該對象是否已經存在的。因此通過new來創建對象,創建出的一定是不同的對象,即使字符串的內容是相同的。

String類的equals方法只比較內容。所以equals返回true。

StringBuilder和StringBuffer類區別:

StringBuilder和StringBuffer類擁有的成員屬性以及成員方法基本相同,區別是StringBuffer類的成員方法前面多了一個關鍵字:synchronized,StringBuffer類是線程安全的

transient關鍵字

將不需要序列化的屬性前添加關鍵字transient,序列化對象的時候,這個屬性就不會被序列化。保證屬性不會被傳遞,安全。

static關鍵字

  • 不能修飾外部類,只有內部類纔可以。
  • 靜態的方法可以被繼承,但是不能重寫。
  • 靜態變量在JVM初始化階段就被賦值。

類初始化的順序:

父類靜態變量->父類靜態代碼塊->子類靜態變量->子類靜態代碼塊->父類普通變量->父類普通代碼塊->父類構造函數->子類普通變量->子類普通代碼塊->子類構造函數

假設類A有靜態內部類B和非靜態內部類C,創建B和C的區別爲:
A a=new A();
A.B b=new A.B();
A.C c=a.new C();

final關鍵字

  • final修飾類不可以被繼承,但是可以繼承其他類。
  • final修飾的變量稱爲常量,這些變量只能賦值一次。
  • final修飾的方法,不可以重寫,但可以繼承使用。

抽象類與接口區別:

  • 抽象類要被子類繼承,接口要被類實現。
  • 接口只能做方法聲明,抽象類中可以作方法聲明,也可以做方法實現。
  • 接口裏定義的變量只能是公共的靜態的常量,抽象類中的變量是普通變量。
  • 抽象類可以有具體的方法和屬性,接口只能有抽象方法和不可變常量。
  • 接口可以繼承接口,抽象類可以實現接口,抽象類可以繼承實體類。

匿名類又稱匿名內部類:

new 類名/接口名/抽象類名(){定義子類/實現類的內容}

Callable與Future

使用Callable和Future,我們可以方便的獲得線程的執行結果。

public interface Callable<V> {
    V call() throws Exception;
}

類型參數V即爲異步方法call的返回值類型。

Future可以對具體的Runnable或者Callable任務的執行結果進行取消、查詢是否完成以及獲取結果。可以通過get方法獲取執行結果,該方法會阻塞直到任務返回結果。

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();                    //表示任務是否被取消成功,如果在任務正常完成前被取消成功,則返回 true。
    boolean isDone();                         //任務是否已經完成,若任務完成,則返回true
    V get() throws InterruptedException, ExecutionException; //獲取執行結果,這個方法會阻塞
    V get(long timeout, TimeUnit unit)                       //在指定時間內,還沒獲取到結果,就直接返回null
        throws InterruptedException, ExecutionException, TimeoutException;
}

線程池: 

  線程池就是首先創建一些線程,它們的集合稱爲線程池。使用線程池可以很好地提高性能,線程池在系統啓動時即創建大量空閒的線程,程序將一個任務傳給線程池,線程池就會啓動一條線程來執行這個任務,執行結束以後,該線程並不會死亡,而是再次返回線程池中成爲空閒狀態,等待執行下一個任務。

 多線程運行時間,系統不斷的啓動和關閉新線程,成本非常高,會過渡消耗系統資源,以及過渡切換線程的危險,從而可能導致系統資源的崩潰。這時,線程池就是最好的選擇了。

線程池的返回值ExecutorService簡介:

         ExecutorService是Java提供的用於管理線程池的類。該類的兩個作用:控制線程數量和重用線程

//返回一個帶緩存的線程池,該池在必要的時候創建線程,在線程空閒60s後終止線程
ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); 
cachedThreadPool.execute(new Runnable(){public void run() {};});

//返回一個線程池,線程數目由threads參數指明
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int theads);
fixedThreadPool.execute(new Runnable(){public void run() {};});

//返回只含一個線程的線程池,它在一個單一的線程中依次執行各個任務
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
singleThreadExecutor.execute(new Runnable(){public void run() {};});

//創建一個定長線程池,支持定時及週期性任務執行。
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(int theads);
scheduledThreadPool.schedule(new Runnable() {
   public void run() {
      System.out.println("延遲1秒執行");
   }
}, 1, TimeUnit.SECONDS);

scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
   public void run() {
      System.out.println("延遲1秒後每3秒執行一次");
   }
}, 1, 3, TimeUnit.SECONDS);
  • newCachedThreadPool():先查看池中有沒有以前建立的線程,如果有,就直接使用。如果沒有,就建一個新的線程加入池中,緩存型池子通常用於執行一些生存期很短的異步型任務.
  • newFixedThreadPool(int n):線程數目由創建時指定,並一直保持不變。若提交給它的任務多於線程池中的空閒線程數目,那麼就會把任務放到隊列中,當其他任務執行完畢後再來執行它們;
  • newSingleThreadExecutor():返回一個大小爲1的線程池,由一個線程執行提交的任務。
  • newScheduledThreadPool(int n):創建一個定長線程池,支持定時及週期性任務執行

同步器(Synchronizer)

 java.util.concurrent包提供了幾個幫助我們管理相互合作的線程集的類,這些類的主要功能和適用場景如下:

  • CyclicBarrier:它允許線程集等待直至其中預定數目的線程到達某個狀態(這個狀態叫公共障柵(barrier)),然後可以選擇執行一個處理障柵的動作。適用場景:當多個線程都完成某操作,這些線程才能繼續執行時,或都完成了某操作後才能執行指定任務時。對CyclicBarrier對象調用await方法即可讓相應線程進入barrier狀態,等到預定數目的線程都進入了barrier狀態後,這些線程就可以繼續往下執行了
//構造函數,parties 是參與線程的個數,Runnable 參數,最後一個到達線程要做的任務。
CyclicBarrier(int parties)
CyclicBarrier(int parties, Runnable barrierAction)

//線程調用await()表示到達柵欄,Exception 表示柵欄已經被破壞,可能是其中一個線程await()時被中斷或者超時
int await() throws InterruptedException, BrokenBarrierException
int await(long timeout, TimeUnit unit) throws InterruptedException,BrokenBarrierException, TimeoutException
  • CountDownLatch:使一個線程等待其他線程各自執行完畢後再執行。是通過一個計數器來實現的,計數器的初始值是線程的數量。每當一個線程執行完畢後,計數器的值就-1,當計數器的值爲0時,表示所有線程都執行完畢,然後在閉鎖上等待的線程就可以恢復工作了。
public void await() throws InterruptedException { };   //調用該方法的線程會進入阻塞狀態,直到count值爲0才繼續執行

//和await()類似,只不過等待一定的時間後count值還沒變爲0的話就會繼續執行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };

public void countDown() { };  //將CountDownLatch對象count值(初始化時作爲參數傳入構造方法)減1
  • Exchanger:一個線程在完成一定的事務後想與另一個線程交換數據,則第一個先拿出數據的線程會一直等待第二個線程,直到第二個線程拿着數據到來時才能彼此交換對應數據。
Exchanger<Integer> exchanger = new Exchanger<Integer>();

//等待另一個線程到達此交換點(除非當前線程被中斷),然後將給定的對象傳送給該線程,並接收該線程的對象,否則阻塞。
data = exchanger.exchange(int);

//等待另一個線程到達此交換點(除非當前線程被中斷或超出了指定的等待時間),然後將給定的對象傳送給該線程,並接收該線程的對象。
exchange(V v, long timeout, TimeUnit unit):
  • Semaphore:可以控制同時訪問資源的線程個數
void acquire():從此信號量獲取一個許可,在提供一個許可前一直將線程阻塞,否則線程被中斷。

void release():釋放一個許可,將其返回給信號量。

int availablePermits():返回此信號量中當前可用的許可數。

boolean hasQueuedThreads():查詢是否有線程正在等待獲取。
  • SynchronousQueue:允許一個線程把對象交給另一個線程。適用場景:在沒有顯式同步的情況下,當兩個線程準備好將一個對象從一個線程傳遞到另一個線程。

反射

JAVA反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱爲Java語言的反射機制。

要想解剖一個類,必須先要獲取到該類的字節碼文件對象。既Class clz = Class.forName("包名.類名");

反射就是在運行時才知道要操作的類是什麼,並且可以在運行時獲取類的完整構造,並調用對應的方法。

反射就是把Java類中的各種成分映射成一個個的Java對象。

//獲取類的 Class 對象實例
Class clz = Class.forName("包名.類名");
//根據 Class 對象實例獲取 Constructor 對象
Constructor phoneConstructor = clz.getConstructor();
//使用 Constructor 對象的 newInstance 方法獲取反射類對象
Object phoneObj = phoneConstructor.newInstance();
//獲取方法的 Method 對象
Method setPriceMethod = clz.getMethod("setPrice", int.class);
//利用 invoke 方法調用方法
setPriceMethod.invoke(phoneObj, 6000);

new與反射區別

  1. new屬於靜態編譯,而反射屬於動態編譯,new時所有模塊都加載了,而反射是用到的時候才加載。
  2. new出來的對象,無法反問它的私有屬性,而反射可以(通過setAccessible()取訪問)
  3. new關鍵字是強類型的,效率相對較高。 反射是弱類型的,效率低。
  4. 反射提供了一種更加靈活的方式創建對象,得到對象的信息。EventBus框架,通過反射獲取類中"onEvent"開頭的訂閱方法。

單鏈表逆置

//單鏈表定義

typedef struct ListNode{

        int m_nValue;

        ListNode* pNext;

};

//單鏈表逆置實現
ListNode* ReverseList(ListNode* pHead)
{
    if (pHead == NULL || pHead->pNext == NULL)
    {
        retrun pHead;
    }
 
    ListNode* finalList = NULL;

    ListNode* originList = pHead;

    while(originList != NULL)

    {

        ListNode* tempList = originList;   // 步驟①

        originList = originList->pNext;       // 步驟②

        tempList -> next = finalList;      // 步驟③

        finalList = tempList;

    }
    return finalList;
}

排序算法:

插入排序:

直接插入排序基本思想是每一步將一個待排序的記錄,插入到前面已經排好序的有序序列中去,直到插完所有元素爲止。

當數據正序時,執行效率最好,每次插入都不用移動前面的元素,時間複雜度爲O(N)。
當數據反序時,執行效率最差,每次插入都要前面的元素後移,時間複雜度爲O(N^2)。

希爾排序:

第一趟排序中,通過計算gap1=N/2(即10/2),將10個元素分爲5組,即(9,4),(1,8),(2,6),(5,3),(7,5),然後對每組內的元素進行插入排序。
第二趟排序中,把上次的 gap 縮小一半,即 gap2 = gap1 / 2 = 2 (取整數)。這樣每相隔距離爲 2 的元素組成一組,可以分爲 2 組。分組後依舊對每組的元素進行插入排序。
第三趟排序中,再次把 gap 縮小一半,即gap3 = gap2 / 2 = 1。 這樣相隔距離爲 1 的元素組成一組,即只有一組。再進行一次插入排序。
需要注意的是,圖中有兩個相等數值的元素 5 和 5 。我們可以清楚的看到,在排序過程中,兩個元素位置交換了。所以,希爾排序是不穩定的算法。

時間複雜度爲O(N^(1.3—2))

冒泡排序算法:
1.比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
2.對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對。在這一點,最後的元素會是最大的數。

其時間複雜度依然爲O(N^2)

選擇排序:
1.從待排序序列中,找到最小的元素;
2.如果最小元素不是待排序序列的第一個元素,將其和待排序序列的第一個元素互換;

時間複雜度爲 O(N*2)

遞歸通常用棧來實現。

異常:

所有的異常都是繼承Throwable的,自定義異常不可以繼承自Error。

URI與URL

URL 比較實體   表示一個具體的

URI 比較抽象 表示一個相對的意思

URL --   比如 http://www.baidu.com/124/123    是一個絕對的路徑

URI -- 比如 /124/123 是一個相對的路徑

泛型中的限定通配符和非限定通配符:

限定通配符包括兩種:
1. 表示類型的上界,格式爲:<? extends T>,即類型必須爲T類型或者T子類
2. 表示類型的下界,格式爲:<? super T>,即類型必須爲T類型或者T的父類

非限定通配符:類型爲<T>,可以用任意類型來替代。

迭代器(Iterator)

  迭代器是一種設計模式,它是一個對象,它可以遍歷並選擇序列中的對象,而開發人員不需要了解該序列的底層結構。迭代器通常被稱爲“輕量級”對象,因爲創建它的代價小。

  Java中的Iterator功能比較簡單,並且只能單向移動:

  (1) 使用方法iterator()要求容器返回一個Iterator。第一次調用Iterator的next()方法時,它返回序列的第一個元素。注意:iterator()方法是java.lang.Iterable接口,被Collection繼承。

  (2) 使用next()獲得序列中的下一個元素。

  (3) 使用hasNext()檢查序列中是否還有元素。

  (4) 使用remove()將迭代器新返回的元素刪除。

  Iterator是Java迭代器最簡單的實現,爲List設計的ListIterator具有更多的功能,它可以從兩個方向遍歷List,也可以從List中插入和刪除元素。

File實現了 Serializable,可以用Intent傳遞。

android:screenOrientation屬性:

  • unspecified——默認值,由系統選擇顯示方向,在不同的設備可能會有所不同。
  • landscape——橫向
  • portrait——縱向
  • user——用戶當前的首選方向
  • behind——與在活動堆棧下的活動相同方向
  • sensor——根據物理方向傳感器確定方向,取決於用戶手持的方向,當用戶轉動設備,他能隨意改變。
  • nosensor——不經物理方向傳感器確定方向,該傳感器被忽略,所以當用戶轉動設備,顯示不會跟隨改變,除了這個卻別,系統選擇相同的政策取向對於“未指定”設置,系統根據“未指定”(unspecified)設定選擇相同顯示方向。

從 Android 3.2 (API級別 13)以後

  1. 不設置Activity的android:configChanges時,或 設置Activity的android:configChanges="orientation"時,或設置Activity的android:configChanges="orientation|keyboardHidden"時,切屏會重新調用各個生命週期,切橫屏時會執行一次,切豎屏時會執行一次方法。
  2. 配置 android:configChanges="orientation|screenSize",纔不會銷燬 activity,且只調用 onConfigurationChanged方法。

圖片加載庫對比

Picasso:120K

Glide:475K

Fresco:3.4M

Android-Universal-Image-Loader:162K

圖片函數庫的選擇需要根據APP的具體情況而定,對於嚴重依賴圖片緩存的APP,例如壁紙類,圖片社交類APP來說,可以選擇最專業的Fresco。對於一般的APP,選擇Fresco會顯得比較重,畢竟Fresco3.4M的體量擺在這。根據APP對圖片的顯示和緩存的需求從低到高,我們可以對以上函數庫做一個排序。

Picasso < Android-Universal-Image-Loader < Glide < Fresco

Picasso所能實現的功能,Glide都能做,無非是所需的設置不同。但是Picasso體積比起Glide小太多如果項目中網絡請求本身用的就是okhttp或者retrofit(本質還是okhttp),那麼建議用Picasso,體積會小很多(Square全家桶的幹活)。Glide的好處是大型的圖片流,比如gif、Video,如果你們是做美拍、愛拍這種視頻類應用,建議使用。

ANR(Application Not Responding):

應用程序無響應:在一定的時間內沒有做完相應的處理。

應用程序的響應性是由Activity Manager和WindowManager系統服務監視的 。

響應輸入input的事件時間超過5S,broadcastReceiver超過10S,前臺service處理超過20S,後臺service超過200S

線程中start()和run()的區別

start():啓動相應的線程,讓一個線程進入就緒隊列等待分配cpu,分到cpu後才調用實現的run()方法。

run():線程體,包含了線程要執行的內容。直接調用run只是一個普通的函數調用,並沒有新建線程的作用。

 

普通內部類如何訪問外部類:

  1. 編譯器自動爲內部類生成一個帶參構造方法, 參數只有一個,類型是外部類。
  2. 編譯器自動爲內部類添加一個成員變量,通過第一步的構造函數賦值, 這個成員變量就是指向外部類對象的引用;
  3. 內部類通過該引用訪問外部類屬性。

線程阻塞與喚醒方法:

    1. sleep() 方法    不釋放鎖

  sleep(毫秒),指定以毫秒爲單位的時間,使線程在該時間內進入線程阻塞狀態,期間得不到cpu的時間片,等到時間過去了,線程重新進入可執行狀態。不會釋放資源。(暫停線程,不會釋放鎖)

  2.suspend() 和 resume() 方法

  掛起和喚醒線程,suspend()使線程進入阻塞狀態,只有對應的resume()被調用的時候,線程纔會進入可執行狀態。(不建議用,容易發生死鎖)

  3. yield() 方法     不釋放鎖

  會使的線程放棄當前分得的cpu時間片,但此時線程任然處於可執行狀態,隨時可以再次分得cpu時間片。yield()方法只能使同優先級的線程有執行的機會。調用 yield()的效果等價於調度程序認爲該線程已執行了足夠的時間從而轉到另一個線程。(暫停當前正在執行的線程,並執行其他線程,且讓出的時間不可知)

  4.wait() 和 notify() 方法       底層調用了wait,釋放鎖

  兩個方法搭配使用,wait()使線程進入阻塞狀態,調用notify()時,線程進入可執行狀態。wait()內可加或不加參數,加參數時是以毫秒爲單位,當到了指定時間或調用notify()方法時,進入可執行狀態。(屬於Object類,而不屬於Thread類,wait()會先釋放鎖住的對象,然後再執行等待的動作。由於wait()所等待的對象必須先鎖住,因此,它只能用在同步化程序段或者同步化方法內,否則,會拋出異常IllegalMonitorStateException.)

  5.join()方法          釋放鎖

  也叫線程加入。是當前線程A調用另一個線程B的join()方法,當前線程轉A入阻塞狀態,直到線程B運行結束,線程A才由阻塞狀態轉爲可執行狀態。

sleep和wait的區別有
  1,這兩個方法來自不同的類分別是Thread和Object
  2,最主要是sleep方法沒有釋放鎖,而wait方法釋放了鎖,使得其他線程可以使用同步控制塊或者方法。
  3,wait,notify和notifyAll只能在同步控制方法或者同步控制塊裏面使用,而sleep可以在任何地方使用

synchronized(x){
      x.notify()
     //或者wait()
   }

volatile關鍵字

所有線程的共享變量都存儲在主存(既內存)中,每一個線程都有一個獨有的工作內存,每個線程不直接操作在主內存中的變量,而是將主內存上變量的副本放進自己的工作內存中,只操作工作內存中的數據。當修改完畢後,再把修改後的結果放回到主內存中。這就導致多線程的環境下可能會出現髒數據,加上volatile關鍵字修飾的話,它可以保證當線程對變量值做了變動之後,會立即刷回到主內存中,而其它線程讀取到該變量的值也作廢,強迫重新從主內存中讀取該變量的值,這樣在任何時刻,線程總是會看到變量的同一個值。

synchronized關鍵字

java中共有兩種類型的鎖:

  • 類鎖:只有synchronized修飾靜態方法或者修飾一個類的class對象時,纔是類鎖。
  • 對象鎖:處了類鎖,所有其他的上鎖方式都認爲是對象鎖。比如synchronized修飾普通方法或者synchronized(this)給代碼塊上鎖等

規則:

  • 加了相同鎖的東西,它們的訪問規則是相同的,即當某個訪問者獲得該鎖時,它們一起向該訪問者開放訪問,向其他沒有獲得該鎖的訪問者關閉訪問。
  • 加了不同鎖的東西訪問互相不干擾 。
  • 而沒有加鎖的東西隨時都可以任意訪問,不受任何限制。

判斷:

  • 不同類型的鎖不是同一把鎖。
  • 加的是對象鎖,那麼必須是同一個對象實例纔是同一把鎖 。
  • 加的是類鎖,那必須是同一類纔是同一把鎖。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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