經(jīng)歷的某度的一場(chǎng)面試
如夢(mèng)朦朧
九月份的時(shí)候有了換工作的躁動(dòng),然后投了某度的Android崗位,本以為像我這種非211、985沒(méi)工作經(jīng)驗(yàn)的渣渣只能被直接pass,結(jié)果卻意外的收到了電話(huà),真是受寵若驚.經(jīng)過(guò)電面,技術(shù)三面,然后就是等通知到最后拿到了OFFER,如夢(mèng)一般,真是挺激動(dòng)的.
面試的準(zhǔn)備
當(dāng)收到HR的面試的通知還是很懵逼的,因?yàn)楦杏X(jué)自己突然啥都不會(huì)了,迅速鎮(zhèn)定下來(lái),去網(wǎng)上找了一下某度的面試題,但是發(fā)現(xiàn)都只有提問(wèn)了什么并沒(méi)有對(duì)所提問(wèn)題的解答,那只能自力更生,像做試卷一樣,一遍總結(jié)一遍溫顧.其實(shí)大多都是平時(shí)開(kāi)發(fā)中用到的,只是我們沒(méi)有總結(jié)過(guò),被問(wèn)起來(lái)的時(shí)候回答的難免會(huì)有點(diǎn)捉襟見(jiàn)肘,不能回答的很全面.下面為我個(gè)人總(bai)結(jié)(du)的,希望對(duì)你能有所幫助,但畢竟能力有限,有寫(xiě)的不對(duì)的地方,還望輕噴.雖然噴我我也不會(huì)改的.
因?yàn)楸疚钠^長(zhǎng)建議收藏,在用到時(shí)候找出來(lái)看一眼.有一些知識(shí)點(diǎn)可能沒(méi)涉及到,以后會(huì)加以補(bǔ)足.因?yàn)槊嬖嚐o(wú)非是考察你對(duì)技術(shù)的理解和總結(jié),所以本篇的每個(gè)點(diǎn)總結(jié)的比較精簡(jiǎn),只是讓你大概的說(shuō)出來(lái),有的部分是需要能夠畫(huà)出原理圖并進(jìn)行解釋說(shuō)明,這個(gè)要在工作中多積累.
JAVA
一. 類(lèi)的加載過(guò)程,Person person = new Person();為例進(jìn)行說(shuō)明。
- 因?yàn)閚ew用到了Person.class,所以會(huì)先找到Person.class文件,并加載到內(nèi)存中;
- 執(zhí)行該類(lèi)中的static代碼塊,如果有的話(huà),給Person.class類(lèi)進(jìn)行初始化;
- 在堆內(nèi)存中開(kāi)辟空間分配內(nèi)存地址;
- 在堆內(nèi)存中建立對(duì)象的特有屬性,并進(jìn)行默認(rèn)初始化;
- 對(duì)屬性進(jìn)行顯示初始化;
- 對(duì)對(duì)象進(jìn)行構(gòu)造代碼塊初始化;
- 對(duì)對(duì)象進(jìn)行與之對(duì)應(yīng)的構(gòu)造函數(shù)進(jìn)行初始化;
- 將內(nèi)存地址付給棧內(nèi)存中的p變量
二. JVM相關(guān)知識(shí),GC機(jī)制。
JVM基本構(gòu)成

從上圖可知,JVM主要包括四個(gè)部分:
1.類(lèi)加載器(ClassLoader):在JVM啟動(dòng)時(shí)或者在類(lèi)運(yùn)行時(shí)將需要的class加載到JVM中。(下圖表示了從java源文件到JVM的整個(gè)過(guò)程,可配合理解。

2.執(zhí)行引擎:負(fù)責(zé)執(zhí)行class文件中包含的字節(jié)碼指令;
3.內(nèi)存區(qū)(也叫運(yùn)行時(shí)數(shù)據(jù)區(qū)):是在JVM運(yùn)行的時(shí)候操作所分配的內(nèi)存區(qū)。運(yùn)行時(shí)內(nèi)存區(qū)主要可以劃分為5個(gè)區(qū)域,如圖:

方法區(qū)(Method Area):用于存儲(chǔ)類(lèi)結(jié)構(gòu)信息的地方,包括常量池、靜態(tài)變量、構(gòu)造函數(shù)等。雖然JVM規(guī)范把方法區(qū)描述為堆的一個(gè)邏輯部分, 但它卻有個(gè)別名non-heap(非堆),所以大家不要搞混淆了。方法區(qū)還包含一個(gè)運(yùn)行時(shí)常量池。
java堆(Heap):存儲(chǔ)java實(shí)例或者對(duì)象的地方。這塊是GC的主要區(qū)域。從存儲(chǔ)的內(nèi)容我們可以很容易知道,方法區(qū)和堆是被所有java線(xiàn)程共享的。
java棧(Stack):java??偸呛途€(xiàn)程關(guān)聯(lián)在一起,每當(dāng)創(chuàng)建一個(gè)線(xiàn)程時(shí),JVM就會(huì)為這個(gè)線(xiàn)程創(chuàng)建一個(gè)對(duì)應(yīng)的java棧。在這個(gè)java棧中又會(huì)包含多個(gè)棧幀,每運(yùn)行一個(gè)方法就創(chuàng)建一個(gè)棧幀,用于存儲(chǔ)局部變量表、操作棧、方法返回值等。每一個(gè)方法從調(diào)用直至執(zhí)行完成的過(guò)程,就對(duì)應(yīng)一個(gè)棧幀在java棧中入棧到出棧的過(guò)程。所以java棧是現(xiàn)成私有的。
程序計(jì)數(shù)器(PC Register):用于保存當(dāng)前線(xiàn)程執(zhí)行的內(nèi)存地址。由于JVM程序是多線(xiàn)程執(zhí)行的(線(xiàn)程輪流切換),所以為了保證線(xiàn)程切換回來(lái)后,還能恢復(fù)到原先狀態(tài),就需要一個(gè)獨(dú)立的計(jì)數(shù)器,記錄之前中斷的地方,可見(jiàn)程序計(jì)數(shù)器也是線(xiàn)程私有的。
本地方法棧(Native Method Stack):和java棧的作用差不多,只不過(guò)是為JVM使用到的native方法服務(wù)的。
4.本地方法接口:主要是調(diào)用C或C++實(shí)現(xiàn)的本地方法及返回結(jié)果。
GC機(jī)制
垃圾收集器一般必須完成兩件事:檢測(cè)出垃圾;回收垃圾。怎么檢測(cè)出垃圾?一般有以下幾種方法:
給一個(gè)對(duì)象添加引用計(jì)數(shù)器,每當(dāng)有個(gè)地方引用它,計(jì)數(shù)器就加1;引用失效就減1。好了,問(wèn)題來(lái)了,如果我有兩個(gè)對(duì)象A和B,互相引用,除此之外,沒(méi)有其他任何對(duì)象引用它們,實(shí)際上這兩個(gè)對(duì)象已經(jīng)無(wú)法訪(fǎng)問(wèn),即是我們說(shuō)的垃圾對(duì)象。但是互相引用,計(jì)數(shù)不為0,導(dǎo)致無(wú)法回收,所以還有另一種方法:
以根集對(duì)象為起始點(diǎn)進(jìn)行搜索,如果有對(duì)象不可達(dá)的話(huà),即是垃圾對(duì)象。這里的根集一般包括java棧中引用的對(duì)象、方法區(qū)常良池中引用的對(duì)象、本地方法中引用的對(duì)象等。
總之,JVM在做垃圾回收的時(shí)候,會(huì)檢查堆中的所有對(duì)象是否會(huì)被這些根集對(duì)象引用,不能夠被引用的對(duì)象就會(huì)被垃圾收集器回收。一般回收算法也有如下幾種:
- 標(biāo)記-清除(Mark-sweep)
- 復(fù)制(Copying
- 標(biāo)記-整理(Mark-Compact)
- 分代收集算法
三. 類(lèi)的加載器,雙親機(jī)制,Android的類(lèi)加載器。
類(lèi)的加載器
大家都知道,當(dāng)我們寫(xiě)好一個(gè)Java程序之后,不是管是CS還是BS應(yīng)用,都是由若干個(gè).class文件組織而成的一個(gè)完整的Java應(yīng)用程序,當(dāng)程序在運(yùn)行時(shí),即會(huì)調(diào)用該程序的一個(gè)入口函數(shù)來(lái)調(diào)用系統(tǒng)的相關(guān)功能,而這些功能都被封裝在不同的class文件當(dāng)中,所以經(jīng)常要從這個(gè)class文件中要調(diào)用另外一個(gè)class文件中的方法,如果另外一個(gè)文件不存在的,則會(huì)引發(fā)系統(tǒng)異常。而程序在啟動(dòng)的時(shí)候,并不會(huì)一次性加載程序所要用的所有class文件,而是根據(jù)程序的需要,通過(guò)Java的類(lèi)加載機(jī)制(ClassLoader)來(lái)動(dòng)態(tài)加載某個(gè)class文件到內(nèi)存當(dāng)中的,從而只有class文件被載入到了內(nèi)存之后,才能被其它c(diǎn)lass所引用。所以ClassLoader就是用來(lái)動(dòng)態(tài)加載class文件到內(nèi)存當(dāng)中用的。
雙親機(jī)制
ClassLoader使用的是雙親委托模型來(lái)搜索類(lèi)的,每個(gè)ClassLoader實(shí)例都有一個(gè)父類(lèi)加載器的引用(不是繼承的關(guān)系,是一個(gè)包含的關(guān)系),虛擬機(jī)內(nèi)置的類(lèi)加載器(Bootstrap ClassLoader)本身沒(méi)有父類(lèi)加載器,但可以用作其它ClassLoader實(shí)例的的父類(lèi)加載器。當(dāng)一個(gè)ClassLoader實(shí)例需要加載某個(gè)類(lèi)時(shí),它會(huì)試圖親自搜索某個(gè)類(lèi)之前,先把這個(gè)任務(wù)委托給它的父類(lèi)加載器,這個(gè)過(guò)程是由上至下依次檢查的,首先由最頂層的類(lèi)加載器Bootstrap ClassLoader試圖加載,如果沒(méi)加載到,則把任務(wù)轉(zhuǎn)交給Extension ClassLoader試圖加載,如果也沒(méi)加載到,則轉(zhuǎn)交給App ClassLoader 進(jìn)行加載,如果它也沒(méi)有加載得到的話(huà),則返回給委托的發(fā)起者,由它到指定的文件系統(tǒng)或網(wǎng)絡(luò)等URL中加載該類(lèi)。如果它們都沒(méi)有加載到這個(gè)類(lèi)時(shí),則拋出ClassNotFoundException異常。否則將這個(gè)找到的類(lèi)生成一個(gè)類(lèi)的定義,并將它加載到內(nèi)存當(dāng)中,最后返回這個(gè)類(lèi)在內(nèi)存中的Class實(shí)例對(duì)象。
因?yàn)檫@樣可以避免重復(fù)加載,當(dāng)父親已經(jīng)加載了該類(lèi)的時(shí)候,就沒(méi)有必要子ClassLoader再加載一次。考慮到安全因素,我們?cè)囅胍幌?,如果不使用這種委托模式,那我們就可以隨時(shí)使用自定義的String來(lái)動(dòng)態(tài)替代java核心api中定義的類(lèi)型,這樣會(huì)存在非常大的安全隱患,而雙親委托的方式,就可以避免這種情況,因?yàn)镾tring已經(jīng)在啟動(dòng)時(shí)就被引導(dǎo)類(lèi)加載器(Bootstrcp ClassLoader)加載,所以用戶(hù)自定義的ClassLoader永遠(yuǎn)也無(wú)法加載一個(gè)自己寫(xiě)的String,除非你改變JDK中ClassLoader搜索類(lèi)的默認(rèn)算法。
JVM在判定兩個(gè)class是否相同時(shí),不僅要判斷兩個(gè)類(lèi)名是否相同,而且要判斷是否由同一個(gè)類(lèi)加載器實(shí)例加載的。只有兩者同時(shí)滿(mǎn)足的情況下,JVM才認(rèn)為這兩個(gè)class是相同的。就算兩個(gè)class是同一份class字節(jié)碼,如果被兩個(gè)不同的ClassLoader實(shí)例所加載,JVM也會(huì)認(rèn)為它們是兩個(gè)不同class。比如網(wǎng)絡(luò)上的一個(gè)Java類(lèi)org.classloader.simple.NetClassLoaderSimple,javac編譯之后生成字節(jié)碼文件NetClassLoaderSimple.class,ClassLoaderA和ClassLoaderB這兩個(gè)類(lèi)加載器并讀取了NetClassLoaderSimple.class文件,并分別定義出了java.lang.Class實(shí)例來(lái)表示這個(gè)類(lèi),對(duì)于JVM來(lái)說(shuō),它們是兩個(gè)不同的實(shí)例對(duì)象,但它們確實(shí)是同一份字節(jié)碼文件,如果試圖將這個(gè)Class實(shí)例生成具體的對(duì)象進(jìn)行轉(zhuǎn)換時(shí),就會(huì)拋運(yùn)行時(shí)異常java.lang.ClassCaseException,提示這是兩個(gè)不同的類(lèi)型。
Android類(lèi)加載器
對(duì)于A(yíng)ndroid而言,最終的apk文件包含的是dex類(lèi)型的文件,dex文件是將class文件重新打包,打包的規(guī)則又不是簡(jiǎn)單地壓縮,而是完全對(duì)class文件內(nèi)部的各種函數(shù)表,變量表進(jìn)行優(yōu)化,產(chǎn)生一個(gè)新的文件,即dex文件。因此加載這種特殊的Class文件就需要特殊的類(lèi)加載器DexClassLoader。
四. 集合框架,list,map,set都有哪些具體的實(shí)現(xiàn)類(lèi),區(qū)別都是什么?
1.List,Set都是繼承自Collection接口,Map則不是;
2.List特點(diǎn):元素有放入順序,元素可重復(fù);
- Set特點(diǎn):元素?zé)o放入順序,元素不可重復(fù),重復(fù)元素會(huì)覆蓋掉,(注意:元素雖然無(wú)放入順序,但是元素在set中的位置是有該元素的HashCode決定的,其位置其實(shí)是固定的,加入Set 的Object必須定義equals()方法;
另外list支持for循環(huán),也就是通過(guò)下標(biāo)來(lái)遍歷,也可以用迭代器,但是set只能用迭代,因?yàn)樗麩o(wú)序,無(wú)法用下標(biāo)來(lái)取得想要的值)。
3.Set和List對(duì)比:
- Set:檢索元素效率低下,刪除和插入效率高,插入和刪除不會(huì)引起元素位置改變。
- List:和數(shù)組類(lèi)似,List可以動(dòng)態(tài)增長(zhǎng),查找元素效率高,插入刪除元素效率低,因?yàn)闀?huì)引起其他元素位置改變。
4.Map適合儲(chǔ)存鍵值對(duì)的數(shù)據(jù)。
5.線(xiàn)程安全集合類(lèi)與非線(xiàn)程安全集合類(lèi)
- LinkedList、ArrayList、HashSet是非線(xiàn)程安全的,Vector是線(xiàn)程安全的;
- HashMap是非線(xiàn)程安全的,HashTable是線(xiàn)程安全的;
- StringBuilder是非線(xiàn)程安全的,StringBuffer是線(xiàn)程安全的。
下面是這些類(lèi)具體的使用介紹:
ArrayList與LinkedList的區(qū)別和適用場(chǎng)景
優(yōu)點(diǎn):ArrayList是實(shí)現(xiàn)了基于動(dòng)態(tài)數(shù)組的數(shù)據(jù)結(jié)構(gòu),因?yàn)榈刂愤B續(xù),一旦數(shù)據(jù)存儲(chǔ)好了,查詢(xún)操作效率會(huì)比較高(在內(nèi)存里是連著放的)。
缺點(diǎn):因?yàn)榈刂愤B續(xù), ArrayList要移動(dòng)數(shù)據(jù),所以插入和刪除操作效率比較低。
優(yōu)點(diǎn):LinkedList基于鏈表的數(shù)據(jù)結(jié)構(gòu),地址是任意的,所以在開(kāi)辟內(nèi)存空間的時(shí)候不需要等一個(gè)連續(xù)的地址,對(duì)于新增和刪除操作add和remove,LinedList比較占優(yōu)勢(shì)。LinkedList 適用于要頭尾操作或插入指定位置的場(chǎng)景。
缺點(diǎn):因?yàn)長(zhǎng)inkedList要移動(dòng)指針,所以查詢(xún)操作性能比較低。
適用場(chǎng)景分析:
當(dāng)需要對(duì)數(shù)據(jù)進(jìn)行對(duì)此訪(fǎng)問(wèn)的情況下選用ArrayList,當(dāng)需要對(duì)數(shù)據(jù)進(jìn)行多次增加刪除修改時(shí)采用LinkedList。
ArrayList與Vector的區(qū)別和適用場(chǎng)景
ArrayList有三個(gè)構(gòu)造方法:
- public ArrayList(int initialCapacity)//構(gòu)造一個(gè)具有指定初始容量的空列表。
- public ArrayList()//構(gòu)造一個(gè)初始容量為10的空列表。
- public ArrayList(Collection c)//構(gòu)造一個(gè)包含指定 collection 的元素的列表
Vector有四個(gè)構(gòu)造方法:
- public Vector()//使用指定的初始容量和等于零的容量增量構(gòu)造一個(gè)空向量。
- public Vector(int initialCapacity)//構(gòu)造一個(gè)空向量,使其內(nèi)部數(shù)據(jù)數(shù)組的大小,其標(biāo)準(zhǔn)容量增量為零。
- public Vector(Collection c)//構(gòu)造一個(gè)包含指定 collection 中的元素的向量
- public Vector(int initialCapacity,int capacityIncrement)//使用指定的初始容量和容量增量構(gòu)造一個(gè)空的向量
ArrayList和Vector都是用數(shù)組實(shí)現(xiàn)的,主要有這么三個(gè)區(qū)別:
- Vector是多線(xiàn)程安全的,線(xiàn)程安全就是說(shuō)多線(xiàn)程訪(fǎng)問(wèn)同一代碼,不會(huì)產(chǎn)生不確定的結(jié)果。而ArrayList不是,這個(gè)可以從源碼中看出,Vector類(lèi)中的方法很多有synchronized進(jìn)行修飾,這樣就導(dǎo)致了Vector在效率上無(wú)法與ArrayList相比;
- 兩個(gè)都是采用的線(xiàn)性連續(xù)空間存儲(chǔ)元素,但是當(dāng)空間不足的時(shí)候,兩個(gè)類(lèi)的增加方式是不同。
- Vector可以設(shè)置增長(zhǎng)因子,而ArrayList不可以。
- Vector是一種老的動(dòng)態(tài)數(shù)組,是線(xiàn)程同步的,效率很低,一般不贊成使用。
適用場(chǎng)景:
- Vector是線(xiàn)程同步的,所以它也是線(xiàn)程安全的,而ArrayList是線(xiàn)程異步的,是不安全的。如果不考慮到線(xiàn)程的安全因素,一般用ArrayList效率比較高。
- 如果集合中的元素的數(shù)目大于目前集合數(shù)組的長(zhǎng)度時(shí),在集合中使用數(shù)據(jù)量比較大的數(shù)據(jù),用Vector有一定的優(yōu)勢(shì)。
HashSet與Treeset的適用場(chǎng)景
- TreeSet 是二叉樹(shù)(紅黑樹(shù)的樹(shù)據(jù)結(jié)構(gòu))實(shí)現(xiàn)的,Treeset中的數(shù)據(jù)是自動(dòng)排好序的,不允許放入null值。
- HashSet 是哈希表實(shí)現(xiàn)的,HashSet中的數(shù)據(jù)是無(wú)序的,可以放入null,但只能放入一個(gè)null,兩者中的值都不能重復(fù),就如數(shù)據(jù)庫(kù)中唯一約束。
- HashSet要求放入的對(duì)象必須實(shí)現(xiàn)HashCode()方法,放入的對(duì)象,是以hashcode碼作為標(biāo)識(shí)的,而具有相同內(nèi)容的String對(duì)象,hashcode是一樣,所以放入的內(nèi)容不能重復(fù)。但是同一個(gè)類(lèi)的對(duì)象可以放入不同的實(shí)例。
適用場(chǎng)景分析:
HashSet是基于Hash算法實(shí)現(xiàn)的,其性能通常都優(yōu)于TreeSet。為快速查找而設(shè)計(jì)的Set,我們通常都應(yīng)該使用HashSet,在我們需要排序的功能時(shí),我們才使用TreeSet。
HashMap與TreeMap、HashTable的區(qū)別及適用場(chǎng)景
HashMap:基于哈希表(散列表)實(shí)現(xiàn)。使用HashMap要求添加的鍵類(lèi)明確定義了hashCode()和equals()[可以重寫(xiě)hashCode()和equals()],為了優(yōu)化HashMap空間的使用,您可以調(diào)優(yōu)初始容量和負(fù)載因子。其中散列表的沖突處理主要分兩種,一種是開(kāi)放定址法,另一種是鏈表法。HashMap的實(shí)現(xiàn)中采用的是鏈表法。
TreeMap:非線(xiàn)程安全基于紅黑樹(shù)實(shí)現(xiàn)。TreeMap沒(méi)有調(diào)優(yōu)選項(xiàng),因?yàn)樵摌?shù)總處于平衡狀態(tài)。
適用場(chǎng)景分析:
HashMap和HashTable:HashMap去掉了HashTable的contains方法,但是加上了containsValue()和containsKey()方法。HashTable同步的,而HashMap是非同步的,效率上比HashTable要高。HashMap允許空鍵值,而HashTable不允許。
HashMap:適用于Map中插入、刪除和定位元素。
Treemap:適用于按自然順序或自定義順序遍歷鍵(key)。
(ps:其實(shí)我們工作的過(guò)程中對(duì)集合的使用是很頻繁的,稍加注意并總結(jié)積累一下,在面試的時(shí)候應(yīng)該會(huì)回答的很輕松)
五. concurrentHashmap原理,原子類(lèi)。
ConcurrentHashMap作為一種線(xiàn)程安全且高效的哈希表的解決方案,尤其其中的"分段鎖"的方案,相比HashTable的全表鎖在性能上的提升非常之大.
六. volatile原理
在《Java并發(fā)編程:核心理論》一文中,我們已經(jīng)提到過(guò)可見(jiàn)性、有序性及原子性問(wèn)題,通常情況下我們可以通過(guò)Synchronized關(guān)鍵字來(lái)解決這些個(gè)問(wèn)題,不過(guò)如果對(duì)Synchronized原理有了解的話(huà),應(yīng)該知道Synchronized是一個(gè)比較重量級(jí)的操作,對(duì)系統(tǒng)的性能有比較大的影響,所以,如果有其他解決方案,我們通常都避免使用Synchronized來(lái)解決問(wèn)題。而volatile關(guān)鍵字就是Java中提供的另一種解決可見(jiàn)性和有序性問(wèn)題的方案。對(duì)于原子性,需要強(qiáng)調(diào)一點(diǎn),也是大家容易誤解的一點(diǎn):對(duì)volatile變量的單次讀/寫(xiě)操作可以保證原子性的,如long和double類(lèi)型變量,但是并不能保證i++這種操作的原子性,因?yàn)楸举|(zhì)上i++是讀、寫(xiě)兩次操作。
參考文章插好眼了等傳送
七. 多線(xiàn)程的使用場(chǎng)景
使用多線(xiàn)程就一定效率高嗎? 有時(shí)候使用多線(xiàn)程并不是為了提高效率,而是使得CPU能夠同時(shí)處理多個(gè)事件。
1).為了不阻塞主線(xiàn)程,啟動(dòng)其他線(xiàn)程來(lái)做好事的事情,比如APP中耗時(shí)操作都不在UI中做.
2).實(shí)現(xiàn)更快的應(yīng)用程序,即主線(xiàn)程專(zhuān)門(mén)監(jiān)聽(tīng)用戶(hù)請(qǐng)求,子線(xiàn)程用來(lái)處理用戶(hù)請(qǐng)求,以獲得大的吞吐量.感覺(jué)這種情況下,多線(xiàn)程的效率未必高。 這種情況下的多線(xiàn)程是為了不必等待, 可以并行處理多條數(shù)據(jù)。
比如JavaWeb的就是主線(xiàn)程專(zhuān)門(mén)監(jiān)聽(tīng)用戶(hù)的HTTP請(qǐng)求,然后啟動(dòng)子線(xiàn)程去處理用戶(hù)的HTTP請(qǐng)求。
3).某種雖然優(yōu)先級(jí)很低的服務(wù),但是卻要不定時(shí)去做。
比如Jvm的垃圾回收。
4.)某種任務(wù),雖然耗時(shí),但是不耗CPU的操作時(shí),開(kāi)啟多個(gè)線(xiàn)程,效率會(huì)有顯著提高。
比如讀取文件,然后處理。 磁盤(pán)IO是個(gè)很耗費(fèi)時(shí)間,但是不耗CPU計(jì)算的工作。 所以可以一個(gè)線(xiàn)程讀取數(shù)據(jù),一個(gè)線(xiàn)程處理數(shù)據(jù)??隙ū纫粋€(gè)線(xiàn)程讀取數(shù)據(jù),然后處理效率高。 因?yàn)閮蓚€(gè)線(xiàn)程的時(shí)候充分利用了CPU等待磁盤(pán)IO的空閑時(shí)間。
八. JAVA常量池
**
- a.當(dāng)數(shù)值范圍為-128~127時(shí):如果兩個(gè)new出來(lái)Integer對(duì)象,即使值相同,通過(guò)“==”比較結(jié)果為false,但兩個(gè)對(duì)象直接賦值,則通過(guò)“==”比較結(jié)果為“true,這一點(diǎn)與String非常相似。
- b.當(dāng)數(shù)值不在-128~127時(shí),無(wú)論通過(guò)哪種方式,即使兩個(gè)對(duì)象的值相等,通過(guò)“==”比較,其結(jié)果為false;
- c.當(dāng)一個(gè)Integer對(duì)象直接與一個(gè)int基本數(shù)據(jù)類(lèi)型通過(guò)“==”比較,其結(jié)果與第一點(diǎn)相同;
- d.Integer對(duì)象的hash值為數(shù)值本身;
**
在Integer類(lèi)中有一個(gè)靜態(tài)內(nèi)部類(lèi)IntegerCache,在IntegerCache類(lèi)中有一個(gè)Integer數(shù)組,用以緩存當(dāng)數(shù)值范圍為-128~127時(shí)的Integer對(duì)象。
九. 簡(jiǎn)單介紹一下java中的泛型,泛型擦除以及相關(guān)的概念。
泛型是Java SE 1.5的新特性,泛型的本質(zhì)是參數(shù)化類(lèi)型,也就是說(shuō)所操作的數(shù)據(jù)類(lèi)型被指定為一個(gè)參數(shù)。這種參數(shù)類(lèi)型可以用在類(lèi)、接口和方法的創(chuàng)建中,分別稱(chēng)為泛型類(lèi)、泛型接口、泛型方法。 Java語(yǔ)言引入泛型的好處是安全簡(jiǎn)單。
在Java SE 1.5之前,沒(méi)有泛型的情況的下,通過(guò)對(duì)類(lèi)型Object的引用來(lái)實(shí)現(xiàn)參數(shù)的“任意化”,“任意化”帶來(lái)的缺點(diǎn)是要做顯式的強(qiáng)制類(lèi)型轉(zhuǎn)換,而這種轉(zhuǎn)換是要求開(kāi)發(fā)者對(duì)實(shí)際參數(shù)類(lèi)型可以預(yù)知的情況下進(jìn)行的。對(duì)于強(qiáng)制類(lèi)型轉(zhuǎn)換錯(cuò)誤的情況,編譯器可能不提示錯(cuò)誤,在運(yùn)行的時(shí)候才出現(xiàn)異常,這是一個(gè)安全隱患。
泛型的好處是在編譯的時(shí)候檢查類(lèi)型安全,并且所有的強(qiáng)制轉(zhuǎn)換都是自動(dòng)和隱式的,提高代碼的重用率。
- 泛型的類(lèi)型參數(shù)只能是類(lèi)類(lèi)型(包括自定義類(lèi)),不能是簡(jiǎn)單類(lèi)型。
- 同一種泛型可以對(duì)應(yīng)多個(gè)版本(因?yàn)閰?shù)類(lèi)型是不確定的),不同版本的泛型類(lèi)實(shí)例是不兼容的。
- 泛型的類(lèi)型參數(shù)可以有多個(gè)。
- 泛型的參數(shù)類(lèi)型可以使用extends語(yǔ)句,例如。習(xí)慣上稱(chēng)為“有界類(lèi)型”。
- 泛型的參數(shù)類(lèi)型還可以是通配符類(lèi)型。例如Class classType = Class.forName("java.lang.String");
泛型擦除以及相關(guān)的概念
Java中的泛型基本上都是在編譯器這個(gè)層次來(lái)實(shí)現(xiàn)的。在生成的Java字節(jié)碼中是不包含泛型中的類(lèi)型信息的。使用泛型的時(shí)候加上的類(lèi)型參數(shù),會(huì)在編譯器在編譯的時(shí)候去掉。這個(gè)過(guò)程就稱(chēng)為類(lèi)型擦除。
- 類(lèi)型擦除引起的問(wèn)題及解決方法
- 先檢查,在編譯,以及檢查編譯的對(duì)象和引用傳遞的問(wèn)題
- 自動(dòng)類(lèi)型轉(zhuǎn)換
- 類(lèi)型擦除與多態(tài)的沖突和解決方法
- 泛型類(lèi)型變量不能是基本數(shù)據(jù)類(lèi)型
- 運(yùn)行時(shí)類(lèi)型查詢(xún)
- 異常中使用泛型的問(wèn)題
- 數(shù)組(這個(gè)不屬于類(lèi)型擦除引起的問(wèn)題)
- 類(lèi)型擦除后的沖突
- 泛型在靜態(tài)方法和靜態(tài)類(lèi)中的問(wèn)題
Android
一. Handler機(jī)制
Android 的消息機(jī)制也就是 handler 機(jī)制,創(chuàng)建 handler 的時(shí)候會(huì)創(chuàng)建一個(gè) looper ( 通過(guò) looper.prepare() 來(lái)創(chuàng)建 ),looper 一般為主線(xiàn)程 looper.
handler 通過(guò) send 發(fā)送消息 (sendMessage) ,當(dāng)然 post 一系列方法最終也是通過(guò) send 來(lái)實(shí)現(xiàn)的,在 send 方法中handler 會(huì)通過(guò) enqueueMessage() 方法中的 enqueueMessage(msg,millis )向消息隊(duì)列 MessageQueue 插入一條消息,同時(shí)會(huì)把本身的 handler 通過(guò) msg.target = this 傳入.
Looper 是一個(gè)死循環(huán),不斷的讀取MessageQueue中的消息,loop 方法會(huì)調(diào)用 MessageQueue 的 next 方法來(lái)獲取新的消息,next 操作是一個(gè)阻塞操作,當(dāng)沒(méi)有消息的時(shí)候 next 方法會(huì)一直阻塞,進(jìn)而導(dǎo)致 loop 一直阻塞,當(dāng)有消息的時(shí)候,Looper 就會(huì)處理消息 Looper 收到消息之后就開(kāi)始處理消息: msg.target.dispatchMessage(msg),當(dāng)然這里的 msg.target 就是上面?zhèn)鬟^(guò)來(lái)的發(fā)送這條消息的 handler 對(duì)象,這樣 handler 發(fā)送的消息最終又交給他的dispatchMessage方法來(lái)處理了,這里不同的是,handler 的 dispatchMessage 方法是在創(chuàng)建 Handler時(shí)所使用的 Looper 中執(zhí)行的,這樣就成功的將代碼邏輯切換到了主線(xiàn)程了.
Handler 處理消息的過(guò)程是:首先,檢查Message 的 callback 是否為 null,不為 null 就通過(guò) handleCallBack 來(lái)處理消息,Message 的 callback 是一個(gè) Runnable 對(duì)象,實(shí)際上是 handler 的 post 方法所傳遞的 Runnable 參數(shù).其次是檢查 mCallback 是 否為 null,不為 null 就調(diào)用 mCallback 的handleMessage 方法來(lái)處理消息.
二. View的繪制流程
View的繪制流程:OnMeasure()——>OnLayout()——>OnDraw()
各步驟的主要工作:
測(cè)量視圖大小。從頂層父View到子View遞歸調(diào)用measure方法,measure方法又回調(diào)OnMeasure。
確定View位置,進(jìn)行頁(yè)面布局。從頂層父View向子View的遞歸調(diào)用view.layout方法的過(guò)程,即父View根據(jù)上一步measure子View所得到的布局大小和布局參數(shù),將子View放在合適的位置上。
繪制視圖:ViewRoot創(chuàng)建一個(gè)Canvas對(duì)象,然后調(diào)用OnDraw()。六個(gè)步驟:①、繪制視圖的背景;②、保存畫(huà)布的圖層(Layer);③、繪制View的內(nèi)容;④、繪制View子視圖,如果沒(méi)有就不用;⑤、還原圖層(Layer);⑥、繪制滾動(dòng)條。
三. 事件傳遞機(jī)制
- Android事件分發(fā)機(jī)制的本質(zhì)是要解決:點(diǎn)擊事件由哪個(gè)對(duì)象發(fā)出,經(jīng)過(guò)哪些對(duì)象,最終達(dá)到哪個(gè)對(duì)象并最終得到處理。這里的對(duì)象是指Activity、ViewGroup、View.
- Android中事件分發(fā)順序:Activity(Window) -> ViewGroup -> View.
- 事件分發(fā)過(guò)程由dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()三個(gè)方法協(xié)助完成
設(shè)置Button按鈕來(lái)響應(yīng)點(diǎn)擊事件事件傳遞情況:(如下圖)
布局如下:

- 最外層:Activiy A,包含兩個(gè)子View:ViewGroup B、View C
- 中間層:ViewGroup B,包含一個(gè)子View:View C
- 最內(nèi)層:View C
假設(shè)用戶(hù)首先觸摸到屏幕上View C上的某個(gè)點(diǎn)(如圖中黃色區(qū)域),那么Action_DOWN事件就在該點(diǎn)產(chǎn)生,然后用戶(hù)移動(dòng)手指并最后離開(kāi)屏幕。
- 按鈕點(diǎn)擊事件:
- DOWN事件被傳遞給C的onTouchEvent方法,該方法返回true,表示處理這個(gè)事件;
- 因?yàn)镃正在處理這個(gè)事件,那么DOWN事件將不再往上傳遞給B和A的onTouchEvent();
- 該事件列的其他事件(Move、Up)也將傳遞給C的onTouchEvent();

(記住這個(gè)圖的傳遞順序,面試的時(shí)候能夠畫(huà)出來(lái),就很詳細(xì)了)
四. Binder機(jī)制
1.了解Binder
在A(yíng)ndroid系統(tǒng)中,每一個(gè)應(yīng)用程序都運(yùn)行在獨(dú)立的進(jìn)程中,這也保證了當(dāng)其中一個(gè)程序出現(xiàn)異常而不會(huì)影響另一個(gè)應(yīng)用程序的正常運(yùn)轉(zhuǎn)。在許多情況下,我們activity都會(huì)與各種系統(tǒng)的service打交道,很顯然,我們寫(xiě)的程序中activity與系統(tǒng)service肯定不是同一個(gè)進(jìn)程,但是它們之間是怎樣實(shí)現(xiàn)通信的呢?所以Binder是android中一種實(shí)現(xiàn)進(jìn)程間通信(IPC)的方式之一。
1).首先,Binder分為Client和Server兩個(gè)進(jìn)程。
注意,Client和Server是相對(duì)的。誰(shuí)發(fā)消息,誰(shuí)就是Client,誰(shuí)接收消息,誰(shuí)就是Server。
舉個(gè)例子,兩個(gè)進(jìn)程A和B之間使用Binder通信,進(jìn)程A發(fā)消息給進(jìn)程B,那么這時(shí)候A是Binder Client,B是Binder Server;進(jìn)程B發(fā)消息給進(jìn)程A,那么這時(shí)候B是Binder Client,A是Binder Server——其實(shí)這么說(shuō)雖然簡(jiǎn)單了,但還是不太嚴(yán)謹(jǐn),我們先這么理解著。
2).其次,我們看下面這個(gè)圖(摘自田維術(shù)的博客),基本說(shuō)明白了Binder的組成解構(gòu):

圖中的IPC就是進(jìn)程間通信的意思。
圖中的ServiceManager,負(fù)責(zé)把Binder Server注冊(cè)到一個(gè)容器中。
有人把ServiceManager比喻成電話(huà)局,存儲(chǔ)著每個(gè)住宅的座機(jī)電話(huà),還是很恰當(dāng)?shù)?。張三給李四打電話(huà),撥打電話(huà)號(hào)碼,會(huì)先轉(zhuǎn)接到電話(huà)局,電話(huà)局的接線(xiàn)員查到這個(gè)電話(huà)號(hào)碼的地址,因?yàn)槔钏牡碾娫?huà)號(hào)碼之前在電話(huà)局注冊(cè)過(guò),所以就能撥通;沒(méi)注冊(cè),就會(huì)提示該號(hào)碼不存在。
對(duì)照著Android Binder機(jī)制,對(duì)著上面這圖,張三就是Binder Client,李四就是Binder Server,電話(huà)局就是ServiceManager,電話(huà)局的接線(xiàn)員在這個(gè)過(guò)程中做了很多事情,對(duì)應(yīng)著圖中的Binder驅(qū)動(dòng).
3).接下來(lái)我們看Binder通信的過(guò)程,還是摘自田維術(shù)博客的一張圖:

注:圖中的SM也就是ServiceManager。
我們看到,Client想要直接調(diào)用Server的add方法,是不可以的,因?yàn)樗鼈冊(cè)诓煌倪M(jìn)程中,這時(shí)候就需要Binder來(lái)幫忙了。
- 首先是Server在SM這個(gè)容器中注冊(cè)。
- 其次,Client想要調(diào)用Server的add方法,就需要先獲取Server對(duì)象, 但是SM不會(huì)把真正的Server對(duì)象返回給Client,而是把Server的一個(gè)代理對(duì)象返回給Client,也就是Proxy。
- 然后,Client調(diào)用Proxy的add方法,SM會(huì)幫他去調(diào)用Server的add方法,并把結(jié)果返回給Client。
以上這3步,Binder驅(qū)動(dòng)出了很多力,但我們不需要知道Binder驅(qū)動(dòng)的底層實(shí)現(xiàn),涉及到C++的代碼了——把有限的時(shí)間去做更有意義的事情。
(ps:以上節(jié)選自包建強(qiáng)老師的文章點(diǎn)我點(diǎn)我 ).
2.為什么android選用Binder來(lái)實(shí)現(xiàn)進(jìn)程間通信?
1).可靠性。在移動(dòng)設(shè)備上,通常采用基于Client-Server的通信方式來(lái)實(shí)現(xiàn)互聯(lián)網(wǎng)與設(shè)備間的內(nèi)部通信。目前l(fā)inux支持IPC包括傳統(tǒng)的管道,System V IPC,即消息隊(duì)列/共享內(nèi)存/信號(hào)量,以及socket中只有socket支持Client-Server的通信方式。Android系統(tǒng)為開(kāi)發(fā)者提供了豐富進(jìn)程間通信的功能接口,媒體播放,傳感器,無(wú)線(xiàn)傳輸。這些功能都由不同的server來(lái)管理。開(kāi)發(fā)都只關(guān)心將自己應(yīng)用程序的client與server的通信建立起來(lái)便可以使用這個(gè)服務(wù)。毫無(wú)疑問(wèn),如若在底層架設(shè)一套協(xié)議來(lái)實(shí)現(xiàn)Client-Server通信,增加了系統(tǒng)的復(fù)雜性。在資源有限的手機(jī) 上來(lái)實(shí)現(xiàn)這種復(fù)雜的環(huán)境,可靠性難以保證。
2).傳輸性能。socket主要用于跨網(wǎng)絡(luò)的進(jìn)程間通信和本機(jī)上進(jìn)程間的通信,但傳輸效率低,開(kāi)銷(xiāo)大。消息隊(duì)列和管道采用存儲(chǔ)-轉(zhuǎn)發(fā)方式,即數(shù)據(jù)先從發(fā)送方緩存區(qū)拷貝到內(nèi)核開(kāi)辟的一塊緩存區(qū)中,然后從內(nèi)核緩存區(qū)拷貝到接收方緩存區(qū),其過(guò)程至少有兩次拷貝。雖然共享內(nèi)存無(wú)需拷貝,但控制復(fù)雜。比較各種IPC方式的數(shù)據(jù)拷貝次數(shù)。共享內(nèi)存:0次。Binder:1次。Socket/管道/消息隊(duì)列:2次。
3).安全性。Android是一個(gè)開(kāi)放式的平臺(tái),所以確保應(yīng)用程序安全是很重要的。Android對(duì)每一個(gè)安裝應(yīng)用都分配了UID/PID,其中進(jìn)程的UID是可用來(lái)鑒別進(jìn)程身份。傳統(tǒng)的只能由用戶(hù)在數(shù)據(jù)包里填寫(xiě)UID/PID,這樣不可靠,容易被惡意程序利用。而我們要求由內(nèi)核來(lái)添加可靠的UID。
所以,出于可靠性、傳輸性、安全性。android建立了一套新的進(jìn)程間通信方式。
五. Android中進(jìn)程的級(jí)別,以及各自的區(qū)別。
1、前臺(tái)進(jìn)程
用戶(hù)當(dāng)前正在做的事情需要這個(gè)進(jìn)程。如果滿(mǎn)足下面的條件之一,一個(gè)進(jìn)程就被認(rèn)為是前臺(tái)進(jìn)程:
- 這個(gè)進(jìn)程擁有一個(gè)正在與用戶(hù)交互的Activity(這個(gè)Activity的onResume()方法被調(diào)用)。
- 這個(gè)進(jìn)程擁有一個(gè)綁定到正在與用戶(hù)交互的activity上的Service。
- 這個(gè)進(jìn)程擁有一個(gè)前臺(tái)運(yùn)行的Service(service調(diào)用了方法startForeground()).
- 這個(gè)進(jìn)程擁有一個(gè)正在執(zhí)行其任何一個(gè)生命周期回調(diào)方法(onCreate(),onStart(),或onDestroy())的Service。
- 這個(gè)進(jìn)程擁有正在執(zhí)行其onReceive()方法的BroadcastReceiver。
通常,在任何時(shí)間點(diǎn),只有很少的前臺(tái)進(jìn)程存在。它們只有在達(dá)到無(wú)法調(diào)合的矛盾時(shí)才會(huì)被殺--如內(nèi)存太小而不能繼續(xù)運(yùn)行時(shí)。通常,到了這時(shí),設(shè)備就達(dá)到了一個(gè)內(nèi)存分頁(yè)調(diào)度狀態(tài),所以需要?dú)⒁恍┣芭_(tái)進(jìn)程來(lái)保證用戶(hù)界面的反應(yīng).
2、可見(jiàn)進(jìn)程
一個(gè)進(jìn)程不擁有運(yùn)行于前臺(tái)的組件,但是依然能影響用戶(hù)所見(jiàn)。滿(mǎn)足下列條件時(shí),進(jìn)程即為可見(jiàn):
這個(gè)進(jìn)程擁有一個(gè)不在前臺(tái)但仍可見(jiàn)的Activity(它的onPause()方法被調(diào)用)。當(dāng)一個(gè)前臺(tái)activity啟動(dòng)一個(gè)對(duì)話(huà)框時(shí),就出了這種情況。
3、服務(wù)進(jìn)程
一個(gè)可見(jiàn)進(jìn)程被認(rèn)為是極其重要的。并且,除非只有殺掉它才可以保證所有前臺(tái)進(jìn)程的運(yùn)行,否則是不能動(dòng)它的。
這個(gè)進(jìn)程擁有一個(gè)綁定到可見(jiàn)activity的Service。
一個(gè)進(jìn)程不在上述兩種之內(nèi),但它運(yùn)行著一個(gè)被startService()所啟動(dòng)的service。
盡管一個(gè)服務(wù)進(jìn)程不直接影響用戶(hù)所見(jiàn),但是它們通常做一些用戶(hù)關(guān)心的事情(比如播放音樂(lè)或下載數(shù)據(jù)),所以系統(tǒng)不到前臺(tái)進(jìn)程和可見(jiàn)進(jìn)程活不下去時(shí)不會(huì)殺它。
4、后臺(tái)進(jìn)程
一個(gè)進(jìn)程擁有一個(gè)當(dāng)前不可見(jiàn)的activity(activity的onStop()方法被調(diào)用)。
這樣的進(jìn)程們不會(huì)直接影響到用戶(hù)體驗(yàn),所以系統(tǒng)可以在任意時(shí)刻殺了它們從而為前臺(tái)、可見(jiàn)、以及服務(wù)進(jìn)程們提供存儲(chǔ)空間。通常有很多后臺(tái)進(jìn)程在運(yùn)行。它們被保存在一個(gè)LRU(最近最少使用)列表中來(lái)確保擁有最近剛被看到的activity的進(jìn)程最后被殺。如果一個(gè)activity正確的實(shí)現(xiàn)了它的生命周期方法,并保存了它的當(dāng)前狀態(tài),那么殺死它的進(jìn)程將不會(huì)對(duì)用戶(hù)的可視化體驗(yàn)造成影響。因?yàn)楫?dāng)用戶(hù)返回到這個(gè)activity時(shí),這個(gè)activity會(huì)恢復(fù)它所有的可見(jiàn)狀態(tài)。
5、空進(jìn)程
一個(gè)進(jìn)程不擁有入何active組件。
保留這類(lèi)進(jìn)程的唯一理由是高速緩存,這樣可以提高下一次一個(gè)組件要運(yùn)行它時(shí)的啟動(dòng)速度。系統(tǒng)經(jīng)常為了平衡在進(jìn)程高速緩存和底層的內(nèi)核高速緩存之間的整體系統(tǒng)資源而殺死它們。
六. 線(xiàn)程池的相關(guān)知識(shí)。
Android中的線(xiàn)程池都是之間或間接通過(guò)配置ThreadPoolExecutor來(lái)實(shí)現(xiàn)不同特性的線(xiàn)程池.Android中最常見(jiàn)的四類(lèi)具有不同特性的線(xiàn)程池分別為FixThreadPool、CachedThreadPool、SingleThreadPool、ScheduleThreadExecutor.
1).FixThreadPool
只有核心線(xiàn)程,并且數(shù)量固定的,也不會(huì)被回收,所有線(xiàn)程都活動(dòng)時(shí),因?yàn)殛?duì)列沒(méi)有限制大小,新任務(wù)會(huì)等待執(zhí)行.
優(yōu)點(diǎn):更快的響應(yīng)外界請(qǐng)求.
2).SingleThreadPool
只有一個(gè)核心線(xiàn)程,確保所有的任務(wù)都在同一線(xiàn)程中按順序完成.因此不需要處理線(xiàn)程同步的問(wèn)題.
3).CachedThreadPool
只有非核心線(xiàn)程,最大線(xiàn)程數(shù)非常大,所有線(xiàn)程都活動(dòng)時(shí),會(huì)為新任務(wù)創(chuàng)建新線(xiàn)程,否則會(huì)利用空閑線(xiàn)程(60s空閑時(shí)間,過(guò)了就會(huì)被回收,所以線(xiàn)程池中有0個(gè)線(xiàn)程的可能)處理任務(wù).
優(yōu)點(diǎn):任何任務(wù)都會(huì)被立即執(zhí)行(任務(wù)隊(duì)列SynchronousQueue相當(dāng)于一個(gè)空集合);比較適合執(zhí)行大量的耗時(shí)較少的任務(wù).
4).ScheduledThreadPool
核心線(xiàn)程數(shù)固定,非核心線(xiàn)程(閑著沒(méi)活干會(huì)被立即回收)數(shù)沒(méi)有限制.
優(yōu)點(diǎn):執(zhí)行定時(shí)任務(wù)以及有固定周期的重復(fù)任務(wù)
七. 內(nèi)存泄露,怎樣查找,怎么產(chǎn)生的內(nèi)存泄露。
產(chǎn)生的內(nèi)存泄露
- 資源對(duì)象沒(méi)關(guān)閉造成的內(nèi)存泄漏
- 構(gòu)造Adapter時(shí),沒(méi)有使用緩存的convertView
- Bitmap對(duì)象不在使用時(shí)調(diào)用recycle()釋放內(nèi)存
- 試著使用關(guān)于application的context來(lái)替代和activity相關(guān)的context
- 注冊(cè)沒(méi)取消造成的內(nèi)存泄漏
- 集合中對(duì)象沒(méi)清理造成的內(nèi)存泄漏
查找內(nèi)存泄漏
查找內(nèi)存泄漏可以使用Android Stdio 自帶的Android Profiler工具,也可以使用Square產(chǎn)品的LeadCanary.
八. Android優(yōu)化
性能優(yōu)化
- 節(jié)制的使用Service 如果應(yīng)用程序需要使用Service來(lái)執(zhí)行后臺(tái)任務(wù)的話(huà),只有當(dāng)任務(wù)正在執(zhí)行的時(shí)候才應(yīng)該讓Service運(yùn)行起來(lái)。當(dāng)啟動(dòng)一個(gè)Service時(shí),系統(tǒng)會(huì)傾向于將這個(gè)Service所依賴(lài)的進(jìn)程進(jìn)行保留,系統(tǒng)可以在LRUcache當(dāng)中緩存的進(jìn)程數(shù)量也會(huì)減少,導(dǎo)致切換程序的時(shí)候耗費(fèi)更多性能。我們可以使用IntentService,當(dāng)后臺(tái)任務(wù)執(zhí)行結(jié)束后會(huì)自動(dòng)停止,避免了Service的內(nèi)存泄漏。
- 當(dāng)界面不可見(jiàn)時(shí)釋放內(nèi)存 當(dāng)用戶(hù)打開(kāi)了另外一個(gè)程序,我們的程序界面已經(jīng)不可見(jiàn)的時(shí)候,我們應(yīng)當(dāng)將所有和界面相關(guān)的資源進(jìn)行釋放。重寫(xiě)Activity的onTrimMemory()方法,然后在這個(gè)方法中監(jiān)聽(tīng)TRIM_MEMORY_UI_HIDDEN這個(gè)級(jí)別,一旦觸發(fā)說(shuō)明用戶(hù)離開(kāi)了程序,此時(shí)就可以進(jìn)行資源釋放操作了。
- 當(dāng)內(nèi)存緊張時(shí)釋放內(nèi)存 onTrimMemory()方法還有很多種其他類(lèi)型的回調(diào),可以在手機(jī)內(nèi)存降低的時(shí)候及時(shí)通知我們,我們應(yīng)該根據(jù)回調(diào)中傳入的級(jí)別來(lái)去決定如何釋放應(yīng)用程序的資源。
- 避免在Bitmap上浪費(fèi)內(nèi)存 讀取一個(gè)Bitmap圖片的時(shí)候,千萬(wàn)不要去加載不需要的分辨率。可以壓縮圖片等操作。
- 使用優(yōu)化過(guò)的數(shù)據(jù)集合 Android提供了一系列優(yōu)化過(guò)后的數(shù)據(jù)集合工具類(lèi),如SparseArray、SparseBooleanArray、LongSparseArray,使用這些API可以讓我們的程序更加高效。HashMap工具類(lèi)會(huì)相對(duì)比較低效,因?yàn)樗枰獮槊恳粋€(gè)鍵值對(duì)都提供一個(gè)對(duì)象入口,而SparseArray就避免掉了基本數(shù)據(jù)類(lèi)型轉(zhuǎn)換成對(duì)象數(shù)據(jù)類(lèi)型的時(shí)間。
布局優(yōu)化
1).重用布局文件
標(biāo)簽可以允許在一個(gè)布局當(dāng)中引入另一個(gè)布局,那么比如說(shuō)我們程序的所有界面都有一個(gè)公共的部分,這個(gè)時(shí)候最好的做法就是將這個(gè)公共的部分提取到一個(gè)獨(dú)立的布局中,然后每個(gè)界面的布局文件當(dāng)中來(lái)引用這個(gè)公共的布局。
Tips:如果我們要在標(biāo)簽中覆寫(xiě)layout屬性,必須要將layout_width和layout_height這兩個(gè)屬性也進(jìn)行覆寫(xiě),否則覆寫(xiě)效果將不會(huì)生效。
標(biāo)簽是作為標(biāo)簽的一種輔助擴(kuò)展來(lái)使用的,它的主要作用是為了防止在引用布局文件時(shí)引用文件時(shí)產(chǎn)生多余的布局嵌套。布局嵌套越多,解析起來(lái)就越耗時(shí),性能就越差。因此編寫(xiě)布局文件時(shí)應(yīng)該讓嵌套的層數(shù)越少越好。
舉例:比如在LinearLayout里邊使用一個(gè)布局。里邊又有一個(gè)LinearLayout,那么其實(shí)就存在了多余的布局嵌套,使用merge可以解決這個(gè)問(wèn)題。
2).僅在需要時(shí)才加載布局
某個(gè)布局當(dāng)中的元素不是一起顯示出來(lái)的,普通情況下只顯示部分常用的元素,而那些不常用的元素只有在用戶(hù)進(jìn)行特定操作時(shí)才會(huì)顯示出來(lái)。
舉例:填信息時(shí)不是需要全部填的,有一個(gè)添加更多字段的選項(xiàng),當(dāng)用戶(hù)需要添加其他信息的時(shí)候,才將另外的元素顯示到界面上。用VISIBLE性能表現(xiàn)一般,可以用ViewStub。ViewStub也是View的一種,但是沒(méi)有大小,沒(méi)有繪制功能,也不參與布局,資源消耗非常低,可以認(rèn)為完全不影響性能。
- <ViewStub
- android:id="@+id/view_stub"
- android:layout="@layout/profile_extra"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- />
- public void onMoreClick() {
- ViewStub viewStub = (ViewStub) findViewById(R.id.view_stub);
- if (viewStub != null) {
- View inflatedView = viewStub.inflate();
- editExtra1 = (EditText) inflatedView.findViewById(R.id.edit_extra1);
- editExtra2 = (EditText) inflatedView.findViewById(R.id.edit_extra2);
- editExtra3 = (EditText) inflatedView.findViewById(R.id.edit_extra3);
- }
- }
tips:ViewStub所加載的布局是不可以使用標(biāo)簽的,因此這有可能導(dǎo)致加載出來(lái)出來(lái)的布局存在著多余的嵌套結(jié)構(gòu)。
高性能編碼優(yōu)化
都是一些微優(yōu)化,在性能方面看不出有什么顯著的提升的。使用合適的算法和數(shù)據(jù)結(jié)構(gòu)是優(yōu)化程序性能的最主要手段。
1).避免創(chuàng)建不必要的對(duì)象 不必要的對(duì)象我們應(yīng)該避免創(chuàng)建:
如果有需要拼接的字符串,那么可以?xún)?yōu)先考慮使用StringBuffer或者StringBuilder來(lái)進(jìn)行拼接,而不是加號(hào)連接符,因?yàn)槭褂眉犹?hào)連接符會(huì)創(chuàng)建多余的對(duì)象,拼接的字符串越長(zhǎng),加號(hào)連接符的性能越低。
當(dāng)一個(gè)方法的返回值是String的時(shí)候,通常需要去判斷一下這個(gè)String的作用是什么,如果明確知道調(diào)用方會(huì)將返回的String再進(jìn)行拼接操作的話(huà),可以考慮返回一個(gè)StringBuffer對(duì)象來(lái)代替,因?yàn)檫@樣可以將一個(gè)對(duì)象的引用進(jìn)行返回,而返回String的話(huà)就是創(chuàng)建了一個(gè)短生命周期的臨時(shí)對(duì)象。
盡可能地少創(chuàng)建臨時(shí)對(duì)象,越少的對(duì)象意味著越少的GC操作。
2).在沒(méi)有特殊原因的情況下,盡量使用基本數(shù)據(jù)類(lèi)型來(lái)代替封裝數(shù)據(jù)類(lèi)型,int比Integer要更加有效,其它數(shù)據(jù)類(lèi)型也是一樣。
基本數(shù)據(jù)類(lèi)型的數(shù)組也要優(yōu)于對(duì)象數(shù)據(jù)類(lèi)型的數(shù)組。另外兩個(gè)平行的數(shù)組要比一個(gè)封裝好的對(duì)象數(shù)組更加高效,舉個(gè)例子,F(xiàn)oo[]和Bar[]這樣的數(shù)組,使用起來(lái)要比Custom(Foo,Bar)[]這樣的一個(gè)數(shù)組高效的多。
3).靜態(tài)優(yōu)于抽象
如果你并不需要訪(fǎng)問(wèn)一個(gè)對(duì)系那個(gè)中的某些字段,只是想調(diào)用它的某些方法來(lái)去完成一項(xiàng)通用的功能,那么可以將這個(gè)方法設(shè)置成靜態(tài)方法,調(diào)用速度提升15%-20%,同時(shí)也不用為了調(diào)用這個(gè)方法去專(zhuān)門(mén)創(chuàng)建對(duì)象了,也不用擔(dān)心調(diào)用這個(gè)方法后是否會(huì)改變對(duì)象的狀態(tài)(靜態(tài)方法無(wú)法訪(fǎng)問(wèn)非靜態(tài)字段)。
4).對(duì)常量使用static final修飾符
- static int intVal = 42;
- static String strVal = "Hello, world!";
編譯器會(huì)為上面的代碼生成一個(gè)初始方法,稱(chēng)為方法,該方法會(huì)在定義類(lèi)第一次被使用的時(shí)候調(diào)用。這個(gè)方法會(huì)將42的值賦值到intVal當(dāng)中,從字符串常量表中提取一個(gè)引用賦值到strVal上。當(dāng)賦值完成后,我們就可以通過(guò)字段搜尋的方式去訪(fǎng)問(wèn)具體的值了。
final進(jìn)行優(yōu)化:
- static final int intVal = 42;
- static final String strVal = "Hello, world!";
這樣,定義類(lèi)就不需要方法了,因?yàn)樗械某A慷紩?huì)在dex文件的初始化器當(dāng)中進(jìn)行初始化。當(dāng)我們調(diào)用intVal時(shí)可以直接指向42的值,而調(diào)用strVal會(huì)用一種相對(duì)輕量級(jí)的字符串常量方式,而不是字段搜尋的方式。
這種優(yōu)化方式只對(duì)基本數(shù)據(jù)類(lèi)型以及String類(lèi)型的常量有效,對(duì)于其他數(shù)據(jù)類(lèi)型的常量是無(wú)效的。
5).使用增強(qiáng)型for循環(huán)語(yǔ)法
- static class Counter {
- int mCount;
- }
- Counter[] mArray = ...
- public void zero() {
- int sum = 0;
- for (int i = 0; i < mArray.length; ++i) {
- sum += mArray[i].mCount;
- } }
- public void one() {
- int sum = 0;
- Counter[] localArray = mArray;
- int len = localArray.length;
- for (int i = 0; i < len; ++i) {
- sum += localArray[i].mCount;
- }
- }
- public void two() {
- int sum = 0;
- for (Counter a : mArray) {
- sum += a.mCount;
- }
- }
zero()最慢,每次都要計(jì)算mArray的長(zhǎng)度,one()相對(duì)快得多,two()fangfa在沒(méi)有JIT(Just In Time Compiler)的設(shè)備上是運(yùn)行最快的,而在有JIT的設(shè)備上運(yùn)行效率和one()方法不相上下,需要注意這種寫(xiě)法需要JDK1.5之后才支持。
Tips:ArrayList手寫(xiě)的循環(huán)比增強(qiáng)型for循環(huán)更快,其他的集合沒(méi)有這種情況。因此默認(rèn)情況下使用增強(qiáng)型for循環(huán),而遍歷ArrayList使用傳統(tǒng)的循環(huán)方式。
6).多使用系統(tǒng)封裝好的API
系統(tǒng)提供不了的Api完成不了我們需要的功能才應(yīng)該自己去寫(xiě),因?yàn)槭褂孟到y(tǒng)的Api很多時(shí)候比我們自己寫(xiě)的代碼要快得多,它們的很多功能都是通過(guò)底層的匯編模式執(zhí)行的。 舉個(gè)例子,實(shí)現(xiàn)數(shù)組拷貝的功能,使用循環(huán)的方式來(lái)對(duì)數(shù)組中的每一個(gè)元素一一進(jìn)行賦值當(dāng)然可行,但是直接使用系統(tǒng)中提供的System.arraycopy()方法會(huì)讓執(zhí)行效率快9倍以上。
7).避免在內(nèi)部調(diào)用Getters/Setters方法
面向?qū)ο笾蟹庋b的思想是不要把類(lèi)內(nèi)部的字段暴露給外部,而是提供特定的方法來(lái)允許外部操作相應(yīng)類(lèi)的內(nèi)部字段。但在A(yíng)ndroid中,字段搜尋比方法調(diào)用效率高得多,我們直接訪(fǎng)問(wèn)某個(gè)字段可能要比通過(guò)getters方法來(lái)去訪(fǎng)問(wèn)這個(gè)字段快3到7倍。但是編寫(xiě)代碼還是要按照面向?qū)ο笏季S的,我們應(yīng)該在能優(yōu)化的地方進(jìn)行優(yōu)化,比如避免在內(nèi)部調(diào)用getters/setters方法。
九. 插件化相關(guān)技術(shù),熱修補(bǔ)技術(shù)是怎樣實(shí)現(xiàn)的,和插件化有什么區(qū)別
相同點(diǎn):
都使用ClassLoader來(lái)實(shí)現(xiàn)的加載的新的功能類(lèi),都可以使用PathClassLoader與DexClassLoader
不同點(diǎn):
熱修復(fù)因?yàn)槭菫榱诵迯?fù)Bug的,所以要將新的同名類(lèi)替代同名的Bug類(lèi),要搶先加載新的類(lèi)而不是Bug類(lèi),所以多做兩件事:在原先的app打包的時(shí)候,阻止相關(guān)類(lèi)去打上CLASS_ISPREVERIFIED標(biāo)志,還有在熱修復(fù)時(shí)動(dòng)態(tài)改變BaseDexClassLoader對(duì)象間接引用的dexElements,這樣才能搶先代替Bug類(lèi),完成系統(tǒng)不加載舊的Bug類(lèi).
而插件化只是增肌新的功能類(lèi)或者是資源文件,所以不涉及搶先加載舊的類(lèi)這樣的使命,就避過(guò)了阻止相關(guān)類(lèi)去打上CLASS_ISPREVERIFIED標(biāo)志和還有在熱修復(fù)時(shí)動(dòng)態(tài)改變BaseDexClassLoader對(duì)象間接引用的dexElements.
所以插件化比熱修復(fù)簡(jiǎn)單,熱修復(fù)是在插件化的基礎(chǔ)上在進(jìn)行替舊的Bug類(lèi)
十. 怎樣計(jì)算一張圖片的大小,加載bitmap過(guò)程(怎樣保證不產(chǎn)生內(nèi)存溢出),二級(jí)緩存,LRUCache算法。
計(jì)算一張圖片的大小
圖片占用內(nèi)存的計(jì)算公式:圖片高度 圖片寬度 一個(gè)像素占用的內(nèi)存大小.所以,計(jì)算圖片占用內(nèi)存大小的時(shí)候,要考慮圖片所在的目錄跟設(shè)備密度,這兩個(gè)因素其實(shí)影響的是圖片的高寬,android會(huì)對(duì)圖片進(jìn)行拉升跟壓縮。
加載bitmap過(guò)程(怎樣保證不產(chǎn)生內(nèi)存溢出)
由于A(yíng)ndroid對(duì)圖片使用內(nèi)存有限制,若是加載幾兆的大圖片便內(nèi)存溢出。Bitmap會(huì)將圖片的所有像素(即長(zhǎng)x寬)加載到內(nèi)存中,如果圖片分辨率過(guò)大,會(huì)直接導(dǎo)致內(nèi)存OOM,只有在BitmapFactory加載圖片時(shí)使用BitmapFactory.Options對(duì)相關(guān)參數(shù)進(jìn)行配置來(lái)減少加載的像素。
BitmapFactory.Options相關(guān)參數(shù)詳解
(1).Options.inPreferredConfig值來(lái)降低內(nèi)存消耗。
比如:默認(rèn)值A(chǔ)RGB_8888改為RGB_565,節(jié)約一半內(nèi)存。
(2).設(shè)置Options.inSampleSize 縮放比例,對(duì)大圖片進(jìn)行壓縮 。
(3).設(shè)置Options.inPurgeable和inInputShareable:讓系統(tǒng)能及時(shí)回 收內(nèi)存。
A:inPurgeable:設(shè)置為T(mén)rue時(shí),表示系統(tǒng)內(nèi)存不足時(shí)可以被回 收,設(shè)置為False時(shí),表示不能被回收。
B:inInputShareable:設(shè)置是否深拷貝,與inPurgeable結(jié)合使用,inPurgeable為false時(shí),該參數(shù)無(wú)意義。
(4).使用decodeStream代替其他方法。
decodeResource,setImageResource,setImageBitmap等方法
十一. LRUCache算法是怎樣實(shí)現(xiàn)的。
內(nèi)部存在一個(gè)LinkedHashMap和maxSize,把最近使用的對(duì)象用強(qiáng)引用存儲(chǔ)在 LinkedHashMap中,給出來(lái)put和get方法,每次put圖片時(shí)計(jì)算緩存中所有圖片總大小,跟maxSize進(jìn)行比較,大于maxSize,就將最久添加的圖片移除;反之小于maxSize就添加進(jìn)來(lái)。
之前,我們會(huì)使用內(nèi)存緩存技術(shù)實(shí)現(xiàn),也就是軟引用或弱引用,在A(yíng)ndroid 2.3(APILevel 9)開(kāi)始,垃圾回收器會(huì)更傾向于回收持有軟引用或弱引用的對(duì)象,這讓軟引用和弱引用變得不再可靠。
算法
一. 算法題
m n的矩陣,能形成幾個(gè)正方形(2 2能形成1個(gè)正方形,2 3 2個(gè),3 3 6個(gè))
計(jì)數(shù)的關(guān)鍵是要觀(guān)察到任意一個(gè)傾斜的正方形必然唯一內(nèi)接于一個(gè)非傾斜的正方形,而一個(gè)非傾斜的邊長(zhǎng)為k的非傾斜正方形,一條邊上有k-1個(gè)內(nèi)點(diǎn),每個(gè)內(nèi)點(diǎn)恰好確定一個(gè)內(nèi)接于其中的傾斜正方形,加上非傾斜正方形本身,可知,將邊長(zhǎng)為k的非傾斜正方形數(shù)目乘以k,再按k求和即可得到所有正方形的數(shù)目。
- 設(shè)2≤n≤m,k≤n-1,則邊長(zhǎng)為k的非傾斜有
- (n-k)(m-k)個(gè),故所有正方形有
- ∑(m-k)(n-k)k個(gè)
- 例如m=n=4
- 正方形有33=20個(gè)
下面是面試過(guò)程中遇到的題目
大多數(shù)題目都可以在上面找到答案.
電話(huà)面試題
- ArrayList 和 Hashmap 簡(jiǎn)單說(shuō)一些,區(qū)別,底層的數(shù)據(jù)結(jié)構(gòu).
- Handler 消息機(jī)制
- 引起內(nèi)存泄漏的場(chǎng)景
- 多線(xiàn)程的使用場(chǎng)景?
- 常用的線(xiàn)程池有哪幾種?
- 在公司做了什么?團(tuán)隊(duì)規(guī)模?為什么離職?
面試中實(shí)際涉及到的問(wèn)題
第一輪
- 知道哪些單例模式,寫(xiě)一個(gè)線(xiàn)程安全的單例,并分析為什么是線(xiàn)程安全的?
- Java中的集合有哪些?解釋一下HashMap?底部的數(shù)據(jù)結(jié)構(gòu)?散列表沖突的處理方法,散列表是一個(gè)什么樣的數(shù)據(jù)結(jié)構(gòu)?HashMap是采用什么方法處理沖突的?
- 解釋一下什么是MVP架構(gòu),畫(huà)出圖解,一句話(huà)解釋MVP和MVC的區(qū)別?
- Handle消息機(jī)制?在使用Handler的時(shí)候要注意哪些東西,是否會(huì)引起內(nèi)存泄漏?畫(huà)一下Handler機(jī)制的圖解?
- 是否做過(guò)性能優(yōu)化?已經(jīng)采取了哪些措施進(jìn)行優(yōu)化?
- 引起內(nèi)存泄漏的原因是什么?以及你是怎么解決的?
這些問(wèn)題應(yīng)該都是比較基礎(chǔ)的問(wèn)題,每個(gè)開(kāi)發(fā)者都應(yīng)該是非常熟悉并能詳細(xì)敘述的.這一輪的面試官問(wèn)的技術(shù)都是平時(shí)用到的.
第二輪
- 關(guān)于并發(fā)理解多少?說(shuō)幾個(gè)并發(fā)的集合?
- Handler 消息機(jī)制圖解?
- 在項(xiàng)目中做了哪些東西?
- 畫(huà)圖說(shuō)明View 事件傳遞機(jī)制?并舉一個(gè)例子闡述
- 類(lèi)加載機(jī)制,如何換膚,換膚插件中存在的問(wèn)題?hotfix是否用過(guò),原理是否了解?
- 說(shuō)說(shuō)項(xiàng)目中用到了哪些設(shè)計(jì)模式,說(shuō)了一下策略模式和觀(guān)察者模式?
- 會(huì)JS么?有Hybid開(kāi)發(fā)經(jīng)驗(yàn)么?
- 說(shuō)一下快排的思想?手寫(xiě)代碼
- 堆有哪些數(shù)據(jù)結(jié)構(gòu)?
對(duì)于這輪米那是明顯感覺(jué)到壓力,知識(shí)的縱向了解也比較深,應(yīng)該是個(gè)leader.
第三輪
- 介紹一下在項(xiàng)目中的角色?
- 遇到困難是怎么解決的?
- 如何與人相處,與別人意見(jiàn)相左的時(shí)候是怎么解決的,并舉生活中的一個(gè)例子.
- 有沒(méi)有壓力特別大的時(shí)候?
這個(gè)應(yīng)該是項(xiàng)目經(jīng)理了,問(wèn)的問(wèn)題偏向于生活性格方面.
以上面試中問(wèn)到的題目基本上都可以在上面找到答案,所以做準(zhǔn)備是很重要的,但技術(shù)是一點(diǎn)點(diǎn)積累的,就算你全會(huì)背了,面試過(guò)了,真正等到工作的時(shí)候還是會(huì)捉襟見(jiàn)肘的,所以踏實(shí)點(diǎn)吧騷年.