1、HashMap、HashTable、ConcurrentHashMap的區別
(關於HashMap的分析,在第三篇總結《Java筆試面試題整理第三波》中的hashCode有分析,同樣在這篇中有關於Java容器的介紹。HashMap和HashTable都屬於Map類集合。)
HashMap和HashTable都實現了Map接口,裏面存放的元素不保證有序,並且不存在相同元素;
區別(線程安全和保存值是否爲null方面):
(1) HashMap和HashTable在功能上基本相同,但HashMap是線程不安全的,HashTable是線程安全的;
HashMap的put源碼如下:
-
public V put(K key, V value) {
-
if (table == EMPTY_TABLE) {
-
inflateTable(threshold);
-
}
-
if (key == null)
-
return putForNullKey(value);
-
int hash = hash(key);
-
int i = indexFor(hash, table.length);
-
for (Entry<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;
-
}
(2)可以看到,HashMap的key和value都是可以爲null的,當get()方法返回null值時,HashMap中可能存在某個key,只不過該key值對應的value爲null,也有可能是HashM中不存在該key,所以不能使用get()==null來判斷是否存在某個key值,對於HashMap和HashTable,提供了containsKey()方法來判斷是否存在某個key。
HashTable的put源碼如下:
-
public synchronized V put(K key, V value) {
-
-
if (value == null) {
-
throw new NullPointerException();
-
}
-
-
-
Entry tab[] = table;
-
int hash = hash(key);
-
int index = (hash & 0x7FFFFFFF) % tab.length;
-
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
-
if ((e.hash == hash) && e.key.equals(key)) {
-
V old = e.value;
-
e.value = value;
-
return old;
-
}
-
}
-
-
modCount++;
-
if (count >= threshold) {
-
-
rehash();
-
-
tab = table;
-
hash = hash(key);
-
index = (hash & 0x7FFFFFFF) % tab.length;
-
}
-
-
-
Entry<K,V> e = tab[index];
-
tab[index] = new Entry<>(hash, key, value, e);
-
count++;
-
return null;
-
}
(3)HashTable是不允許key和value爲null的。HashTable中的方法大部分是同步的,因此HashTable是線程安全的。
拓展:
(1) 影響HashMap(或HashTable)性能的兩個因素:初始容量和load factor;
HashMap中有如下描述: When the number of entries in the hash table exceeds the product of the load factor and the current capacity,
the hash table is <i>rehashed</i> (that is, internal data structures are rebuilt)
當我們Hash表中數據記錄的大小超過當前容量,Hash表會進行rehash操作,其實就是自動擴容,這種操作一般會比較耗時。所以當我們能夠預估Hash表大小時,在初始化的時候就儘量指定初始容量,避免中途Hash表重新擴容操作,如:
-
HashMap<String, Integer> map = new HashMap<String, Integer>(20);
(類似可以指定容量的還有ArrayList、Vector)
(2)使用選擇上,當我們需要保證線程安全,HashTable優先選擇。當我們程序本身就是線程安全的,HashMap是優先選擇。
其實HashTable也只是保證在數據結構層面上的同步,對於整個程序還是需要進行多線程併發控制;在JDK後期版本中,對於HashMap,可以通過Collections獲得同步的HashMap;如下:
Map m = Collections.synchronizedMap(new HashMap(...));
這種方式獲得了具有同步能力的HashMap。
(3)在JDK1.5以後,出現了ConcurrentHashMap,它可以很好地解決在併發程序中使用HashMap的問題,ConcurrentHashMap和HashTable功能很像,不允許爲null的key或value,但它不是通過給方法加synchronized方法進行併發控制的。
在ConcurrentHashMap中使用分段鎖技術Segment,將數據分成一段一段的存儲,然後給每一段數據配一把鎖,當一個線程佔用鎖訪問其中一個段數據的時候,其他段的數據也能被其他線程訪問,能夠實現真正的併發訪問。效率也比HashTable好的多。
(關於ConcurrentHashMap具體可以參考:
2、TreeMap、HashMap、LinkedHashMap的區別
關於Map集合,前面幾篇都有講過,可以去回顧一下。而TreeMap、HashMap、LinkedHashMap都是Map的一些具體實現類,其關係圖如下:
其中,HashMap和HashTable主要區別在線程安全方面和存儲null值方面。HashMap前面討論的已經比較多了,下面說說LinkedHashMap和TreeMap。
(1)LinkedHashMap保存了數據的插入順序,底層是通過一個雙鏈表的數據結構來維持這個插入順序的。key和value都可以爲null;
(2)TreeMap實現了SortMap接口,它保存的記錄是根據鍵值key排序,默認是按key升序排列。也可以指定排序的Comparator。
HashMap、LinkedHashMap和TreeMap都是線程不安全的,HashTable是線程安全的。提供兩種遍歷Map的方法如下:
(1)推薦方式:
-
Map<String, Integer> map = new HashMap<String, Integer>(20);
-
for(Map.Entry<String, Integer> entry : map.entrySet()){
-
System.out.println("key-->"+entry.getKey()+",value-->"+m.get(entry.getValue()));
-
}
這種方式相當於首先通過Set<Map.Entry<String,Integer>> set = map.entrySet();方式拿到Set集合,而Set集合是可以通過foreach的方式遍歷的。
(2) 普通方式:
-
Map<String, Integer> map = new HashMap<String, Integer>(20);
-
Iterator<String> keySet = map.keySet().iterator();
-
while(keySet .hasNext()){
-
Object key = keySet .next();
-
System.out.println("key-->"+key+",value-->"+m.get(key));
-
}
3、Collection包結構,與Collections的區別。
Collection的包結構如下:
Statck類爲Vector的子類。由於Collection類繼承Iterable類,所以,所有Collection的實現類都可以通過foreach的方式進行遍歷。
Collections是針對集合類的一個幫助類。提供了一系列靜態方法實現對各種集合的搜索、排序、線程完全化等操作。
當於對Array進行類似操作的類——Arrays。
如,Collections.max(Collection coll); 取coll中最大的元素。
Collections.sort(List list); 對list中元素排序
4、OOM你遇到過哪些情況,SOF你遇到過哪些情況
OOM:OutOfMemoryError異常,
即內存溢出,是指程序在申請內存時,沒有足夠的空間供其使用,出現了Out
Of Memory,也就是要求分配的內存超出了系統能給你的,系統不能滿足需求,於是產生溢出。
內存溢出分爲上溢和下溢,比方說棧,棧滿時再做進棧必定產生空間溢出,叫上溢,棧空時再做退棧也產生空間溢出,稱爲下溢。
有時候內存泄露會導致內存溢出,所謂內存泄露(memory leak),是指程序在申請內存後,無法釋放已申請的內存空間,一次內存泄露危害可以忽略,但內存泄露堆積後果很嚴重,無論多少內存,遲早會被佔光,舉個例子,就是說系統的籃子(內存)是有限的,而你申請了一個籃子,拿到之後沒有歸還(忘記還了或是丟了),於是造成一次內存泄漏。在你需要用籃子的時候,又去申請,如此反覆,最終系統的籃子無法滿足你的需求,最終會由內存泄漏造成內存溢出。
遇到的OOM:
(1)Java Heap 溢出
Java堆用於存儲對象實例,我們只要不斷的創建對象,而又沒有及時回收這些對象(即內存泄漏),就會在對象數量達到最大堆容量限制後產生內存溢出異常。
(2)方法區溢出
方法區用於存放Class的相關信息,如類名、訪問修飾符、常量池、字段描述、方法描述等。
異常信息:Java.lang.OutOfMemoryError:PermGen space
方法區溢出也是一種常見的內存溢出異常,一個類如果要被垃圾收集器回收,判定條件是很苛刻的。在經常動態生成大量Class的應用中,要特別注意這點。
SOF:StackOverflow(堆棧溢出)
當應用程序遞歸太深而發生堆棧溢出時,拋出該錯誤。因爲棧一般默認爲1-2m,一旦出現死循環或者是大量的遞歸調用,在不斷的壓棧過程中,造成棧容量超過1m而導致溢出。
棧溢出的原因:
(1)遞歸調用
(2)大量循環或死循環
(3)全局變量是否過多
(4)數組、List、Map數據過大
OOM在Android開發中出現比較多:
場景有: 加載的圖片太多或圖片過大時、分配特大的數組、內存相應資源過多沒有來不及釋放等。
解決方法:
(1)在內存引用上做處理
軟引用是主要用於內存敏感的高速緩存。在jvm報告內存不足之前會清除所有的軟引用,這樣以來gc就有可能收集軟可及的對象,可能解決內存吃緊問題,避免內存溢出。什麼時候會被收集取決於gc的算法和gc運行時可用內存的大小。
(2)對圖片做邊界壓縮,配合軟引用使用
(3)顯示的調用GC來回收內存,如:
if
(bitmapObject.isRecycled()==
false
)
//如果沒有回收
bitmapObject.recycle();
(4)優化Dalvik虛擬機的堆內存分配
》增強程序堆內存的處理效率
//在程序onCreate時就可以調用 即可
private
final
static
float
TARGET_HEAP_UTILIZATION =
0
.75f;
VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION);
》設置堆內存的大小
private
final
static
int
CWJ_HEAP_SIZE
=
6
*
1024
*
1024
;
//設置最小heap內存爲6MB大小
VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE);
(5)用LruCache 和 AsyncTask<>解決
從cache中去取Bitmap,如果取到Bitmap,就直接把這個Bitmap設置到ImageView上面。
如果緩存中不存在,那麼啓動一個task去加載(可能從文件來,也可能從網絡)。
5、Java面向對象的三個特徵與含義,多態的實現方式
Java中兩個非常重要的概念:類和對象。類可以看做是一個模板,描述了一類對象的屬性和行爲;而對象是類的一個具體實現。Java面向對象的三大基本特徵:
(1)封裝
屬性用來描述同一類事物的特徵,行爲用來描述同一類事物可做的一些操作。封裝就是把屬於同一類事物的共性(屬性和行爲)歸到一個類中,只保留有限的接口和方法與外部進行交互,避免了外界對對象內部屬性的破壞。Java中使用訪問控制符來保護對類、屬性、方法的訪問。
(2)繼承
子類通過這種方式來接收父類所有的非private的屬性和方法(構造方法除外)。這裏的接收是直接擁有的意思,即可以直接使用父類字段和方法,因此,繼承相當於“擴展”,子類在擁有了父類的屬性和特徵後,可以專心實現自己特有的功能。
(構造方法不能被繼承,因爲在創建子類時,會先去自動“調用”父類的構造方法,如果真的需要子類構造函數特殊的形式,子類直接修改或重載自己的構造函數就好了。)
(3)多態
多態是程序在運行的過程中,同一種類型在不同的條件下表現不同的結果。比如:
Animal a = new Dog(); // 子類對象當做父類對象來使用,運行時,根據對象的實際類型去找子類覆蓋之後的方法
多態實現方式:
(1)設計時多態,通過方法的重載實現多態;
(2)運行時多態,通過重寫父類或接口的方法實現運行時多態;
6、interface與abstract類的區別
abstract class 只能被繼承extends,體現的是一種繼承關係,而根據繼承的特徵,有繼承關係的子類和父類應該是一種“is-a”的關係,也即兩者在本質上應該是相同的(有共同的屬性特徵)。
interface 是用來實現的implements,它並不要求實現者和interface之間在本質上相同,是一種“like-a”的關係,interface只是定義了一系列的約定而已(實現者表示願意遵守這些約定)。所以一個類可以去實現多個interface(即該類遵守了多種約定)。
很多情況下interface和abstract都能滿足我們要求,在我們選擇用abstract火interface的時候,儘量符合上面的要求,即如果兩者間本質是一樣的,是一種“is-a”的關係,儘量用abstract,當兩者之間本質不同只是簡單的約定行爲的話,可以選擇interface。
特點:
(1)abstract類其實和普通類一樣,擁有有自己的數據成員和方法,只不過abstract類裏面可以定義抽象abstract的方法(聲明爲abstract的類也可以不定義abstract的方法,直接當做普通類使用,但是這樣就失去了抽象類的意義)。
(2)一個類中聲明瞭abstract的方法,該類必須聲明爲abstract類。
(3)interface中只能定義常量和抽象方法。在接口中,我們定義的變量默認爲public
static final類型,所以不可以在顯示類中修改interface中的變量;定義的方法默認爲public abstract,其中abstract可以不明確寫出。
7、static class 與non static class的區別
static class--靜態內部類,non static class--非靜態內部類,即普通內部類
普通內部類:
內部類可以直接使用外部類的所有變量(包括private、靜態變量、普通變量),這也是內部類的主要優點(不用生成外部類對象而直接使用外部類變量)。如下例子:
-
public class OutClass {
-
private String mName = "lly";
-
static int mAge = 12;
-
-
class InnerClass{
-
String name;
-
int age;
-
private void getName(){
-
name = mName;
-
age = mAge;
-
System.out.println("name="+name+",age="+age);
-
}
-
}
-
-
public static void main(String[] args) {
-
-
OutClass.InnerClass innerClass = new OutClass().new InnerClass();
-
innerClass.getName();
-
-
OutClass out = new OutClass();
-
InnerClass in = out.new InnerClass();
-
in.getName();
-
}
-
}
輸出:name=lly,age=12
可以看到,內部類裏面可以直接訪問外部類的靜態和非靜態變量,包括private變量。在內部類中,我們也可以通過外部類.this.變量名的方式訪問外部類變量,如:name = OutClass.this.mName;
內部類的初始化依賴於外部類,只有外部類初始化出來了,內部類才能夠初始化。
私有內部類(包括私有靜態內部類和私有非靜態內部類):
如果一個內部類只希望被外部類中的方法操作,那隻要給該內部類加上private修飾,聲明爲private 的內部類只能在外部類中使用,不能在別的類中new出來。如上private
class InnerClass{...},此時只能在OutClass類中使用。
靜態內部類:
靜態內部類只能訪問外部類中的靜態變量。如下:
-
public class OutClass {
-
private String mName = "lly";
-
static int mAge = 12;
-
-
static class StaticClass{
-
String name = "lly2";
-
int age;
-
private void getName(){
-
-
age = mAge;
-
System.out.println("name="+name+",age="+age);
-
}
-
}
-
-
public static void main(String[] args) {
-
-
OutClass.StaticClass staticClass = new OutClass.StaticClass();
-
staticClass.getName();
-
-
StaticClass staticClass2 = new StaticClass();
-
staticClass2.getName();
-
}
-
}
輸出:name=lly2,age=12
可以看到,靜態內部類只能訪問外部類中的靜態變量,靜態內部類的初始化不依賴於外部類,由於是static,類似於方法使用,OutClass.StaticClass是一個整體。
匿名內部類:
匿名內部類主要是針對抽象類和接口的具體實現。在Android的監聽事件中用的很多。如:
-
textView.setOnClickListener(new View.OnClickListener(){
-
public void onClick(View v){
-
...
-
}
-
});
對於抽象類:
-
public abstract class Animal {
-
public abstract void getColor();
-
}
-
-
public class Dog{
-
public static void main(String[] args) {
-
Animal dog = new Animal() {
-
@Override
-
public void getColor() {
-
System.out.println("黑色");
-
}
-
};
-
dog.getColor();
-
}
-
}
輸出:黑色
對於接口類似,只需要把abstract class 改爲interface即可。