回顧下一次面試經歷,去的是一家文學網站的Android開發應聘,面試過程中回答的一些面試題有些片面或者比較不全面,現在抽時間進行整體總結下:
一、Java方面:
1、二進制和異或的算法?
這個當時估計也是想考察下最基礎的Java知識點,簡單問了1和2的二進制以及其異或後的結果。首先考察了二進制和十進制的轉換,其次考察了異或的知識點,涉及知識點或者延申知識點:
1)十進制轉換爲二進制
關於進制間轉換,我專門寫過一篇博客,需要學習可以參考下二進制轉換
2)位運算符
&:按位與 —— 只有兩個操作數對應位同爲1時,結果爲1,其餘全爲0.
| :按位或 —— 只有兩個操作數對應位同爲0時,結果爲0,其餘全爲1.
~:按位非 —— 原碼 --取反-> 反碼 --加1-> 補碼 --取反-> 按位非值(負數);原碼(補碼 ) --取反-> 按位非值(正數).
^:按位異或 —— 異:1 ,同:0
<<:左移位運算符 —— 丟棄最高位,0補最低位;如果移動的位數超過了該類型的最大位數,那麼編譯器會對移動的位數取 模。如對int型移動33位,實際上只移動了332=1位。
>>:右移位運算符 —— 符號位不變,左邊補上符號位 。 按二進制形式把所有的數字向右移動對應的位數,低位移出(舍 棄),高位的空位補符號位,即正數補零,負數補1,當右移的運算數是byte 和short類型時,將自動把 這些類型擴大爲 int 型
>>>:無符號右移運算符
位運 算 符 中 ,除 ~ 以 外 ,其餘 均 爲 二 元 運 算 符 。 操 作 數 只 能 爲 整 型 和字 符 型 數 據 。
在Java語言中,二進制數使用補碼錶示,最高位爲符號位,正數的符號位爲0,負數爲1。補碼的表示需要滿足如下要求。
(a)正數的最高位爲0,其餘各位代表數值本身(二進制數)。
(b)對於負數,通過對該數絕對值的補碼按位取反,再對整個數加1。
具體分析,後續博客會進行詳盡的總結(未完待續。。。)。
2、HashMap中元素可以有null麼,原因是什麼?
HashMap的key 和 value 都可以爲null,但是HashTable的key 和 value 都不可以爲null;並且兩者的key都不能重複,若是添加的key值相同,後面的value會自動覆蓋前面的value,但是不會報錯。
hashMap是根據key 的 hashCode來尋找存放位置的,當key 爲null的時候在put 方法中,處理了key== null的情況:
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for the key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with <tt>key</tt>.)
*/
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key);
int i = indexFor(hash, table.length);
for (HashMapEntry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
從put方法可以看出key == null 的時候直接執行putForNullKey這個方法:
/**
* Offloaded version of put for null keys
*/
private V putForNullKey(V value) {
for (HashMapEntry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}
在for循環中,是在table[0]鏈表中查找key == null 的元素,如果找到,則將這個值重新賦值給這個元素的value,並返回原來的value 。具體HashMap的底層存儲分析可以參考博客:https://www.cnblogs.com/peizhe123/p/5790252.html
3、內存?
內存與PC的內存是一樣的,是用來運行程序,不能用來永久存儲數據,手機一旦關機,在內存中的所有數據都將會丟失,內存也是現在人類製造的所有電子設備所必需擁有的。
內部存儲,一般可以存儲在以下的 目錄文件中:
1.data/data/包名/shared_prefs
2.data/data/包名/databases
3.data/data/包名/files
4.data/data/包名/cache
外部存儲,一般在storage 或者 mnt 的目錄文件下,這個具體由各大手機的廠商來決定。
具體的存儲分析可以參考這篇博客:https://blog.csdn.net/wulex/article/details/72899363,關於Android的數據存儲的分析後期也會自己做一些相關的分析總結(未完待續。。。)
4、多線程併發,多線程可以同時訪問數據庫數據麼,會出現什麼情況?
/**
* 多線程
*/
private void threadTest() {
//方法一:線程啓動
new Thread() {
@Override
public void run() {
super.run();
//耗時操作
}
}.start();
//方法二:線程啓動
new Thread(new Runnable() {
@Override
public void run() {
//耗時操作
}
}).start();
}
這是兩種啓動線程的方法,看起來好像是一樣的,其實不是,方法一是覆寫了Thread類中run 方法,方法二通過Thread的構造函數傳遞了Runnable對象,而在其中執行耗時操作。但是其實分析兩個方法的源碼,發現最終執線程執行的任務是Runnable。
方法一中覆寫了run()方法,其實
@Override
public void run() {
if (target != null) {
target.run();
}
}
在這裏target其實就是Runnable對象,Thread其實就是對Runnable的包裝。當啓動一個線程的時候,如果Thread的target不爲空,則會在子線程中執行這個run 方法中的耗時操作,否則虛擬機會執行該線程自身run函數。具體線程基本問題後期會具體分析(未完待續。。。)
多線程訪問數據庫,一般是通過加鎖synchronize來解決併發問題的,給增刪改查方法加鎖,但是這種方式的效率比較低。可以用隊列來解決這個問題:
思路就是:創建一個單線程的線程池,所有的數據庫操作都用這個線程池來操作
https://blog.csdn.net/lizhengwei1989/article/details/69264554
5、ArrayList 和 LinkedList 的區別?
ArrayList : 採用數組的形式來保存數據,採用順序存儲方式,對象存儲在連續的位置中,最大缺點是插入和刪除非常麻煩。
LinkedList :採用雙向鏈表的存儲方式,但是不是首尾相連接的循環鏈表,對象存放在獨立的空間中,且每個空間中還存儲着下一個鏈接的索引,最大的缺點就是查找非常麻煩,需要從第一個索引開始。
兩者都實現List接口,並同時擁有Collection接口,Android開發中使用區別:
1)、ArrayList實現了基於動態數組的數據結構,LinkedList基於鏈表的數據結構;
2)、對於隨機訪問get() set(),ArrayList優於LinkedList,因爲LinkedList要移動指針;
3)、對於新增add()和刪除remove操作,LinkedList比較佔優勢,因爲ArrayList要移動數據;
4)、ArrayList線程是不同步的。
6、內部類?
內部類分爲成員內部類、方法內部類、匿名內部類,靜態內部類。
內部類隱藏你不想讓別人知道的操作,即封裝性;
一個內部類對象可以訪問創建他的外部類對象的內容,甚至包括私有變量;
靜態內部類沒有了指向外部類的引用;
多繼承更簡單易懂;
可以使用來寫測試用例。
7、進程及線程,Service屬於進程還是線程,進程的優先級?
在Android的底層,進程構造了底部的一個運行池,不僅僅是Task中的各個Activity組件,其他三大組件Service、Content Provider、Broadcast Receiver,都是寄宿在底層某個進程中,進行運轉;service 和activity 一樣是Android一個組件, 依賴進程的主線程上,處理跟交互UI無關工作。
具體分析區別曾經轉載過一片博客進程與線程分析
進程優先級:前臺進程 > 服務進程 > 後臺進程
- 後臺服務會隨着app的退出而退出
- 前臺服務在app退出後仍然在運行
- 服務進程比後臺進程優先級高
- 服務比線程更方便控制
- Service的onCreate、onBind、onStartCommand運行在主線程中,可以在Service中開啓子線程
8、Java中== 與 equals 的區別?
== : 基本數據類型,比較的是他們的值;
其他類型比較的是操作符兩端是否是同一個對象;
兩邊的操作數必須是同一個類型才能編譯通過;
比較的是地址,如果是具體的阿拉伯數字的比較,值相等則爲true,如:
int a=10 與 long b=10L 與 double c=10.0都是相同的(爲true),因爲他們都指向地址爲10的堆。
equals: 用來比較的是兩個對象的內容是否相等;
在比較兩個字符串是否相等的時候,也可以說是在比較他倆的值是否相等。
9、switch參數可以是long , int ,String 麼?
switch表達式後面的數據類型只能是byte,short,char,int四種數據類型,枚舉類型和java.lang.String類型(從java 7才允 許),switch能夠支持他們的包裝類,是因爲自動拆箱(從JDK1.5開始支持自動拆箱和自動裝箱,自動拆箱就是自動將引用 數據類型轉化爲基本數據類型,自動裝箱就是自動將基本數據類型轉化爲引用數據類型)的原因。
二、Android方面
1、app銷燬後內存是否釋放?
程序退出後,還有一些內存沒有釋放。銷燬進程殺死,可以釋放內存。
2、LinearLayout和RelativeLayout哪個效率更高?
(1)RelativeLayout慢於LinearLayout是因爲它會讓子View調用2次measure過程,而後者只需一次,但是有weight屬性存在時,後者同樣會進行兩次measure。
(2)RelativeLayout的子View如果高度和RelativeLayout不同,會引發效率問題,可以使用padding代替margin以優化此問題。
(3)在不響應層級深度的情況下,使用Linearlayout而不是RelativeLayout。
具體分析:https://www.cnblogs.com/qitian1/p/6461473.html
3、Glide與Picasso 的比較?
1)、Glide的庫大小和方法的數量遠遠大於Picasso;
2)、Glide的使用方法與Picasso類似,但是Glide與Activity以及Fragment的生命週期有關係;
3)、Glide可以加載gif圖片,Picasso 不行;
4)、兩個庫都支持緩存圖片,都通過下載網絡圖片緩存到本地。但是緩存機制兩個庫有不同的做法:
Picasso 是將網絡圖片緩存到本地,如果需要改變圖片尺寸,一般先從網絡緩存完整圖片尺寸,然後再進行裁剪得到需要 的尺寸。
Glide 不同的尺寸需求從網絡獲取圖片後先進行裁剪獲得合適尺寸圖片然後再緩存到本地,同一網絡圖片可以緩存不同尺 寸的本地圖片。
5)、Glide默認使用RGB_555 的設定,Picasso默認使用ARGB _8888的設定。Picasso是緩存full size 的圖片,而Glide是加載 已經改變後的圖片,所以內存相對於Picasso會小一點,這樣可以減少 OutOfMemoryError 的可能性;
6)、加載圖片Picasso相對於Glide會快一點,因爲Picasso是直接把圖加載到內存中,而Glide需要先改變圖片尺寸再加載到內 存中;從緩存中加載圖片Glide相對於Picasso要快一點,因爲Glide是直接從內存中加載圖片,而Picasso從內存得到圖片 還需要進行裁剪再加載。
4、未完待續。。。