多線程專題

2019Android多線程總結(jié)

1.什么是線程

線程就是進程中運行的多個子任務(wù),是操作系統(tǒng)調(diào)用的最小單元

2.線程的狀態(tài)

New:新建狀態(tài),new出來,還沒有調(diào)用start
Runnable:可運行狀態(tài),調(diào)用start進入可運行狀態(tài),可能運行也可能沒有運行,取決于操作系統(tǒng)的調(diào)度
Blocked:阻塞狀態(tài),被鎖阻塞,暫時不活動,阻塞狀態(tài)是線程阻塞在進入synchronized關(guān)鍵字修飾的方法或代碼塊(獲取鎖)時的狀態(tài)。
Waiting:等待狀態(tài),不活動,不運行任何代碼,等待線程調(diào)度器調(diào)度,wait sleep
Timed Waiting:超時等待,在指定時間自行返回
Terminated:終止狀態(tài),包括正常終止和異常終止

3.線程的創(chuàng)建

a.繼承Thread重寫run方法
b.實現(xiàn)Runnable重寫run方法
c.實現(xiàn)Callable重寫call方法

實現(xiàn)Callable和實現(xiàn)Runnable類似,但是功能更強大,具體表現(xiàn)在
a.可以在任務(wù)結(jié)束后提供一個返回值,Runnable不行
b.call方法可以拋出異常,Runnable的run方法不行
c.可以通過運行Callable得到的Fulture對象監(jiān)聽目標線程調(diào)用call方法的結(jié)果,得到返回值,(fulture.get(),調(diào)用后會阻塞,直到獲取到返回值)

4.線程中斷

一般情況下,線程不執(zhí)行完任務(wù)不會退出,但是在有些場景下,我們需要手動控制線程中斷結(jié)束任務(wù),Java中有提供線程中斷機制相關(guān)的Api,每個線程都一個狀態(tài)位用于標識當前線程對象是否是中斷狀態(tài)
public boolean isInterrupted() //判斷中斷標識位是否是true,不會改變標識位public void interrupt() //將中斷標識位設(shè)置為truepublic static boolean interrupted() //判斷當前線程是否被中斷,并且該方法調(diào)用結(jié)束的時候會清空中斷標識位
需要注意的是interrupt()方法并不會真的中斷線程,它只是將中斷標識位設(shè)置為true,具體是否要中斷由程序來判斷,如下,只要線程中斷標識位為false,也就是沒有中斷就一直執(zhí)行線程方法

new Thread(new Runnable(){
      while(!Thread.currentThread().isInterrupted()){
              //執(zhí)行線程方法
      }
}).start();

前邊我們提到了線程的六種狀態(tài),New Runnable Blocked Waiting Timed Waiting Terminated,那么在這六種狀態(tài)下調(diào)用線程中斷的代碼會怎樣呢,New和Terminated狀態(tài)下,線程不會理會線程中斷的請求,既不會設(shè)置標記位,在Runnable和Blocked狀態(tài)下調(diào)用interrupt會將標志位設(shè)置位true,在Waiting和Timed Waiting狀態(tài)下會發(fā)生InterruptedException異常,針對這個異常我們?nèi)绾翁幚恚?/p>

1.在catch語句中通過interrupt設(shè)置中斷狀態(tài),因為發(fā)生中斷異常時,中斷標志位會被復位,我們需要重新將中斷標志位設(shè)置為true,這樣外界可以通過這個狀態(tài)判斷是否需要中斷線程

try{
    ....
}catch(InterruptedException e){
    Thread.currentThread().interrupt();
}

2.更好的做法是,不捕獲異常,直接拋出給調(diào)用者處理,這樣更靈活

5.Thread為什么不能用stop方法停止線程

從SUN的官方文檔可以得知,調(diào)用Thread.stop()方法是不安全的,這是因為當調(diào)用Thread.stop()方法時,會發(fā)生下面兩件事:

1.即刻拋出ThreadDeath異常,在線程的run()方法內(nèi),任何一點都有可能拋出ThreadDeath Error,包括在catch或finally語句中。

2.釋放該線程所持有的所有的鎖。調(diào)用thread.stop()后導致了該線程所持有的所有鎖的突然釋放,那么被保護數(shù)據(jù)就有可能呈現(xiàn)不一致性,其他線程在使用這些被破壞的數(shù)據(jù)時,有可能導致一些很奇怪的應(yīng)用程序錯誤。

6.重入鎖與條件對象,同步方法和同步代碼塊

。。。

7.volatile關(guān)鍵字

volatile為實例域的同步訪問提供了免鎖機制,如果聲明一個域為volatile,那么編譯器和虛擬機就直到該域可能被另一個線程并發(fā)更新

8.java內(nèi)存模型

堆內(nèi)存是被所有線程共享的運行時內(nèi)存區(qū)域,存在可見性的問題。線程之間共享變量存儲在主存中,每個線程都有一個私有的本地內(nèi)存,本地內(nèi)存存儲了該線程共享變量的副本(本地內(nèi)存是一個抽象概念,并不真實存在),兩個線程要通信的話,首先A線程把本地內(nèi)存更新過的共享變量更新到主存中,然后B線程去主存中讀取A線程更新過的共享變量,也就是說假設(shè)線程A執(zhí)行了i = 1這行代碼更新主線程變量i的值,會首先在自己的工作線程中堆變量i進行賦值,然后再寫入主存當中,而不是直接寫入主存

9.原子性 可見性 有序性

原子性:對基本數(shù)據(jù)類型的讀取和賦值操作是原子性操作,這些操作不可被中斷,是一步到位的,例如x=3是原子性操作,而y = x就不是,它包含兩步:第一讀取x,第二將x寫入工作內(nèi)存;x++也不是原子性操作,它包含三部,第一,讀取x,第二,對x加1,第三,寫入內(nèi)存。原子性操作的類如:AtomicInteger AtomicBoolean AtomicLong AtomicReference
可見性:指線程之間的可見性,既一個線程修改的狀態(tài)對另一個線程是可見的。volatile修飾可以保證可見性,它會保證修改的值會立即被更新到主存,所以對其他線程是可見的,普通的共享變量不能保證可見性,因為被修改后不會立即寫入主存,何時被寫入主存是不確定的,所以其他線程去讀取的時候可能讀到的還是舊值
有序性:Java中的指令重排序(包括編譯器重排序和運行期重排序)可以起到優(yōu)化代碼的作用,但是在多線程中會影響到并發(fā)執(zhí)行的正確性,使用volatile可以保證有序性,禁止指令重排
volatile可以保證可見性 有序性,但是無法保證原子性,在某些情況下可以提供優(yōu)于鎖的性能和伸縮性,替代sychronized關(guān)鍵字簡化代碼,但是要嚴格遵循使用條件。

10.線程池ThreadPoolExecutor

線程池的工作原理:線程池可以減少創(chuàng)建和銷毀線程的次數(shù),從而減少系統(tǒng)資源的消耗,當一個任務(wù)提交到線程池時
a. 首先判斷核心線程池中的線程是否已經(jīng)滿了,如果沒滿,則創(chuàng)建一個核心線程執(zhí)行任務(wù),否則進入下一步
b. 判斷工作隊列是否已滿,沒有滿則加入工作隊列,否則執(zhí)行下一步
c. 判斷線程數(shù)是否達到了最大值,如果不是,則創(chuàng)建非核心線程執(zhí)行任務(wù),否則執(zhí)行飽和策略,默認拋出異常

11.線程池的種類

1.FixedThreadPool:可重用固定線程數(shù)的線程池,只有核心線程,沒有非核心線程,核心線程不會被回收,有任務(wù)時,有空閑的核心線程就用核心線程執(zhí)行,沒有則加入隊列排隊
2.SingleThreadExecutor:單線程線程池,只有一個核心線程,沒有非核心線程,當任務(wù)到達時,如果沒有運行線程,則創(chuàng)建一個線程執(zhí)行,如果正在運行則加入隊列等待,可以保證所有任務(wù)在一個線程中按照順序執(zhí)行,和FixedThreadPool的區(qū)別只有數(shù)量
3.CachedThreadPool:按需創(chuàng)建的線程池,沒有核心線程,非核心線程有Integer.MAX_VALUE個,每次提交
任務(wù)如果有空閑線程則由空閑線程執(zhí)行,沒有空閑線程則創(chuàng)建新的線程執(zhí)行,適用于大量的需要立即處理的并且耗時較短的任務(wù)
4.ScheduledThreadPoolExecutor:繼承自ThreadPoolExecutor,用于延時執(zhí)行任務(wù)或定期執(zhí)行任務(wù),核心線程數(shù)固定,線程總數(shù)為Integer.MAX_VALUE

12.線程同步機制與原理,舉例說明

為什么需要線程同步?
當多個線程操作同一個變量的時候,存在這個變量何時對另一個線程可見的問題,也就是可見性。每一個線程都持有主存中變量的一個副本,當他更新這個變量時,首先更新的是自己線程中副本的變量值,然后會將這個值更新到主存中,但是是否立即更新以及更新到主存的時機是不確定的,這就導致當另一個線程操作這個變量的時候,他從主存中讀取的這個變量還是舊的值,導致兩個線程不同步的問題。線程同步就是為了保證多線程操作的可見性和原子性,比如我們用synchronized關(guān)鍵字包裹一端代碼,我們希望這段代碼執(zhí)行完成后,對另一個線程立即可見,另一個線程再次操作的時候得到的是上一個線程更新之后的內(nèi)容,還有就是保證這段代碼的原子性,這段代碼可能涉及到了好幾部操作,我們希望這好幾步的操作一次完成不會被中間打斷,鎖的同步機制就可以實現(xiàn)這一點。一般說的synchronized用來做多線程同步功能,其實synchronized只是提供多線程互斥,而對象的wait()和notify()方法才提供線程的同步功能。JVM通過Monitor對象實現(xiàn)線程同步,當多個線程同時請求synchronized方法或塊時,monitor會設(shè)置幾個虛擬邏輯數(shù)據(jù)結(jié)構(gòu)來管理這些多線程。新請求的線程會首先被加入到線程排隊隊列中,線程阻塞,當某個擁有鎖的線程unlock之后,則排隊隊列里的線程競爭上崗(synchronized是不公平競爭鎖,下面還會講到)。如果運行的線程調(diào)用對象的wait()后就釋放鎖并進入wait線程集合那邊,當調(diào)用對象的notify()或notifyall()后,wait線程就到排隊那邊。這是大致的邏輯。

13.arrayList與linkedList的讀寫時間復雜度

(1)ArrayList:ArrayList是一個泛型類,底層采用數(shù)組結(jié)構(gòu)保存對象。數(shù)組結(jié)構(gòu)的優(yōu)點是便于對集合進行快速的隨機訪問,即如果需要經(jīng)常根據(jù)索引位置訪問集合中的對象,使用由ArrayList類實現(xiàn)的List集合的效率較好。數(shù)組結(jié)構(gòu)的缺點是向指定索引位置插入對象和刪除指定索引位置對象的速度較慢,并且插入或刪除對象的索引位置越小效率越低,原因是當向指定的索引位置插入對象時,會同時將指定索引位置及之后的所有對象相應(yīng)的向后移動一位。
(2)LinkedList:LinkedList是一個泛型類,底層是一個雙向鏈表,所以它在執(zhí)行插入和刪除操作時比ArrayList更加的高效,但也因為鏈表的數(shù)據(jù)結(jié)構(gòu),所以在隨機訪問方面要比ArrayList差。
ArrayList 是線性表(數(shù)組)
get() 直接讀取第幾個下標,復雜度 O(1)
add(E) 添加元素,直接在后面添加,復雜度O(1)
add(index, E) 添加元素,在第幾個元素后面插入,后面的元素需要向后移動,復雜度O(n)
remove()刪除元素,后面的元素需要逐個移動,復雜度O(n)
LinkedList 是鏈表的操作
get() 獲取第幾個元素,依次遍歷,復雜度O(n)
add(E) 添加到末尾,復雜度O(1)
add(index, E) 添加第幾個元素后,需要先查找到第幾個元素,直接指針指向操作,復雜度O(n)
remove()刪除元素,直接指針指向操作,復雜度O(1)

14.為什么HashMap線程不安全(hash碰撞與擴容導致)

HashMap的底層存儲結(jié)構(gòu)是一個Entry數(shù)組,每個Entry又是一個單鏈表,一旦發(fā)生Hash沖突的的時候,HashMap采用拉鏈法解決碰撞沖突,因為hashMap的put方法不是同步的,所以他的擴容方法也不是同步的,在擴容過程中,會新生成一個新的容量的數(shù)組,然后對原數(shù)組的所有鍵值對重新進行計算和寫入新的數(shù)組,之后指向新生成的數(shù)組。當多個線程同時檢測到hashmap需要擴容的時候就會同時調(diào)用resize操作,各自生成新的數(shù)組并rehash后賦給該map底層的數(shù)組table,結(jié)果最終只有最后一個線程生成的新數(shù)組被賦給table變量,其他線程的均會丟失。而且當某些線程已經(jīng)完成賦值而其他線程剛開始的時候,就會用已經(jīng)被賦值的table作為原始數(shù)組,這樣也會有問題。擴容的時候 可能會引發(fā)鏈表形成環(huán)狀結(jié)構(gòu)

15.進程線程的區(qū)別

1.地址空間:同一進程的線程共享本進程的地址空間,而進程之間則是獨立的地址空間。
2.資源擁有:同一進程內(nèi)的線程共享本進程的資源如內(nèi)存、I/O、cpu等,但是進程之間的資源是獨立的。
3.一個進程崩潰后,在保護模式下不會對其他進程產(chǎn)生影響,但是一個線程崩潰整個進程都死掉。所以多進程要比多線程健壯。
4.進程切換時,消耗的資源大,效率不高。所以涉及到頻繁的切換時,使用線程要好于進程。同樣如果要求同時進行并且又要共享某些變量的并發(fā)操作,只能用線程不能用進程
5.執(zhí)行過程:每個獨立的進程程有一個程序運行的入口、順序執(zhí)行序列和程序入口。但是線程不能獨立執(zhí)行,必須依存在應(yīng)用程序中,由應(yīng)用程序提供多個線程執(zhí)行控制。
6.線程是處理器調(diào)度的基本單位,但是進程不是。
7.兩者均可并發(fā)執(zhí)行。

16.Binder的內(nèi)存拷貝過程

相比其他的IPC通信,比如消息機制、共享內(nèi)存、管道、信號量等,Binder僅需一次內(nèi)存拷貝,即可讓目標進程讀取到更新數(shù)據(jù),同共享內(nèi)存一樣相當高效,其他的IPC通信機制大多需要2次內(nèi)存拷貝。Binder內(nèi)存拷貝的原理為:進程A為Binder客戶端,在IPC調(diào)用前,需將其用戶空間的數(shù)據(jù)拷貝到Binder驅(qū)動的內(nèi)核空間,由于進程B在打開Binder設(shè)備(/dev/binder)時,已將Binder驅(qū)動的內(nèi)核空間映射(mmap)到自己的進程空間,所以進程B可以直接看到Binder驅(qū)動內(nèi)核空間的內(nèi)容改動

17.傳統(tǒng)IPC機制的通信原理(2次內(nèi)存拷貝)

1.發(fā)送方進程通過系統(tǒng)調(diào)用(copy_from_user)將要發(fā)送的數(shù)據(jù)存拷貝到內(nèi)核緩存區(qū)中。
2.接收方開辟一段內(nèi)存空間,內(nèi)核通過系統(tǒng)調(diào)用(copy_to_user)將內(nèi)核緩存區(qū)中的數(shù)據(jù)拷貝到接收方的內(nèi)存緩存區(qū)。
種傳統(tǒng)IPC機制存在2個問題:
1.需要進行2次數(shù)據(jù)拷貝,第1次是從發(fā)送方用戶空間拷貝到內(nèi)核緩存區(qū),第2次是從內(nèi)核緩存區(qū)拷貝到接收方用戶空間。
2.接收方進程不知道事先要分配多大的空間來接收數(shù)據(jù),可能存在空間上的浪費。

18.Java內(nèi)存模型(記住堆棧是內(nèi)存分區(qū),不是模型)

Java內(nèi)存模型(即Java Memory Model,簡稱JMM)本身是一種抽象的概念,并不真實存在,它描述的是一組規(guī)則或規(guī)范,通過這組規(guī)范定義了程序中各個變量(包括實例字段,靜態(tài)字段和構(gòu)成數(shù)組對象的元素)的訪問方式。由于JVM運行程序的實體是線程,而每個線程創(chuàng)建時JVM都會為其創(chuàng)建一個工作內(nèi)存(有些地方稱為??臻g),用于存儲線程私有的數(shù)據(jù),而Java內(nèi)存模型中規(guī)定所有變量都存儲在主內(nèi)存,主內(nèi)存是共享內(nèi)存區(qū)域,所有線程都可以訪問,但線程對變量的操作(讀取賦值等)必須在工作內(nèi)存中進行,首先要將變量從主內(nèi)存拷貝的自己的工作內(nèi)存空間,然后對變量進行操作,操作完成后再將變量寫回主內(nèi)存,不能直接操作主內(nèi)存中的變量,工作內(nèi)存中存儲著主內(nèi)存中的變量副本拷貝,前面說過,工作內(nèi)存是每個線程的私有數(shù)據(jù)區(qū)域,因此不同的線程間無法訪問對方的工作內(nèi)存,線程間的通信(傳值)必須通過主內(nèi)存來完成

19.類的加載過程

類加載過程主要包含加載、驗證、準備、解析、初始化、使用、卸載七個方面,下面一一闡述。
1.加載:獲取定義此類的二進制字節(jié)流,生成這個類的java.lang.Class對象
2.驗證:保證Class文件的字節(jié)流包含的信息符合JVM規(guī)范,不會給JVM造成危害
3.準備:準備階段為變量分配內(nèi)存并設(shè)置類變量的初始化
4.解析:解析過程是將常量池內(nèi)的符號引用替換成直接引用
5.初始化:不同于準備階段,本次初始化,是根據(jù)程序員通過程序制定的計劃去初始化類的變量和其他資源。這些資源有static{}塊,構(gòu)造函數(shù),父類的初始化等
6.使用:使用過程就是根據(jù)程序定義的行為執(zhí)行
7.卸載:卸載由GC完成。

20.什么情況下會觸發(fā)類的初始化

1、遇到new,getstatic,putstatic,invokestatic這4條指令;
2、使用java.lang.reflect包的方法對類進行反射調(diào)用;
3、初始化一個類的時候,如果發(fā)現(xiàn)其父類沒有進行過初始化,則先初始化其父類(注意!如果其父類是接口的話,則不要求初始化父類);
4、當虛擬機啟動時,用戶需要指定一個要執(zhí)行的主類(包含main方法的那個類),虛擬機會先初始化這個主類;
5、當使用jdk1.7的動態(tài)語言支持時,如果一個java.lang.invoke.MethodHandle實例最后的解析結(jié)果REF_getstatic,REF_putstatic,REF_invokeStatic的方法句柄,并且這個方法句柄所對應(yīng)的類沒有進行過初始化,則先觸發(fā)其類初始化;

21.雙親委托模式

類加載器查找class所采用的是雙親委托模式,所謂雙親委托模式就是判斷該類是否已經(jīng)加載,如果沒有則不是自身去查找而是委托給父加載器進行查找,這樣依次進行遞歸,直到委托到最頂層的Bootstrap ClassLoader,如果Bootstrap ClassLoader找到了該Class,就會直接返回,如果沒找到,則繼續(xù)依次向下查找,如果還沒找到則最后交給自身去查找

22.雙親委托模式的好處

1.避免重復加載,如果已經(jīng)加載過一次Class,則不需要再次加載,而是直接讀取已經(jīng)加載的Class
2.更加安全,確保,java核心api中定義類型不會被隨意替換,比如,采用雙親委托模式可以使得系統(tǒng)在Java虛擬機啟動時舊加載了String類,也就無法用自定義的String類來替換系統(tǒng)的String類,這樣便可以防止核心API庫被隨意篡改。

23.死鎖的產(chǎn)生條件,如何避免死鎖

死鎖的四個必要條件
1.互斥條件:一個資源每次只能被一個進程使用
2.請求與保持條件:進程已經(jīng)保持了至少一個資源,但又提出了新的資源請求,而該資源 已被其他進程占有,此時請求進程被阻塞,但對自己已獲得的資源保持不放。
3.不可剝奪條件:進程所獲得的資源在未使用完畢之前,不能被其他進程強行奪走,即只能 由獲得該資源的進程自己來釋放(只能是主動釋放)。
4.循環(huán)等待條件: 若干進程間形成首尾相接循環(huán)等待資源的關(guān)系
這四個條件是死鎖的必要條件,只要系統(tǒng)發(fā)生死鎖,這些條件必然成立,而只要上述條件之一不滿足,就不會發(fā)生死鎖。
避免死鎖的方法:系統(tǒng)對進程發(fā)出每一個系統(tǒng)能夠滿足的資源申請進行動態(tài)檢查,并根據(jù)檢查結(jié)果決定是否分配資源,如果分配后系統(tǒng)可能發(fā)生死鎖,則不予分配,否則予以分配,這是一種保證系統(tǒng)不進入死鎖狀態(tài)的動態(tài)策略。
在資源的動態(tài)分配過程中,用某種方法去防止系統(tǒng)進入不安全狀態(tài),從而避免發(fā)生死鎖。 一般來說互斥條件是無法破壞的,所以在預防死鎖時主要從其他三個方面入手 :
(1)破壞請求和保持條件:在系統(tǒng)中不允許進程在已獲得某種資源的情況下,申請其他資源,即要想出一個辦法,阻止進程在持有資源的同時申請其它資源。
方法一:在所有進程開始運行之前,必須一次性的申請其在整個運行過程中所需的全部資源,
方法二:要求每個進程提出新的資源申請前,釋放它所占有的資源
(2)破壞不可搶占條件:允許對資源實行搶奪。
方式一:如果占有某些資源的一個進程進行進一步資源請求被拒絕,則該進程必須釋放它最初占有的資源,如果有必要,可再次請求這些資源和另外的資源。
方式二:如果一個進程請求當前被另一個進程占有的資源,則操作系統(tǒng)可以搶占另一個進程,要求它釋放資源,只有在任意兩個進程的優(yōu)先級都不相同的條件下,該方法才能預防死鎖。
(3)破壞循環(huán)等待條件
對系統(tǒng)所有資源進行線性排序并賦予不同的序號,這樣我們便可以規(guī)定進程在申請資源時必須按照序號遞增的順序進行資源的申請,當以后要申請時需檢查要申請的資源的編號大于當前編號時,才能進行申請。
利用銀行家算法避免死鎖:
所謂銀行家算法,是指在分配資源之前先看清楚,資源分配后是否會導致系統(tǒng)死鎖。如果會死鎖,則不分配,否則就分配。
按照銀行家算法的思想,當進程請求資源時,系統(tǒng)將按如下原則分配系統(tǒng)資源:

24.App啟動流程

App啟動時,AMS會檢查這個應(yīng)用程序所需要的進程是否存在,不存在就會請求Zygote進程啟動需要的應(yīng)用程序進程,Zygote進程接收到AMS請求并通過fock自身創(chuàng)建應(yīng)用程序進程,這樣應(yīng)用程序進程就會獲取虛擬機的實例,還會創(chuàng)建Binder線程池(ProcessState.startThreadPool())和消息循環(huán)(ActivityThread looper.loop),然后App進程,通過Binder IPC向sytem_server進程發(fā)起attachApplication請求;system_server進程在收到請求后,進行一系列準備工作后,再通過Binder IPC向App進程發(fā)送scheduleLaunchActivity請求;App進程的binder線程(ApplicationThread)在收到請求后,通過handler向主線程發(fā)送LAUNCH_ACTIVITY消息;主線程在收到Message后,通過反射機制創(chuàng)建目標Activity,并回調(diào)Activity.onCreate()等方法。到此,App便正式啟動,開始進入Activity生命周期,執(zhí)行完onCreate/onStart/onResume方法,UI渲染結(jié)束后便可以看到App的主界面。

25.Android單線程模型

Android單線程模型的核心原則就是:只能在UI線程(Main Thread)中對UI進行處理。當一個程序第一次啟動時,Android會同時啟動一個對應(yīng)的 主線程(Main Thread),主線程主要負責處理與UI相關(guān)的事件,如:用戶的按鍵事件,用戶接觸屏幕的事件以及屏幕繪圖事 件,并把相關(guān)的事件分發(fā)到對應(yīng)的組件進行處理。所以主線程通常又被叫做UI線 程。在開發(fā)Android應(yīng)用時必須遵守單線程模型的原則: Android UI操作并不是線程安全的并且這些操作必須在UI線程中執(zhí)行。
Android的單線程模型有兩條原則:
1.不要阻塞UI線程。
2.不要在UI線程之外訪問Android UI toolkit(主要是這兩個包中的組件:android.widget and android.view

26.RecyclerView在很多方面能取代ListView,Google為什么沒把ListView劃上一條過時的橫線?

ListView采用的是RecyclerBin的回收機制在一些輕量級的List顯示時效率更高。

27.HashMap如何保證元素均勻分布

hash & (length-1)
通過Key值的hashCode值和hashMap長度-1做與運算
hashmap中的元素,默認情況下,數(shù)組大小為16,也就是2的4次方,如果要自定義HashMap初始化數(shù)組長度,也要設(shè)置為2的n次方大小,因為這樣效率最高。因為當數(shù)組長度為2的n次冪的時候,不同的key算出的index相同的幾率較小,那么數(shù)據(jù)在數(shù)組上分布就比較均勻,也就是說碰撞的幾率小,相對的,查詢的時候就不用遍歷某個位置上的鏈表,這樣查詢效率也就較高了

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容